Compare commits

..

37 Commits

Author SHA1 Message Date
Kendall Garner 68476deb98 fix album song count 2025-10-12 16:44:05 -07:00
jeffvli c88c6cf55e add mediasession playback controls 2025-10-12 16:37:24 -07:00
jeffvli f332d547a3 rename lint job 2025-10-12 16:20:50 -07:00
jeffvli 5803ac04d1 update linux desktop file instructions with disclaimer 2025-10-12 16:08:59 -07:00
jeffvli 58becc5c8e add useMediaSession hook to set metadata and status 2025-10-12 16:07:59 -07:00
jeffvli 309b49b46e refactor playerbar slider to separate component 2025-10-12 16:07:00 -07:00
jeffvli 40e7eda882 hide mediasession setting on desktop non-windows 2025-10-12 15:53:16 -07:00
jeffvli 1081829aa4 add new languages to config 2025-10-12 15:43:34 -07:00
Hosted Weblate 5a411417b0 Translated using Weblate (German)
Currently translated at 86.3% (613 of 710 strings)

Translated using Weblate (German)

Currently translated at 86.3% (613 of 710 strings)

Co-authored-by: Erik Val <Elaktrato@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Trrevvoorr <trevinofficial@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/
Translation: feishin/Translation
2025-10-12 15:29:08 -07:00
Hosted Weblate 8472a4013e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (714 of 714 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (714 of 714 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (706 of 706 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: 無情天 <kofzhanganguo@126.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hans/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 14bb135116 Translated using Weblate (French)
Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Translated using Weblate (French)

Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Dylan MONTIGAUD <dylanmontigaud17@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 9583c3343b Translated using Weblate (Spanish)
Currently translated at 100.0% (714 of 714 strings)

Translated using Weblate (Spanish)

Currently translated at 99.8% (709 of 710 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (705 of 705 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (701 of 701 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (699 of 699 strings)

Co-authored-by: Aitor Astorga <a.astorga.sdv@protonmail.com>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jaime-grj <weblate.4ljj9@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate d28b90afa6 Translated using Weblate (Arabic)
Currently translated at 16.3% (114 of 699 strings)

Added translation using Weblate (Arabic)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: xB <abxb19@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ar/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 479b93410f Translated using Weblate (Italian)
Currently translated at 100.0% (706 of 706 strings)

Co-authored-by: Daivy <reale805@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/it/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate 3496e1c938 Translated using Weblate (Polish)
Currently translated at 97.9% (685 of 699 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: skajmer <skajmer@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/
Translation: feishin/Translation
2025-10-12 15:06:43 -07:00
Hosted Weblate be44906b49 Translated using Weblate (Czech)
Currently translated at 100.0% (710 of 710 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (705 of 705 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (701 of 701 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 31488ce973 Translated using Weblate (Catalan)
Currently translated at 100.0% (706 of 706 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (706 of 706 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ondo <SparkyOndo@proton.me>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 0b0696a42b Translated using Weblate (Japanese)
Currently translated at 72.8% (517 of 710 strings)

Co-authored-by: Erik Val <Elaktrato@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 6d51f66641 Translated using Weblate (Basque)
Currently translated at 73.3% (521 of 710 strings)

Translated using Weblate (Basque)

Currently translated at 57.7% (407 of 705 strings)

Translated using Weblate (Basque)

Currently translated at 44.1% (311 of 705 strings)

Translated using Weblate (Basque)

Currently translated at 41.7% (293 of 701 strings)

Translated using Weblate (Basque)

Currently translated at 15.5% (109 of 699 strings)

Translated using Weblate (Basque)

Currently translated at 11.1% (78 of 699 strings)

Translated using Weblate (Basque)

Currently translated at 7.8% (55 of 699 strings)

Added translation using Weblate (Basque)

Co-authored-by: Aitor Astorga <a.astorga.sdv@protonmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/eu/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 9ec8ec806d Translated using Weblate (German)
Currently translated at 86.3% (613 of 710 strings)

Translated using Weblate (German)

Currently translated at 86.3% (613 of 710 strings)

Co-authored-by: Erik Val <Elaktrato@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Trrevvoorr <trevinofficial@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/
Translation: feishin/Translation
2025-10-12 15:06:42 -07:00
Hosted Weblate 1baae08dc7 Translated using Weblate (Turkish)
Currently translated at 99.4% (701 of 705 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Slincess <kisacikdevran0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/tr/
Translation: feishin/Translation
2025-10-12 15:06:25 -07:00
Flame Sage b987324899 Added a Linux Notes section below MacOS for desktop. (#1020)
* Added a Linux Notes section below MacOS for desktop.
2025-10-12 15:01:18 -07:00
Mike Benz f07393c82a enable mediaSession api (#1040)
* enable mediaSession api
2025-10-12 14:59:30 -07:00
Lyall 3636384508 show time remaining instead of duration on click (#1179)
* show time remaining on duration click
2025-10-12 14:22:58 -07:00
jeffvli fc847631d3 move prerelease set to new step 2025-10-12 03:57:08 -07:00
jeffvli 1d9f462959 fix prerelease notes 2025-10-12 03:56:57 -07:00
jeffvli 369b956c66 remove EP_PRE_RELEASE variable on publish 2025-10-12 03:30:36 -07:00
jeffvli 5f14ccf70d set electron-builder publish to draft, convert to prerelease after build 2025-10-12 03:21:09 -07:00
jeffvli f9679f3bda handle prerelease versions in autoupdater beta channel 2025-10-12 02:33:49 -07:00
jeffvli 9f8d9a5b28 move update settings to advanced tab 2025-10-12 02:33:35 -07:00
jeffvli 912aea8174 rename latest channel string to "stable" 2025-10-12 02:31:58 -07:00
jeffvli 9ae4be9336 add missing publish to win beta 2025-10-12 01:05:54 -07:00
jeffvli 1686e7ad0b add separate beta publish scripts 2025-10-12 00:54:07 -07:00
jeffvli bf6047da17 add separate electron builder config for beta channel 2025-10-12 00:53:05 -07:00
jeffvli c3b4a9edf8 attempt fix on beta channel release files 2025-10-11 20:25:33 -07:00
jeffvli 0340cc8a85 update to v0.21.0 2025-10-11 20:12:17 -07:00
jeffvli 3f7a402ce8 add commit notes to beta deploy 2025-10-11 20:05:26 -07:00
35 changed files with 1544 additions and 207 deletions
+118 -19
View File
@@ -144,70 +144,62 @@ jobs:
if: matrix.os == 'windows-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_PRE_RELEASE: true
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:win
pnpm run publish:win
pnpm run publish:win:beta
on_retry_command: pnpm cache delete
- name: Build and Publish releases (macOS)
if: matrix.os == 'macos-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_PRE_RELEASE: true
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:mac
pnpm run publish:mac
pnpm run publish:mac:beta
on_retry_command: pnpm cache delete
- name: Build and Publish releases (Linux)
if: matrix.os == 'ubuntu-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_PRE_RELEASE: true
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux
pnpm run publish:linux
pnpm run publish:linux:beta
on_retry_command: pnpm cache delete
- name: Build and Publish releases (Linux ARM64)
if: matrix.os == 'ubuntu-latest'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_PRE_RELEASE: true
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux-arm64
pnpm run publish:linux-arm64
pnpm run publish:linux-arm64:beta
on_retry_command: pnpm cache delete
rename-release:
edit-release:
needs: [prepare, publish]
runs-on: ubuntu-latest
steps:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Rename release title to Beta
- name: Edit release with commits and title
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -215,19 +207,126 @@ jobs:
# Get the version from the prepare job
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
$tagVersion = "v" + $versionWithBeta
Write-Host "Renaming release title for tag: $tagVersion"
Write-Host "Editing release for tag: $tagVersion"
# Check if release exists
$releaseExists = gh release view $tagVersion 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Host "Found release with tag $tagVersion, renaming title to 'Beta'..."
Write-Host "Found release with tag $tagVersion"
# Get current release notes
$releaseNotes = gh release view $tagVersion --json body --jq '.body'
# Update the release with new title
# Find the latest non-prerelease tag
Write-Host "Finding latest non-prerelease tag..."
$latestNonPrerelease = gh release list --limit 100 --json tagName,isPrerelease | ConvertFrom-Json | Where-Object { $_.isPrerelease -eq $false -and $_.tagName -ne $tagVersion } | Select-Object -First 1
if ($latestNonPrerelease) {
$latestTag = $latestNonPrerelease.tagName
Write-Host "Latest non-prerelease tag: $latestTag"
# Get commits between latest non-prerelease and current HEAD
Write-Host "Getting commits between $latestTag and HEAD..."
# Use proper git range syntax and handle PowerShell string interpolation
$gitRange = "$latestTag..HEAD"
Write-Host "Git range: $gitRange"
# Get commits using proper git command with datetime
$commits = git log --oneline --pretty=format:"%ad|%s|%h" --date=short $gitRange
# Check if commits exist
if ($commits -and $commits.Trim() -ne "") {
Write-Host "Found commits:"
Write-Host $commits
# Group commits by date
$groupedCommits = @{}
foreach ($line in $commits) {
if ($line.Trim() -ne "") {
$parts = $line.Split('|')
$date = $parts[0]
$message = $parts[1]
$hash = $parts[2]
if (-not $groupedCommits.ContainsKey($date)) {
$groupedCommits[$date] = @()
}
$groupedCommits[$date] += "- $message ($hash)"
}
}
# Build formatted release notes grouped by date
$commitNotes = "## Changes since $latestTag`n`n"
$sortedDates = $groupedCommits.Keys | Sort-Object -Descending
foreach ($date in $sortedDates) {
$commitNotes += "### $date`n"
foreach ($commit in $groupedCommits[$date]) {
$commitNotes += "$commit`n"
}
$commitNotes += "`n"
}
$releaseNotes = $commitNotes
} else {
Write-Host "No commits found between $latestTag and HEAD"
Write-Host "Trying alternative approach..."
# Alternative: get commits since the tag (not range) with datetime
$commits = git log --oneline --pretty=format:"%ad|%s|%h" --date=short $latestTag.. --not $latestTag
if ($commits -and $commits.Trim() -ne "") {
Write-Host "Found commits with alternative method:"
Write-Host $commits
# Group commits by date
$groupedCommits = @{}
foreach ($line in $commits) {
if ($line.Trim() -ne "") {
$parts = $line.Split('|')
$date = $parts[0]
$message = $parts[1]
$hash = $parts[2]
if (-not $groupedCommits.ContainsKey($date)) {
$groupedCommits[$date] = @()
}
$groupedCommits[$date] += "- $message ($hash)"
}
}
# Build formatted release notes grouped by date
$commitNotes = "## Changes since $latestTag`n`n"
$sortedDates = $groupedCommits.Keys | Sort-Object -Descending
foreach ($date in $sortedDates) {
$commitNotes += "### $date`n"
foreach ($commit in $groupedCommits[$date]) {
$commitNotes += "$commit`n"
}
$commitNotes += "`n"
}
$releaseNotes = $commitNotes
} else {
Write-Host "Still no commits found, using basic release notes"
$releaseNotes = "## Beta Release`n`nThis is a beta release."
}
}
} else {
Write-Host "No non-prerelease tags found, using basic release notes"
$releaseNotes = "## Beta Release`n`nThis is a beta release."
}
# Update the release with new title and notes
Write-Host "Updating release with title 'Beta' and new notes..."
gh release edit $tagVersion --title "Beta" --notes "$releaseNotes"
Write-Host "Successfully renamed release title to 'Beta'"
Write-Host "Successfully updated release title to 'Beta' and added commit notes"
} else {
Write-Host "No release found with tag $tagVersion"
}
- name: Set release as prerelease
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release edit $tagVersion --prerelease
Write-Host "Successfully set release as prerelease"
-2
View File
@@ -31,7 +31,6 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux
pnpm run publish:linux
on_retry_command: pnpm cache delete
@@ -44,6 +43,5 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux-arm64
pnpm run publish:linux-arm64
on_retry_command: pnpm cache delete
-1
View File
@@ -31,6 +31,5 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:mac
pnpm run publish:mac
on_retry_command: pnpm cache delete
-1
View File
@@ -31,6 +31,5 @@ jobs:
max_attempts: 3
retry_on: error
command: |
pnpm run package:win
pnpm run publish:win
on_retry_command: pnpm cache delete
+1 -1
View File
@@ -3,7 +3,7 @@ name: Test
on: [push, pull_request]
jobs:
release:
lint:
runs-on: ${{ matrix.os }}
strategy:
+20 -4
View File
@@ -57,6 +57,18 @@ If you're using a device running macOS 12 (Monterey) or higher, [check here](htt
For media keys to work, you will be prompted to allow Feishin to be a Trusted Accessibility Client. After allowing, you will need to restart Feishin for the privacy settings to take effect.
#### Linux Notes
If you're using a Linux device, a `.desktop` file is recommended for easy launching of Feishin.
Download the [latest release (AppImage)](https://github.com/jeffvli/feishin/releases) and [application icon](https://github.com/jeffvli/feishin/blob/development/resources/icon.png?raw=true) to your `~/applications/` folder. This folder may need to be created if it does not already exist.
Rename the icon to `Feishin-linux-x86_64.png`.
Save the [example desktop file](https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/feishin.desktop) as `~/.local/share/applications/feishin.desktop`.
You will now see Feishin show up in your menu. The properties in the example desktop file may need to be modified to match your system.
### Web and Docker
Visit [https://feishin.vercel.app](https://feishin.vercel.app) to use the hosted web version of Feishin. The web client only supports the web player backend.
@@ -157,14 +169,18 @@ This project is built off of [electron-vite](https://github.com/alex8088/electro
- `pnpm run build:remote` - Build the remote app (remote)
- `pnpm run build:web` - Build the standalone web app (renderer)
- `pnpm run package` - Package the project
- `pnpm run package:dev` - Package the project for development
- `pnpm run package:linux` - Package the project for Linux
- `pnpm run package:mac` - Package the project for Mac
- `pnpm run package:win` - Package the project for Windows
- `pnpm run package:dev` - Package the project for development locally
- `pnpm run package:linux` - Package the project for Linux locally
- `pnpm run package:mac` - Package the project for Mac locally
- `pnpm run package:win` - Package the project for Windows locally
- `pnpm run publish:linux` - Publish the project for Linux
- `pnpm run publish:linux:beta` - Publish the project for Linux (beta channel)
- `pnpm run publish:linux-arm64` - Publish the project for Linux ARM64
- `pnpm run publish:linux-arm64:beta` - Publish the project for Linux ARM64 (beta channel)
- `pnpm run publish:mac` - Publish the project for Mac
- `pnpm run publish:mac:beta` - Publish the project for Mac (beta channel)
- `pnpm run publish:win` - Publish the project for Windows
- `pnpm run publish:win:beta` - Publish the project for Windows (beta channel)
- `pnpm run typecheck` - Type check the project
- `pnpm run typecheck:node` - Type check the project with tsconfig.node.json
- `pnpm run typecheck:web` - Type check the project with tsconfig.web.json
+58
View File
@@ -0,0 +1,58 @@
appId: org.jeffvli.feishin
productName: Feishin
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
electronVersion: 35.1.5
directories:
buildResources: assets
files:
- 'out/**/*'
- 'package.json'
extraResources:
- assets/**
asarUnpack:
- resources/**
win:
target:
- zip
- nsis
icon: assets/icons/icon.png
nsis:
allowToChangeInstallationDirectory: true
oneClick: false
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
target:
target: default
arch:
- arm64
- x64
icon: assets/icons/icon.icns
type: distribution
hardenedRuntime: true
entitlements: assets/entitlements.mac.plist
entitlementsInherit: assets/entitlements.mac.plist
gatekeeperAssess: false
notarize: false
dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
linux:
target:
- AppImage
- tar.xz
category: AudioVideo;Audio;Player
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
npmRebuild: false
publish:
provider: github
owner: jeffvli
repo: feishin
channel: beta
releaseType: draft
+7 -1
View File
@@ -16,12 +16,14 @@ win:
- zip
- nsis
icon: assets/icons/icon.png
nsis:
allowToChangeInstallationDirectory: true
oneClick: false
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
target:
target: default
@@ -35,8 +37,10 @@ mac:
entitlementsInherit: assets/entitlements.mac.plist
gatekeeperAssess: false
notarize: false
dmg:
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
linux:
target:
- AppImage
@@ -44,9 +48,11 @@ linux:
category: AudioVideo;Audio;Player
icon: assets/icons/icon.png
artifactName: ${productName}-${os}-${arch}.${ext}
npmRebuild: false
publish:
provider: github
owner: jeffvli
repo: feishin
generateUpdatesFilesForAllChannels: true
channel: latest
releaseType: draft
+9
View File
@@ -0,0 +1,9 @@
[Desktop Entry]
Name=Feishin
Comment=An Electron-based music streaming app
Exec=/home/username/.applications/Feishin-linux-x86_64.AppImage
Icon=/home/username/.applications/Feishin-linux-x86_64.png
Terminal=false
Type=Application
Categories=AudioVideo;Audio;Music;Player;
StartupNotify=true
+9 -5
View File
@@ -1,6 +1,6 @@
{
"name": "feishin",
"version": "0.20.1",
"version": "0.21.0",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
@@ -44,10 +44,14 @@
"package:mac:pr": "pnpm run build && electron-builder --mac --publish never",
"package:win": "pnpm run build && electron-builder --win",
"package:win:pr": "pnpm run build && electron-builder --win --publish never",
"publish:linux": "electron-builder --publish always --linux",
"publish:linux-arm64": "electron-builder --publish always --linux --arm64",
"publish:mac": "electron-builder --publish always --mac",
"publish:win": "electron-builder --publish always --win",
"publish:linux": "pnpm run build && electron-builder --publish always --linux",
"publish:linux-arm64": "pnpm run build && electron-builder --publish always --linux --arm64",
"publish:linux-arm64:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux --arm64",
"publish:linux:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux",
"publish:mac": "pnpm run build && electron-builder --publish always --mac",
"publish:mac:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --mac",
"publish:win": "pnpm run build && electron-builder --publish always --win",
"publish:win:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --win",
"start": "electron-vite preview",
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
+14 -2
View File
@@ -2,11 +2,13 @@ import { PostProcessorModule, StringMap, TOptions } from 'i18next';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ar from './locales/ar.json';
import ca from './locales/ca.json';
import cs from './locales/cs.json';
import de from './locales/de.json';
import en from './locales/en.json';
import es from './locales/es.json';
import eu from './locales/eu.json';
import fa from './locales/fa.json';
import fi from './locales/fi.json';
import fr from './locales/fr.json';
@@ -30,11 +32,13 @@ import zhHans from './locales/zh-Hans.json';
import zhHant from './locales/zh-Hant.json';
const resources = {
ar: { translation: ar },
ca: { translation: ca },
cs: { translation: cs },
de: { translation: de },
en: { translation: en },
es: { translation: es },
eu: { translation: eu },
fa: { translation: fa },
fi: { translation: fi },
fr: { translation: fr },
@@ -63,6 +67,10 @@ export const languages = [
label: 'English',
value: 'en',
},
{
label: 'العربية',
value: 'ar',
},
{
label: 'Català',
value: 'ca',
@@ -71,13 +79,17 @@ export const languages = [
label: 'Čeština',
value: 'cs',
},
{
label: 'Deutsch',
value: 'de',
},
{
label: 'Español',
value: 'es',
},
{
label: 'Deutsch',
value: 'de',
label: 'Basque',
value: 'eu',
},
{
label: 'Français',
+149
View File
@@ -0,0 +1,149 @@
{
"action": {
"addToFavorites": "إضافة الى $t(entity.favorite_other)",
"addToPlaylist": "إضافة الى $t(entity.playlist_one)",
"clearQueue": "مسح قائمة الإنتظار",
"createPlaylist": "إنشاء $t(entity.playlist_one)",
"deletePlaylist": "حذف $t(entity.playlist_one)",
"deselectAll": "إلغاء تحديد الكل",
"editPlaylist": "تعديل $t(entity.playlist_one)",
"goToPage": "اذهب الى صفحة",
"moveToNext": "الذهاب الى التالي",
"moveToBottom": "الذهاب الى الأسفل",
"moveToTop": "الذهاب الى الأعلى",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "حذف من $t(entity.favorite_other)",
"removeFromPlaylist": "حذف من $t(entity.playlist_one)",
"removeFromQueue": "حذف من قائمة الإنتظار",
"setRating": "تحديد التقييم",
"toggleSmartPlaylistEditor": "تشغيل / إطفاء وضع التعديل لـ $t(entity.smartPlaylist)",
"viewPlaylists": "إظهار $t(entity.playlist_other)",
"openIn": {
"lastfm": "فتح في Last.fm",
"musicbrainz": "فتح في MusicBrainz"
}
},
"common": {
"action_zero": "عملية",
"action_one": "عملية",
"action_two": "عمليتين",
"action_few": "عمليات",
"action_many": "عمليات",
"action_other": "عمليات",
"add": "إضافة",
"additionalParticipants": "مشاركين إضافيين",
"newVersion": "تم تثبيت تحديث جديد {{version}}",
"viewReleaseNotes": "عرض معلومات الإصدار",
"albumGain": "مستوى صوت الألبوم",
"albumPeak": "اعلى مستوى للألبوم",
"areYouSure": "هل أنت متأكد؟",
"ascending": "تصاعدي",
"backward": "خلف",
"biography": "سيرة",
"bitDepth": "عمق البت",
"bitrate": "معدل البت (البت ريت)",
"bpm": "نبضة في الدقيقة",
"cancel": "إلغاء",
"center": "منتصف",
"channel_zero": "قناة",
"channel_one": "قناة",
"channel_two": "قناتين",
"channel_few": "قنوات",
"channel_many": "قنوات",
"channel_other": "قنوات",
"clear": "مسح",
"close": "إغلاق",
"codec": "كوديك",
"collapse": "طي",
"comingSoon": "قريبًا…",
"configure": "تعديل",
"confirm": "تأكيد",
"create": "إنشاء",
"currentSong": "$t(entity.track_one) الحالي",
"decrease": "تنقيص",
"delete": "حذف",
"descending": "تنازلي",
"description": "وصف",
"disable": "تعطيل",
"disc": "قرص",
"dismiss": "إخفاء",
"duration": "مدة",
"edit": "تعديل",
"enable": "تفعيل",
"expand": "توسيع",
"favorite": "مفضلة",
"filter_zero": "فلتر",
"filter_one": "فلتر",
"filter_two": "فلاتر",
"filter_few": "فلاتر",
"filter_many": "فلاتر",
"filter_other": "فلاتر",
"filters": "فلاتر",
"forceRestartRequired": "اعد التشغيل لتطبيق التعديلات... اغلق التنبية لإعادة التشغيل",
"forward": "امام",
"gap": "فجوة",
"home": "الرئيسية",
"increase": "زيادة",
"left": "يسار",
"limit": "حد",
"manage": "إدارة",
"maximize": "تكبير",
"menu": "القائمة",
"minimize": "تصغير",
"modified": "تم تعديله",
"mbid": "معرف MusicBrainz",
"name": "إسم",
"no": "لا",
"none": "لا شي",
"noResultsFromQuery": "لا توجد نتائج",
"note": "ملاحظة",
"ok": "نعم",
"owner": "المالك",
"path": "المسار",
"playerMustBePaused": "يجب إيقاف المشغل",
"preview": "معاينة",
"previousSong": "$t(entity.track_one) السابق",
"quit": "خروج",
"random": "عشوائي",
"rating": "التقييم",
"refresh": "تحديث",
"reload": "تحديث",
"reset": "إعادة تعيين",
"resetToDefault": "إعادة تعيين الى الافتراضي",
"restartRequired": "يجب إعادة التشغيل",
"right": "يمين",
"sampleRate": "معدل العينة (sample rate)",
"save": "حفظ",
"saveAndReplace": "حفظ واستبدال",
"saveAs": "حفظ بإسم",
"search": "بحث",
"setting": "إعداد",
"share": "نشر",
"size": "حجم",
"sortOrder": "الترتيب",
"tags": "العلامات",
"title": "العنوان",
"trackNumber": "رقم المسار",
"trackGain": "مستوى صوت المسار",
"trackPeak": "اعلى مستوى للمسار",
"translation": "الترجمة",
"unknown": "غير معروف",
"version": "الإصدار",
"year": "السنة",
"yes": "نعم"
},
"entity": {
"album_zero": "الالبوم",
"album_one": "الالبوم",
"album_two": "الالبومين",
"album_few": "الالبومات",
"album_many": "الالبومات",
"album_other": "الالبومات",
"albumArtist_zero": "فنان الالبوم",
"albumArtist_one": "فنان الالبوم",
"albumArtist_two": "فنان الالبومين",
"albumArtist_few": "فنان الالبومات",
"albumArtist_many": "فنان الالبومات",
"albumArtist_other": "فنان الالبومات"
}
}
+11 -4
View File
@@ -22,7 +22,7 @@
"appearsOn": "apareix a",
"recentReleases": "Llançaments recents",
"viewDiscography": "Mosta la discografia",
"topSongs": "millos cançons",
"topSongs": "millors cançons",
"topSongsFrom": "les millors cançons de {{title}}",
"viewAll": "mostra-ho tot"
},
@@ -89,7 +89,8 @@
"explore": "explora la teva biblioteca",
"newlyAdded": "afegits recentment",
"mostPlayed": "els més reproduïts",
"recentlyPlayed": "reproduït recentment"
"recentlyPlayed": "reproduït recentment",
"recentlyReleased": "estrenat fa poc"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
@@ -349,7 +350,9 @@
"input_savePassword": "desa la contrasenya",
"input_url": "url",
"success": "servidor afegit correctament",
"title": "afegeix un servidor"
"title": "afegeix un servidor",
"input_preferInstantMix": "prefereix el mix instantani",
"input_preferInstantMixDescription": "utilitza només el mix instantani per obtenir cançons similars. útil si teniu complements que modifiquin aquest comportament"
},
"shareItem": {
"description": "descripció",
@@ -646,7 +649,11 @@
"discordDisplayType_artistname": "nom de l'artista",
"hotkey_navigateHome": "ves a l'inici",
"preventSleepOnPlayback": "evitar entrar en repòs durant la reproducció",
"preventSleepOnPlayback_description": "evita que la pantalla s'adormi mentre la música es reprodueix"
"preventSleepOnPlayback_description": "evita que la pantalla s'adormi mentre la música es reprodueix",
"discordLinkType": "enllaços d'estat de {{discord}}",
"discordLinkType_description": "afegeix enllaços externs a {{lastfm}} o {{musicbrainz}} als camps de cançó i artista a l'estat d'activitat de {{discord}}. {{musicbrainz}} és el més precís, però requereix etiquetes i no proporciona enllaços d'artista, mentre que {{lastfm}} hauria de propocionar un enllaç sempre. no fa sol·licituds de xarxa addicionals",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} amb {{lastfm}} com a alternativa"
},
"table": {
"column": {
+14 -3
View File
@@ -280,7 +280,15 @@
"discordDisplayType_artistname": "jména umělců",
"hotkey_navigateHome": "přejít domů",
"preventSleepOnPlayback": "zabránit uspání při přehrávání",
"preventSleepOnPlayback_description": "zabránit uspání displeje během přehrávání hudby"
"preventSleepOnPlayback_description": "zabránit uspání displeje během přehrávání hudby",
"discordLinkType": "odkazy ve stavu na službě {{discord}}",
"discordLinkType_description": "přidá externí odkazy na {{lastfm}} nebo {{musicbrainz}} do polí skladby a umělce ve stavu na službě {{discord}}. {{musicbrainz}} je nejpřesnější, ale vyžaduje značky a neposkytuje odkazy na umělce, zatímco {{lastfm}} by mělo vždy poskytnout odkaz. neprovádí žádné další síťové požadavky",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} se zálohou na {{lastfm}}",
"artistBackground": "obrázek umělce na pozadí",
"artistBackground_description": "přidá obrázek na pozadí u stránek umělců",
"artistBackgroundBlur": "velikost rozostření obrázku umělce na pozadí",
"artistBackgroundBlur_description": "upraví velikost rozostření použitého na obrázek umělce na pozadí"
},
"action": {
"editPlaylist": "upravit $t(entity.playlist_one)",
@@ -637,7 +645,8 @@
"newlyAdded": "nově přidáno",
"title": "$t(common.home)",
"explore": "procházet z vaší knihovny",
"recentlyPlayed": "nedávno přehráno"
"recentlyPlayed": "nedávno přehráno",
"recentlyReleased": "nedávno vydáno"
},
"albumDetail": {
"moreFromArtist": "více od tohoto umělce",
@@ -733,7 +742,9 @@
"input_savePassword": "uložit heslo",
"ignoreSsl": "ignorovat SSL $t(common.restartRequired)",
"ignoreCors": "ignorovat CORS $t(common.restartRequired)",
"error_savePassword": "při ukládání hesla se vyskytla chyba"
"error_savePassword": "při ukládání hesla se vyskytla chyba",
"input_preferInstantMix": "preferovat instantní mix",
"input_preferInstantMixDescription": "pro získání podobných skladeb použít pouze instantní mix. užitečné, pokud máte doplňky, které upravují toto chování"
},
"addToPlaylist": {
"success": "přidáno $t(entity.trackWithCount, {\"count\": {{message}} }) do $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
+21 -7
View File
@@ -234,7 +234,8 @@
},
"editPlaylist": {
"title": "Bearbeite $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) erfolgreich aktualisiert"
"success": "$t(entity.playlist_one) erfolgreich aktualisiert",
"publicJellyfinNote": "Jellyfin legt aus irgendwelchen Gründen nicht offen ob eine Playlist öffentlich ist oder nicht. Wenn du möchtest, dass sie öffentlich bleibt, wähle bitte diese Option aus"
},
"lyricSearch": {
"title": "Songtext Suche",
@@ -246,7 +247,13 @@
"setExpiration": "Ablaufdatum setzen",
"expireInvalid": "Ablaufdatum muss in der Zukunft liegen",
"allowDownloading": "Herunterladen zulassen",
"success": "Link in die Zwischenablage kopiert (oder hier klicken um zu öffnen)"
"success": "Link in die Zwischenablage kopiert (oder hier klicken um zu öffnen)",
"createFailed": "Fehler beim Teilen (Ist Teilen aktiviert?)"
},
"privateMode": {
"enabled": "Privatmodus aktiviert, Wiedergabe-Status wird externen Quellen nicht preisgegeben",
"disabled": "Privatmodus deaktiviert, Wiedergabe-Status wird externen Quellen preisgegeben",
"title": "Privatmodus"
}
},
"entity": {
@@ -386,14 +393,17 @@
"goBack": "Gehe zurück",
"goForward": "Gehe vorwärts",
"settings": "$t(common.setting_other)",
"quit": "$t(common.quit)"
"quit": "$t(common.quit)",
"privateModeOff": "Privatmodus deaktivieren",
"privateModeOn": "Privatmodus aktivieren"
},
"home": {
"mostPlayed": "Meistgespielt",
"newlyAdded": "Neu hinzugefügte Veröffentlichungen",
"explore": "Entdecke deine Bibliothek",
"recentlyPlayed": "Kürzlich gespielt",
"title": "$t(common.home)"
"title": "$t(common.home)",
"recentlyReleased": "kürzlich veröffentlicht"
},
"albumDetail": {
"moreFromArtist": "Mehr von diesem $t(entity.artist_one)",
@@ -428,7 +438,9 @@
"playShuffled": "$t(player.shuffle)",
"download": "Download",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"shareItem": "teilen",
"showDetails": "Informationen"
},
"sidebar": {
"nowPlaying": "läuft gerade",
@@ -481,7 +493,8 @@
"viewAllTracks": "Alle $t(entity.track_other) ansehen",
"topSongsFrom": "Toplieder von {{title}}",
"viewAll": "Alles ansehen",
"topSongs": "Toplieder"
"topSongs": "Toplieder",
"relatedArtists": "ähnliche $t(entity.artist_other)"
},
"manageServers": {
"title": "Servers verwalten",
@@ -709,6 +722,7 @@
"clearCacheSuccess": "Cache erfolgreich geleert",
"contextMenu": "Kontextmenü-Einstellungen (Rechtsklick)",
"customCssEnable_description": "ermöglicht das Schreiben benutzerdefinierten CSS.",
"doubleClickBehavior": "bei Doppelklick alle gesuchten Tracks zur Warteschlange hinzufügen"
"doubleClickBehavior": "bei Doppelklick alle gesuchten Tracks zur Warteschlange hinzufügen",
"artistBackground": "Künstler Hintergrundbild"
}
}
+3 -1
View File
@@ -530,7 +530,7 @@
"customFontPath": "custom font path",
"customFontPath_description": "sets the path to the custom font to use for the application",
"disableAutomaticUpdates": "disable automatic updates",
"releaseChannel_optionLatest": "latest",
"releaseChannel_optionLatest": "stable",
"releaseChannel_optionBeta": "beta",
"releaseChannel": "release channel",
"releaseChannel_description": "choose between stable releases or beta releases for automatic updates",
@@ -727,6 +727,8 @@
"transcodeBitrate_description": "selects the bitrate to transcode. 0 means let the server pick",
"transcodeFormat": "format to transcode",
"transcodeFormat_description": "selects the format to transcode. leave empty to let the server decide",
"mediaSession": "enable media session",
"mediaSession_description": "Enables Windows Media Session integration, displaying media controls and metadata in the system volume overlay and lock screen (Windows only)",
"translationApiProvider": "translation api provider",
"translationApiProvider_description": "api provider for translation",
"translationApiKey": "translation api key",
+23 -8
View File
@@ -259,7 +259,7 @@
"lastfmApiKey_description": "la clave API para {{lastfm}}. Requerida para la portada",
"lastfmApiKey": "Clave API para {{lastfm}}",
"discordServeImage": "Servir imágenes de {{discord}} desde el servidor",
"discordServeImage_description": "Comparte el arte de la portada para el estado de actividad de {{discord}} desde el propio servidor, solo disponible para Jellyfin y Navidrome",
"discordServeImage_description": "Comparte el arte de la portada para el estado de actividad de {{discord}} desde el propio servidor, solo disponible para Jellyfin y Navidrome. {{discord}} usa un bot para obtener las imágenes, por lo que tu servidor debe ser alcanzable desde el Internet público.",
"lastfm": "Mostrar enlaces de last.fm",
"lastfm_description": "Muestra enlaces a last.fm en las páginas de artistas/álbumes",
"musicbrainz": "Mostrar enlaces de MusicBrainz",
@@ -280,7 +280,19 @@
"discordDisplayType": "Tipo de pantalla de actividad de {{discord}}",
"hotkey_navigateHome": "Navegar a inicio",
"preventSleepOnPlayback": "Evitar entrar en reposo durante la reproducción",
"preventSleepOnPlayback_description": "Evita que la pantalla entre en reposo mientras se está reproduciendo música"
"preventSleepOnPlayback_description": "Evita que la pantalla entre en reposo mientras se está reproduciendo música",
"discordLinkType": "Enlaces de estado de {{discord}}",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} con {{lastfm}} como alternativa",
"discordLinkType_description": "Añade enlaces externos a {{lastfm}} o {{musicbrainz}} a la canción y campos del artista en el estado de actividad de {{discord}} . {{musicbrainz}} es el más preciso pero requiere etiquetas y no proporciona enlaces del artista mientras que {{lastfm}} debería siempre proporcionar un enlace. No realiza peticiones de red adicionales",
"artistBackground": "imagen de fondo del artista",
"artistBackgroundBlur": "tamaño de desenfoque de imagen de fondo del artista",
"artistBackgroundBlur_description": "ajusta la cantidad de desenfoque aplicado a la imagen de fondo del artista",
"releaseChannel_optionLatest": "Estable",
"releaseChannel_optionBeta": "Beta",
"releaseChannel": "Canal de lanzamiento",
"releaseChannel_description": "Elige entre lanzamientos estables o beta para las actualizaciones automáticas",
"artistBackground_description": "Añade una imagen de fondo para las páginas de artista que contienen el arte del artista"
},
"action": {
"editPlaylist": "editar $t(entity.playlist_one)",
@@ -313,7 +325,7 @@
"bpm": "lpm",
"refresh": "actualizar",
"unknown": "desconocido",
"areYouSure": "estás seguro?",
"areYouSure": "seguro?",
"edit": "editar",
"favorite": "favorito",
"left": "izquierda",
@@ -478,7 +490,7 @@
},
"page": {
"sidebar": {
"nowPlaying": "en reproducción",
"nowPlaying": "reproduciendo",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"tracks": "$t(entity.track_other)",
@@ -503,8 +515,8 @@
"quit": "$t(common.quit)",
"goBack": "retroceder",
"goForward": "avanzar",
"privateModeOff": "Apagar modo privado",
"privateModeOn": "Encender modo privado"
"privateModeOff": "Desactivar modo privado",
"privateModeOn": "Activar modo privado"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -537,7 +549,8 @@
"newlyAdded": "nuevos lanzamientos añadidos",
"title": "$t(common.home)",
"explore": "explora desde tu biblioteca",
"recentlyPlayed": "reproducidos recientemente"
"recentlyPlayed": "reproducidos recientemente",
"recentlyReleased": "Lanzado recientemente"
},
"fullscreenPlayer": {
"upNext": "siguiente",
@@ -656,7 +669,9 @@
"input_savePassword": "guardar contraseña",
"ignoreSsl": "ignorar ssl ($t(common.restartRequired))",
"ignoreCors": "ignorar cors ($t(common.restartRequired))",
"error_savePassword": "un error ocurrió cuando se intentó guardar la contraseña"
"error_savePassword": "un error ocurrió cuando se intentó guardar la contraseña",
"input_preferInstantMix": "Preferir mix instantáneo",
"input_preferInstantMixDescription": "Usa solo el mix instantáneo para obtener canciones similares. Útil si tienes complementos que modifican este comportamiento"
},
"addToPlaylist": {
"success": "añadido $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
+633
View File
@@ -0,0 +1,633 @@
{
"action": {
"deselectAll": "deshautatu dena",
"editPlaylist": "editatu $t(entity.playlist_one)",
"goToPage": "joan orrira",
"moveToNext": "mugitu hurrengora",
"moveToBottom": "mugitu behera",
"moveToTop": "mugitu gora",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "kendu $t(entity.favorite_other)-(e)tik",
"removeFromPlaylist": "kendu $t(entity.playlist_one)-(e)tik",
"removeFromQueue": "kendu ilaratik",
"setRating": "ezarri balorazioa",
"toggleSmartPlaylistEditor": "txandakatu $t(entity.smartPlaylist) editorea",
"viewPlaylists": "ikusi $t(entity.playlist_other)",
"openIn": {
"lastfm": "Ireki Last.fm-n",
"musicbrainz": "Ireki MusicBrainz-en"
},
"clearQueue": "garbitu ilara",
"createPlaylist": "sortu $t(entity.playlist_one)",
"deletePlaylist": "ezabatu $t(entity.playlist_one)",
"addToFavorites": "gehitu $t(entity.favorite_other)-(e)ra",
"addToPlaylist": "gehitu $t(entity.playlist_one)-(e)ra"
},
"common": {
"add": "gehitu",
"additionalParticipants": "partaide gehigarriak",
"newVersion": "bertsio berri bat instalatu da ({{version}})",
"viewReleaseNotes": "ikusi argitalpen oharrak",
"areYouSure": "ziur zaude?",
"ascending": "goranzkoa",
"backward": "atzeraka",
"biography": "biografia",
"close": "itxi",
"codec": "kodeka",
"collapse": "tolestu",
"configure": "konfiguratu",
"confirm": "berretsi",
"create": "sortu",
"currentSong": "uneko $t(entity.track_one)",
"decrease": "gutxitu",
"delete": "ezabatu",
"descending": "beheranzkoa",
"description": "deskripzioa",
"disable": "desgaitu",
"disc": "diskoa",
"dismiss": "baztertu",
"duration": "iraupena",
"edit": "editatu",
"enable": "gaitu",
"expand": "zabaldu",
"favorite": "gogokoa",
"filter_one": "iragazkia",
"filter_other": "iragazkiak",
"filters": "iragazkiak",
"forceRestartRequired": "berreabiarazi aldaketak aplikatzeko... itxi notifikazioa berreabiarazteko",
"setting": "ezarpena",
"share": "partekatu",
"action_one": "ekintza",
"action_other": "ekintzak",
"unknown": "ezezaguna",
"version": "bertsioa",
"year": "urtea",
"yes": "bai",
"bitrate": "bit-emaria",
"bpm": "bpm",
"cancel": "utzi",
"center": "lerrokatu",
"channel_one": "kanala",
"channel_other": "kanalak",
"clear": "garbitu",
"forward": "aurrerantz",
"home": "etxea",
"increase": "handitu",
"left": "ezkerra",
"limit": "mugatu",
"manage": "kudeatu",
"maximize": "maximizatu",
"menu": "menua",
"minimize": "minimizatu",
"modified": "aldatuta",
"mbid": "MusicBrainz IDa",
"name": "izena",
"no": "ez",
"none": "bat ere ez",
"noResultsFromQuery": "kontsultak ez du emaitzik itzuli",
"note": "oharra",
"ok": "ados",
"owner": "jabea",
"path": "bidea",
"playerMustBePaused": "erreproduzitzailea pausatuta egon behar da",
"preview": "aurrebista",
"previousSong": "aurreko $t(entity.track_one)",
"quit": "irten",
"random": "ausazkoa",
"rating": "balorazioa",
"refresh": "freskatu",
"reload": "birkargatu",
"reset": "berrerazi",
"right": "eskuina",
"save": "gorde",
"search": "bilatu",
"size": "tamaina",
"sortOrder": "ordena",
"tags": "etiketak",
"title": "tituloa",
"trackNumber": "pista",
"translation": "itzulpena",
"albumGain": "album irabazpena",
"bitDepth": "bit-sakonera",
"resetToDefault": "lehenetsitako egoerara berrezarri",
"restartRequired": "berrabiarazi behar da",
"sampleRate": "laginketa-tasa",
"saveAndReplace": "gorde eta ordezkatu",
"saveAs": "gorde honela",
"trackGain": "pista irabazpena",
"comingSoon": "laster…",
"trackPeak": "pistaren gailurra"
},
"player": {
"repeat": "errepikatu",
"play": "erreproduzitu",
"previous": "aurrekoa",
"pause": "pausatu",
"favorite": "gogokoa",
"mute": "isilarazi",
"muted": "isilduta",
"next": "hurrengoa",
"skip": "saltatu",
"stop": "gelditu",
"unfavorite": "kendu gogokoetatik",
"addLast": "gehitu azkena",
"addNext": "gehitu hurrengoa",
"playbackFetchInProgress": "abestiak kargatzen…",
"playbackSpeed": "erreprodukzio-abiadura",
"playRandom": "erreproduzitu auzaz",
"playbackFetchNoResults": "ez da abestirik aurkitu",
"playSimilarSongs": "erreproduzitu antzeko abestiak",
"queue_clear": "garbitu ilara",
"queue_moveToBottom": "gora eraman hautatutakoak",
"queue_moveToTop": "behera eraman hautatutakoak",
"queue_remove": "kendu hautatutakoak",
"repeat_all": "errepikatu dena",
"repeat_off": "errepikapena desgaituta",
"shuffle": "erreproduzitu ausaz",
"shuffle_off": "auza desgaituta",
"skip_back": "saltatu atzeraka",
"skip_forward": "saltatu aurreraka",
"toggleFullscreenPlayer": "txandakatu pantaila osoko erreproduzitzailea",
"viewQueue": "ikusi ilara"
},
"table": {
"config": {
"view": {
"table": "taula",
"list": "zerrenda",
"card": "txartela",
"grid": "sareta",
"poster": "kartela"
},
"general": {
"gap": "$t(common.gap)",
"size": "$t(common.size)"
},
"label": {
"actions": "$t(common.action_other)",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"rating": "$t(common.rating)",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"year": "$t(common.year)"
}
},
"column": {
"album": "albuma",
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"bitrate": "bit-emaria",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"discNumber": "diskoa",
"favorite": "gogokoa",
"genre": "$t(entity.genre_one)",
"path": "bidea",
"rating": "balorazioa",
"releaseYear": "urtea",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "tituloa",
"trackNumber": "pista",
"bpm": "bpm",
"comment": "iruzkina",
"playCount": "erreprodukzioak"
}
},
"entity": {
"album_one": "albuma",
"album_other": "albumak",
"albumArtist_one": "albumaren artista",
"albumArtist_other": "albumaren artistak",
"albumArtistCount_one": "album artista {{count}}",
"albumArtistCount_other": "{{count}} album artista",
"albumWithCount_one": "album {{count}}",
"albumWithCount_other": "{{count}} album",
"artist_one": "artista",
"artist_other": "artistak",
"artistWithCount_one": "artista {{count}}",
"artistWithCount_other": "{{count}} artista",
"favorite_one": "gogokoa",
"favorite_other": "gogokoak",
"folder_one": "karpeta",
"folder_other": "karpetak",
"folderWithCount_one": "karpeta {{count}}",
"folderWithCount_other": "{{count}} karpeta",
"genre_one": "generoa",
"genre_other": "generoak",
"genreWithCount_one": "genero {{count}}generoa",
"genreWithCount_other": "{{count}} genero",
"playlist_one": "erreprodukzio-zerrenda",
"playlist_other": "erreprodukzio-zerrendak",
"play_one": "erreprodukzio {{count}}",
"play_other": "{{count}} erreprodukzio",
"playlistWithCount_one": "erreprodukzio-zerrenda {{count}}",
"playlistWithCount_other": "{{count}} erreprodukzio-zerrenda",
"smartPlaylist": "$t(entity.playlist_one) adimentsua",
"track_one": "pista",
"track_other": "pistak",
"song_one": "abestia",
"song_other": "abestiak",
"trackWithCount_one": "pista {{count}}",
"trackWithCount_other": "{{count}} pista"
},
"error": {
"apiRouteError": "ezin izan da eskaera bideratu",
"audioDeviceFetchError": "errore bat gertatu da audio gailuak lortzen saiatzean",
"authenticationFailed": "autentifikazioa huts egin du",
"badValue": "\"{{value}}\" aukera baliogabea. Balio hau ez da gehiago existitzen.",
"credentialsRequired": "kredentzialak beharrezkoak dira",
"endpointNotImplementedError": "{{endpoint}} amaiera-puntua ez dago {{serverType}}-(e)rako inplementatuta",
"genericError": "errore bat gertatu da",
"invalidServer": "zerbitzari baliogabea",
"localFontAccessDenied": "tokiko letra-tipoetarako sarbidea ukatuta",
"mpvRequired": "MPV beharrezkoa da",
"networkError": "sareko errore bat gertatu da",
"openError": "ezin izan da fitxategia ireki",
"playbackError": "errore bat gertatu da multimedia erreproduzitzen saiatzean",
"remoteDisableError": "errore bat gertatu da urruneko zerbitzaria $t(common.disable) desgaitzen saiatzean",
"remoteEnableError": "errore bat gertatu da urruneko zerbitzaria $t(common.enable) gaitzen saiatzean",
"remotePortError": "errore bat gertatu da urruneko zerbitzariaren ataka ezartzen saiatzean",
"remotePortWarning": "Berrabiarazi zerbitzaria portu berria aplikatzeko",
"serverNotSelectedError": "ez da zerbitzaririk hautatu",
"serverRequired": "zerbitzaria beharrezkoa da",
"sessionExpiredError": "zure saioa iraungi da",
"badAlbum": "Orrialde hau ikusten ari zara abesti hau album batekoa ez delako. Ziurrenik arazo hau ikusten ari zara zure musika karpetaren goiko mailan abesti bat baduzu. Jellyfinek abestiak karpeta batean badaude taldekatzen ditu bakarrik.",
"loginRateError": "Saioa hasteko saiakera gehiegi egin dira, saiatu berriro segundo batzuk barru",
"notificationDenied": "Jakinarazpenetarako baimenak ukatu dira. Ezarpen honek ez du eraginik.",
"systemFontError": "errore bat gertatu da sistemaren letra-tipoak lortzen saiatzean"
},
"filter": {
"disc": "diskoa",
"duration": "iraupena",
"id": "id-a",
"isPublic": "publikoa da",
"name": "izena",
"note": "oharra",
"owner": "$t(common.owner)",
"path": "bidea",
"random": "ausazkoa",
"rating": "balorazioa",
"trackNumber": "pista",
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"bitrate": "bit-emaria",
"bpm": "bpm-ak",
"channels": "$t(common.channel_other)",
"comment": "iruzkina",
"favorited": "gogoko gisa markatua",
"genre": "$t(entity.genre_one)",
"search": "bilatu",
"title": "tituloa",
"albumCount": "$t(entity.album_other) kopurua",
"communityRating": "komunitatearen balorazioa",
"criticRating": "kritikarien balorazioa",
"dateAdded": "gehitutako data",
"isCompilation": "konpilazioa da",
"isFavorited": "gogokoetan dago",
"isRated": "baloratua dago",
"isRecentlyPlayed": "duela gutxi entzundakoa",
"lastPlayed": "azken aldiz entzundakoa",
"mostPlayed": "gehien entzundakoa",
"playCount": "erreprodukzio kopurua",
"recentlyAdded": "duela gutxi gehitutakoa",
"recentlyPlayed": "duela gutxi entzundakoa",
"recentlyUpdated": "duela gutxi eguneratua",
"songCount": "abesti kopurua",
"releaseDate": "argitalpen data",
"releaseYear": "argitalpen urtea",
"toYear": "urtera arte",
"fromYear": "urtetik aurrera"
},
"setting": {
"hotkey_playbackPause": "pausatu",
"hotkey_playbackPlay": "erreproduzitu",
"language": "hizkuntza",
"playbackStyle_optionNormal": "normala",
"playButtonBehavior_optionPlay": "$t(player.play)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"font": "letra-tipoa",
"hotkey_playbackStop": "gelditu",
"buttonSize_description": "erreproduzitzailearen barrako botoien tamaina",
"clearCache": "garbitu nabigatzailearen katxea",
"clearQueryCache": "garbitu feishinen katxea",
"clearCacheSuccess": "katxea behar bezala garbitu da",
"contextMenu": "testuinguru-menuaren konfigurazioa (klik eskuineko botoiarekin)",
"customCssEnable": "gaitu css pertsonalizatua",
"customCssEnable_description": "css pertsonalizatua idazteko aukera eman.",
"customCss": "css pertsonalizatua",
"customFontPath": "letra-tipo pertsonalizatuaren bidea",
"customFontPath_description": "aplikazioan erabiliko den letra-tipo pertsonalizatuaren bidea ezartzen du",
"disableAutomaticUpdates": "desgaitu eguneratze automatikoak",
"discordApplicationId": "{{discord}} aplikazioaren IDa",
"followLyric": "jarraitu uneko letra",
"font_description": "aplikazioan erabiliko den letra-tipoa ezartzen du",
"fontType": "letra-tipo mota",
"fontType_optionCustom": "letra-tipo pertsonalizatua",
"fontType_optionSystem": "sistemaren letra-tipoa",
"gaplessAudio_optionWeak": "ahula (gomendatua)",
"homeConfiguration": "hasierako orriaren konfigurazioa",
"hotkey_favoriteCurrentSong": "$t(common.currentSong) gogokoa",
"hotkey_favoritePreviousSong": "$t(common.previousSong) gogokoa",
"hotkey_navigateHome": "nabigatu etxera",
"hotkey_playbackNext": "hurrengo pista",
"hotkey_playbackPlayPause": "erreproduzitu / pausatu",
"hotkey_playbackPrevious": "aurreko pista",
"hotkey_skipBackward": "saltatu atzeraka",
"hotkey_skipForward": "saltatu aurrerantz",
"hotkey_toggleCurrentSongFavorite": "txandakatu $t(common.currentSong) gogokoa",
"hotkey_toggleFullScreenPlayer": "txandakatu pantaila osoko erreproduzitzailea",
"hotkey_togglePreviousSongFavorite": "txandakatu $t(common.previousSong) gogokoa",
"hotkey_toggleQueue": "txandakatu ilara",
"hotkey_toggleRepeat": "txandakatu errepikapena",
"hotkey_toggleShuffle": "txandakatu auzazkoa",
"hotkey_unfavoriteCurrentSong": "kendu $t(common.currentSong) gogokoetatik",
"hotkey_unfavoritePreviousSong": "kendu $t(common.previousSong) gogokoetatik",
"hotkey_volumeDown": "bolumena jaitsi",
"hotkey_volumeMute": "isilarazi bolumena",
"hotkey_volumeUp": "bolumena igo",
"hotkey_zoomIn": "hurbildu",
"hotkey_zoomOut": "txikiagotu",
"language_description": "aplikazioaren hizkuntza ezartzen du ($t(common.restartRequired))",
"lastfm": "erakutsi last.fm estekak",
"lastfm_description": "erakutsi last.fm-rako estekak artista/album orrialdeetan",
"lastfmApiKey": "{{lastfm}} API gakoa",
"lastfmApiKey_description": "{{lastfm}}-ren API gakoa. Azaleko arterako beharrezkoa.",
"lyricFetch": "eskuratu letrak internetetik",
"lyricFetch_description": "Eskuratu letrak hainbat internet iturrietatik",
"notify": "gaitu abesti japinarazpenak",
"notify_description": "erakutsi jakinarazpenak uneko abestia aldatzean",
"audioExclusiveMode_description": "gaitu irteera esklusiboko modua. Modu honetan, sistema normalean blokeatuta egoten da, eta mpv-k bakarrik atera ahal izango du audioa",
"audioDevice_description": "aukeratu erreproduzitzeko erabiliko den audio gailua (web erreproduzitzailea bakarrik)",
"audioPlayer": "audio erreproduzitzailea",
"audioPlayer_description": "aukeratu erabiliko den audio erreproduzitzailea",
"buttonSize": "erreproduzitzaile barrako botoien tamaina",
"crossfadeDuration": "crossfade iraupena",
"crossfadeDuration_description": "crossfade efektuaren iraupena ezartzen du",
"crossfadeStyle": "crossfade estiloa",
"crossfadeStyle_description": "aukeratu audio erreproduzitzailearentzat erabiliko den crossfade estiloa",
"disableLibraryUpdateOnStartup": "desgaitu bertsio berrien egiaztapena abiaraztean",
"discordApplicationId_description": "{{discord}} jarduera-egoeraren aplikazioaren IDa (lehenetsia {{defaultId}} da)",
"discordPausedStatus": "erakutsi jarduera-egoera pausatuta dagoenean",
"discordPausedStatus_description": "gaituta dagoenean, egoera agertuko da erreproduzitzailea pausatuta dagoenean",
"discordIdleStatus": "erakutsi inaktibo jarduera-egoeran",
"discordIdleStatus_description": "gaituta dagoenean, eguneratu egoera erreproduzitzailea inaktibo dagoen bitartean",
"discordListening_description": "erakutsi egoera entzuten bezala erreproduzitzen ordez",
"discordListening": "erakutsi egoera entzuten bezala",
"discordRichPresence": "{{discord}} jarduera-egoera",
"discordRichPresence_description": "gaitu erreprodukzioa egoera {{discord}}-en jarduera-egoeran. Irudi gakoak hauek dira: {{icon}}, {{playing}}, eta {{paused}}",
"discordServeImage": "zerbitzatu {{discord}} irudiak zerbitzaritik",
"discordServeImage_description": "partekatu {{discord}} jarduera-egoerarentzako azala artea zerbitzaritik bertatik, Jellyfin eta Navidrome-rentzat bakarrik eskuragarri",
"discordUpdateInterval": "{{discord}} jarduera-egoera eguneraketa tartea",
"discordLinkType_none": "$t(common.none)",
"albumBackground": "albumaren atzeko planoaren irudia",
"albumBackground_description": "albumaren azala artea duten album orrietarako atzeko plano irudi bat gehitzen du",
"albumBackgroundBlur": "albumaren atzeko planoaren irudiaren lausotze tamaina",
"discordLinkType_description": "{{lastfm}} edo {{musicbrainz}}-(e)rako kanpoko estekak gehitzen ditu abesti eta artista eremuetan {{discord}} jarduera-egoeran. {{musicbrainz}} da zehatzena, baina etiketak behar ditu eta ez ditu artistaren estekak ematen, {{lastfm}}-k beti esteka bat eman beharko lukeen bitartean. ez du sareko eskaera gehigarririk egiten",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"scrobble": "scrobble",
"sidePlayQueueStyle_optionAttached": "erantsita",
"sidePlayQueueStyle_optionDetached": "bereizita",
"theme": "gaia",
"audioDevice": "audio gailua",
"discordDisplayType_songname": "abesti izena",
"discordDisplayType_artistname": "artista izena(k)",
"fontType_optionBuiltIn": "barneko letra-tipoa",
"hotkey_globalSearch": "bilaketa globala"
},
"form": {
"addServer": {
"input_password": "pasahitza",
"input_url": "url-a",
"input_username": "erabiltzaile-izena",
"error_savePassword": "errore bat gertatu da pasahitza gordetzen saiatzean",
"input_name": "zerbitzari izena",
"input_savePassword": "pasahitza gorde",
"title": "zerbitzaria gehitu",
"ignoreCors": "alde batera utzi cors $t(common.restartRequired)",
"ignoreSsl": "alde batera utzi ssl $t(common.restartRequired)",
"input_legacyAuthentication": "gaitu zaharkitutako autentifikazioa"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) gehitu da $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })-ra",
"input_skipDuplicates": "saltatu bikoiztuak",
"title": "gehitu $t(entity.playlist_one)-(a)ri"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "publikoa",
"title": "$t(entity.playlist_one) sortu",
"success": "$t(entity.playlist_one) behar bezala sortu da"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "letra bilatu"
},
"shareItem": {
"description": "deskripzioa",
"setExpiration": "iraungitze-data ezarri",
"success": "partekatzeko esteka arbelera kopiatu da (edo egin klik hemen irekitzeko)",
"expireInvalid": "iraungitze-data etorkizunean izan behar da",
"allowDownloading": "baimendu deskargatzea",
"createFailed": "partekatzea sortzeak huts egin du (partekatzea gaituta al dago?)"
},
"deletePlaylist": {
"success": "$t(entity.playlist_one) behar bezala ezabatu da",
"title": "$t(entity.playlist_one) ezabatu",
"input_confirm": "idatzi $t(entity.playlist_one)-(a)ren izena berresteko"
},
"editPlaylist": {
"success": "$t(entity.playlist_one) behar bezala eguneratu da",
"title": "$t(entity.playlist_one) editatu"
},
"queryEditor": {
"title": "kontsulta editorea",
"input_optionMatchAll": "guztiak bat etorri",
"input_optionMatchAny": "edozeinekin bat etorri"
},
"updateServer": {
"success": "zerbitzaria behar bezala eguneratu da",
"title": "zerbitzaria eguneratu"
},
"privateMode": {
"title": "modu pribatua",
"enabled": "modu pribatua gaituta, erreprodukzio egoera kanpoko integrazioetatik ezkutatuta dago orain",
"disabled": "modu pribatua desgaituta, erreprodukzio egoera ikusgai dago orain gaitutako kanpoko integrazioentzat"
}
},
"page": {
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"released": "argitaratuta",
"moreFromArtist": "$t(entity.artist_one) honetatik gehiago",
"moreFromGeneric": "{{item}}-(e)tik gehiago"
},
"albumList": {
"title": "$t(entity.album_other)",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"artistAlbums": "{{artist}}-(a)ren albumak"
},
"appMenu": {
"quit": "$t(common.quit)",
"settings": "$t(common.setting_other)",
"collapseSidebar": "tolestu alboko barra",
"expandSidebar": "zabaldu alboko barra",
"goBack": "atzera",
"goForward": "aurrera",
"manageServers": "kudeatu zerbitzariak",
"privateModeOff": "itzali modu pribatua",
"privateModeOn": "aktibatu modu pribatua",
"selectServer": "aukeratu zerbitzaria",
"version": "bertsioa {{version}}"
},
"manageServers": {
"url": "URLa",
"username": "erabiltzaile-izena",
"title": "kudeatu zerbitzariak",
"serverDetails": "zerbitzariaren xehetasunak",
"editServerDetailsTooltip": "editatu zerbitzariaren xehetasunak",
"removeServer": "kendu zerbitzaria"
},
"contextMenu": {
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"download": "deskargatu",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"play": "$t(player.play)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"numberSelected": "{{count}} hautatuta",
"shareItem": "partekatu elementua",
"goToAlbum": "joan $t(entity.album_one)-(e)ra",
"goToAlbumArtist": "joan $t(entity.albumArtist_one)-(e)ra",
"showDetails": "informazioa lortu"
},
"fullscreenPlayer": {
"config": {
"opacity": "opakotasuna",
"synchronized": "sinkronizatuta",
"unsynchronized": "sinkronizatu gabe",
"dynamicIsImage": "gaitu atzeko planoaren irudia",
"followCurrentLyric": "jarraitu uneko letra",
"lyricSize": "letraren tamaina",
"dynamicBackground": "atzeko plano dinamikoa",
"dynamicImageBlur": "irudiaren lausotze tamaina",
"lyricAlignment": "letraren lerrokatzea",
"showLyricMatch": "erakutsi letren bat-etortzea",
"showLyricProvider": "erakutsi letra hornitzailea"
},
"lyrics": "letrak",
"related": "erlazionatuta",
"upNext": "hurrengoa",
"visualizer": "bistaratzailea",
"noLyrics": "ez da letrarik aurkitu"
},
"genreList": {
"title": "$t(entity.genre_other)",
"showAlbums": "erakutsi $t(entity.album_other) $t(entity.album_other)",
"showTracks": "erakutsi $t(entity.genre_one) $t(entity.track_other)"
},
"globalSearch": {
"title": "komandoak",
"commands": {
"goToPage": "joan orrira",
"searchFor": "bilatu {{query}}",
"serverCommands": "zerbitzariaren komandoak"
}
},
"home": {
"title": "$t(common.home)",
"mostPlayed": "gehien entzundakoak",
"newlyAdded": "azken aldian gehitutako argitalpenak",
"recentlyPlayed": "azken aldian entzundakoak",
"recentlyReleased": "azken aldian argitaratutak",
"explore": "arakatu zure liburutegitik"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "aurreratua",
"generalTab": "orokorra",
"playbackTab": "erreprodukzioa",
"windowTab": "leihoa"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"tracks": "$t(entity.track_other)",
"myLibrary": "nire liburutegia",
"nowPlaying": "orain erreproduzitzen",
"shared": "partekatutako $t(entity.playlist_other)"
},
"trackList": {
"title": "$t(entity.track_other)",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"artistTracks": "{{artist}}-(r)en abestiak"
},
"albumArtistDetail": {
"about": "{{artist}}-(r)i buruz",
"relatedArtists": "erlazionatutako $t(entity.artist_other)",
"topSongs": "abesti nagusiak",
"topSongsFrom": "{{title}}-(a)ren abesti nagusiak",
"viewAll": "ikusi guztiak",
"viewAllTracks": "ikusi $t(entity.track_other) guztiak",
"appearsOn": "agertzen da hemen",
"recentReleases": "azken argitalpenak",
"viewDiscography": "ikusi diskografia"
},
"itemDetail": {
"copyPath": "kopiatu bidea arbelean",
"openFile": "erakutsi pista fitxategi-kudeatzailean",
"copiedPath": "bidea behar bezala kopiatu da"
},
"playlist": {
"reorder": "berrantolaketa IDaren arabera ordenatzean bakarrik gaituta dago"
}
}
}
+46 -39
View File
@@ -65,7 +65,7 @@
"unknown": "inconnu",
"areYouSure": "êtes-vous sûr?",
"edit": "éditer",
"favorite": "favoris",
"favorite": "favori",
"left": "gauche",
"save": "enregistrer",
"right": "droite",
@@ -85,7 +85,7 @@
"duration": "durée",
"name": "nom",
"maximize": "agrandir",
"decrease": "baisser",
"decrease": "diminuer",
"ok": "ok",
"description": "description",
"configure": "configurer",
@@ -110,7 +110,7 @@
"filter_other": "filtres",
"filters": "filtres",
"create": "créer",
"bitrate": "bitrate",
"bitrate": "bit binaire",
"saveAndReplace": "enregistrer et remplacer",
"action_one": "action",
"action_many": "actions",
@@ -144,7 +144,7 @@
"albumGain": "gain de l'album",
"albumPeak": "crête de l'album",
"close": "fermer",
"mbid": "Identifiants MusicBrainz",
"mbid": "Identifiant MusicBrainz",
"preview": "aperçu",
"share": "partager",
"reload": "recharger",
@@ -157,7 +157,7 @@
"newVersion": "une nouvelle version vient d'être installée ({{version}})",
"viewReleaseNotes": "voir la note de version",
"sampleRate": "taux d'échantillonnage",
"bitDepth": "bit par échantillon"
"bitDepth": "format d'échantillonnage"
},
"error": {
"remotePortWarning": "redémarrer le serveur pour appliquer le nouveau port",
@@ -187,7 +187,7 @@
},
"filter": {
"mostPlayed": "plus joués",
"playCount": "nombre d'écoute",
"playCount": "nombre d'écoutes",
"isCompilation": "est une compilation",
"recentlyPlayed": "récemment joué",
"isRated": "est noté",
@@ -202,7 +202,7 @@
"releaseDate": "date de sortie",
"communityRating": "note de la communauté",
"path": "chemin",
"favorited": "favoris",
"favorited": "favori",
"isRecentlyPlayed": "est récemment joué",
"isFavorited": "est favori",
"bpm": "BPM",
@@ -212,7 +212,7 @@
"songCount": "nombre de chansons",
"duration": "durée",
"random": "aléatoire",
"lastPlayed": "dernier joué",
"lastPlayed": "écouté récemment",
"toYear": "à l'année",
"fromYear": "depuis l'année",
"criticRating": "note des critiques",
@@ -256,10 +256,10 @@
"lyricAlignment": "alignement des paroles",
"useImageAspectRatio": "utiliser le ratio de l'image",
"opacity": "opacité",
"lyricSize": "Taille des paroles",
"lyricSize": "taille des paroles",
"lyricGap": "espacement des lettres",
"dynamicIsImage": "activer l'image d'arrière-plan",
"dynamicImageBlur": "intensité de flou sur image d'arrière-plan",
"dynamicImageBlur": "intensité du flou sur l'image d'arrière-plan",
"lyricOffset": "paroles décalées (ms)"
},
"upNext": "à suivre",
@@ -285,9 +285,10 @@
"home": {
"mostPlayed": "Les plus joués",
"newlyAdded": "Ajoutés récemment",
"explore": "explorer depuis la bibliothèque",
"explore": "Explorer depuis la bibliothèque",
"recentlyPlayed": "Joués récemment",
"title": "$t(common.home)"
"title": "$t(common.home)",
"recentlyReleased": "Sortis récemment"
},
"albumDetail": {
"moreFromArtist": "plus de $t(entity.artist_one)",
@@ -364,7 +365,7 @@
"viewAllTracks": "voir tout $t(entity.track_other)",
"recentReleases": "sorties récentes",
"viewDiscography": "voir la discographie",
"relatedArtists": "en rapport avec $t(entity.artist_other)",
"relatedArtists": "$t(entity.artist_other) similaires",
"topSongs": "meilleurs titres"
},
"itemDetail": {
@@ -394,12 +395,12 @@
"accentColor": "couleur d'accentuation",
"accentColor_description": "définit la couleur d'accentuation de l'application",
"applicationHotkeys": "raccourcis clavier d'application",
"crossfadeDuration": "durée de fondue enchaînée",
"crossfadeDuration": "durée de fondu enchaîné",
"audioPlayer": "lecteur audio",
"applicationHotkeys_description": "configurer les raccourcis clavier dapplication. activer la case à cocher pour définir comme raccourci clavier global (bureau uniquement)",
"crossfadeStyle_description": "sélectionnez le style du fondu enchaîné à utiliser pour le lecteur audio",
"customFontPath": "chemin de police personnalisé",
"disableAutomaticUpdates": "désactiver les mises à jour automatique",
"disableAutomaticUpdates": "désactiver les mises à jour automatiques",
"customFontPath_description": "définit le chemin de police personnalisé pour l'application",
"remotePort_description": "définit le port du serveur de contrôle à distance",
"hotkey_skipBackward": "reculer",
@@ -417,9 +418,9 @@
"sampleRate": "taux d'échantillonnage",
"sampleRate_description": "sélectionne le taux d'échantillonnage de sortie utilisé si la fréquence d'échantillonnage sélectionnée est différente de celle du média actuel. une valeur inférieure à 8000 utilisera la fréquence par défaut",
"hotkey_zoomIn": "zoom avant",
"scrobble_description": "scrobble les lectures à votre serveur multimédia",
"scrobble_description": "scrobbler les lectures à votre serveur multimédia",
"hotkey_browserForward": "avancer",
"discordUpdateInterval": "interval de mise à jour de {{discord}} rich presence",
"discordUpdateInterval": "intervalle de mise à jour de {{discord}} Rich Presence",
"fontType_optionBuiltIn": "police intégrée",
"hotkey_playbackPlayPause": "lecture / pause",
"hotkey_rate1": "noter 1 étoile",
@@ -502,11 +503,11 @@
"sidebarConfiguration": "configuration de la barre latérale",
"sidebarConfiguration_description": "sélectionnez les éléments et l'ordre dans lequel ils seront affichés dans la barre latérale",
"sidebarPlaylistList": "liste des listes de lecture de la barre latérale",
"sidebarCollapsedNavigation": "navigation de la barre latéral (réduite)",
"sidebarCollapsedNavigation": "navigation de la barre latérale (réduite)",
"skipDuration": "durée de l'avance rapide",
"sidePlayQueueStyle_optionAttached": "attaché",
"sidePlayQueueStyle": "style de la liste de lecture latérale",
"sidebarPlaylistList_description": "affiche ou cache le menu de listes de lecture de la barre latérale",
"sidebarPlaylistList_description": "affiche ou cache la liste des listes de lecture de la barre latérale",
"sidePlayQueueStyle_description": "définit le style de la liste de lecture latérale",
"sidePlayQueueStyle_optionDetached": "détaché",
"volumeWheelStep_description": "la valeur de volume à modifier lors du défilement de la molette de la souris sur le curseur de volume",
@@ -559,13 +560,13 @@
"homeConfiguration": "configuration de la page d'accueil",
"homeFeature": "carrousel de la page d'accueil",
"homeFeature_description": "active ou désactive le carrousel sur la page d'accueil",
"imageAspectRatio": "utiliser le rapport hauteur/largeur natif de la pochette",
"imageAspectRatio_description": "si cette option est activée, les pochettes seront affichées en utilisant leur rapport hauteur/largeur natif. pour les pochettes qui n'ont pas un rapport 1:1 (carré), l'espace restant sera vide",
"imageAspectRatio": "utiliser le rapport hauteur/largeur natif de la pochette d'album",
"imageAspectRatio_description": "si cette option est activée, les pochettes d'album seront affichées en utilisant leur rapport hauteur/largeur natif. pour les pochettes qui n'ont pas un rapport 1:1 (carré), l'espace restant sera vide",
"mpvExtraParameters_help": "un par ligne",
"passwordStore_description": "quel mot de passe utiliser. changez cela si vous rencontrez des problèmes pour stocker les mots de passe.",
"playerAlbumArtResolution": "résolution de la pochette de l'album du lecteur",
"playerAlbumArtResolution": "résolution de la pochette d'album du lecteur",
"passwordStore": "mots de passe",
"playerAlbumArtResolution_description": "la résolution pour l'aperçu de la pochette d'album agrandie du lecteur. plus grand le rend plus net, mais peut ralentir le chargement. la valeur par défaut est 0 (automatique)",
"playerAlbumArtResolution_description": "résolution pour l'aperçu de la pochette d'album agrandie du lecteur. plus grand le rend plus net, mais peut ralentir le chargement. la valeur par défaut est 0 (automatique)",
"homeConfiguration_description": "configurer quels éléments sont affichés sur la page d'accueil, et dans quel ordre",
"startMinimized": "démarrer l'application en mode réduit",
"genreBehavior_description": "détermine si cliquer sur un genre ouvre par défaut la liste des pistes ou des albums",
@@ -589,7 +590,7 @@
"contextMenu": "configuration du menu contextuel (clic droit)",
"contextMenu_description": "permet de masquer les éléments qui s'affichent dans le menu lorsque vous cliquez avec le bouton droit de la souris sur un élément. les éléments qui ne sont pas cochés seront masqués",
"albumBackground": "image d'arrière-plan de l'album",
"albumBackground_description": "ajoute une image d'arrière-plan pour les pages de l'album contenant les illustrations de l'album",
"albumBackground_description": "ajoute une image d'arrière-plan pour les pages de l'album contenant une pochette d'album",
"albumBackgroundBlur_description": "ajuste le niveau de flou appliqué à l'image d'arrière-plan de l'album",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerbarOpenDrawer": "basculement plein écran de la barre de lecteur",
@@ -607,15 +608,15 @@
"transcodeNote": "prend effet après 1 (web) - 2 (mpv) titres",
"trayEnabled_description": "afficher ou masquer l'icône et le menu de la barre d'état système. si désactivé, désactive également la réduction et la sortie vers la barre d'état système",
"doubleClickBehavior_description": "si vrai, toutes les pistes correspondantes dans une recherche de piste seront mises en file d'attente. sinon, seule celle sur laquelle vous avez cliqué sera mise en file d'attente",
"albumBackgroundBlur": "taille du flou de l'image d'arrière-plan de l'album",
"albumBackgroundBlur": "intensité du flou de l'image d'arrière-plan de l'album",
"lastfmApiKey": "clé API {{lastfm}}",
"lastfmApiKey_description": "la clé API pour {{lastfm}} . requise pour la pochette d'album",
"lastfmApiKey_description": "la clé API pour {{lastfm}}. requise pour la pochette d'album",
"discordServeImage": "servir l'image {{discord}} depuis le serveur",
"discordServeImage_description": "partage pochette du status d'activité {{discord}} depuis le serveur lui même, disponible uniquement pour jellyfin et navidrome",
"discordServeImage_description": "partage de la pochette d'album de Rich Presence {{discord}} depuis le serveur directement (disponible uniquement pour Jellyfin et Navidrome)",
"lastfm": "affiche les liens de last.fm",
"musicbrainz_description": "affiches les liens vers musicbrainz sur les pages des artistes/albums, quand mbid existes",
"musicbrainz_description": "affiche les liens vers MusicBrainz sur les pages des artistes/albums, quand l'identifiant MusicBrainz existe",
"lastfm_description": "affiche les liens vers last.fm sur les pages des artistes/albums",
"musicbrainz": "affiches les liens musicbrainz",
"musicbrainz": "affiche les liens MusicBrainz",
"neteaseTranslation": "Activer les traductions NetEase",
"neteaseTranslation_description": "Lorsque cette option est activée, récupère et affiche les paroles traduites de NetEase si elles sont disponibles.",
"preferLocalLyrics_description": "privilégier les paroles locales aux paroles distantes lorsqu'elles sont disponibles",
@@ -627,12 +628,16 @@
"notify": "activer les notifications des chansons",
"notify_description": "affiche une notification lors du changement de chanson",
"discordDisplayType": "type d'affichage du status {{discord}}",
"discordDisplayType_description": "change ce que vous écoutez dans votre statut",
"discordDisplayType_description": "modifie ce que vous écoutez dans votre statut",
"discordDisplayType_songname": "nom du morceau",
"discordDisplayType_artistname": "nom(s) dartiste",
"hotkey_navigateHome": "aller à l'accueil",
"preventSleepOnPlayback_description": "Empêche la mise en veille du lecteur lorsque la musique est en cours de lecture",
"preventSleepOnPlayback": "Empêche la mise en veille lors de la lecture"
"preventSleepOnPlayback": "Empêche la mise en veille lors de la lecture",
"discordLinkType": "lien de Rich Presence {{discord}}",
"discordLinkType_description": "Ajoute des liens externes vers {{lastfm}} ou {{musicbrainz}} aux champs piste et artiste de la Rich Presence de {{discord}}. {{musicbrainz}} est la méthode la plus précise, mais nécessite des balises et ne fournit pas de liens vers les artistes, tandis que {{lastfm}} doit toujours fournir un lien. Aucune requête réseau supplémentaire n'est effectuée",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} avec {{lastfm}} si le premier n'est pas disponible"
},
"form": {
"deletePlaylist": {
@@ -651,7 +656,9 @@
"input_savePassword": "enregister le mot de passe",
"ignoreSsl": "ignorer ssl $t(common.restartRequired)",
"ignoreCors": "ignorer cors $t(common.restartRequired)",
"error_savePassword": "une erreur sest produite lors de la tentative de sauvegarde du mot de passe"
"error_savePassword": "une erreur sest produite lors de la tentative de sauvegarde du mot de passe",
"input_preferInstantMix": "Préférer le mix instantané",
"input_preferInstantMixDescription": "Utiliser uniquement le mix instantané pour jouer des pistes similaires. Activez cette option si vous avez des plugins qui modifient ce comportement"
},
"addToPlaylist": {
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) ajouté à $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
@@ -682,7 +689,7 @@
"success": "$t(entity.playlist_one) mis à jour avec succès"
},
"lyricSearch": {
"title": "rechercher parole",
"title": "recherche de paroles",
"input_name": "$t(common.name)",
"input_artist": "$t(entity.artist_one)"
},
@@ -747,9 +754,9 @@
"trackWithCount_one": "{{count}} piste",
"trackWithCount_many": "{{count}} pistes",
"trackWithCount_other": "{{count}} pistes",
"play_one": "{{count}} écouter",
"play_many": "{{count}} écoute",
"play_other": "{{count}} écoute",
"play_one": "{{count}} écoute",
"play_many": "{{count}} écoutes",
"play_other": "{{count}} écoutes",
"song_one": "titre",
"song_many": "titres",
"song_other": "titres"
@@ -777,7 +784,7 @@
"releaseDate": "date de sortie",
"titleCombined": "$t(common.title) (combiné)",
"dateAdded": "date d'ajout",
"lastPlayed": "dernière écoute",
"lastPlayed": "écouté récemment",
"trackNumber": "numéro de piste",
"rowIndex": "index de ligne",
"playCount": "nombre de lecture",
@@ -809,7 +816,7 @@
"album": "album",
"rating": "note",
"favorite": "favori",
"playCount": "lectures",
"playCount": "écoutes",
"releaseYear": "année",
"biography": "biographie",
"releaseDate": "date de sortie",
@@ -822,7 +829,7 @@
"path": "chemin",
"discNumber": "disque",
"albumCount": "$t(entity.album_other)",
"lastPlayed": "dernière lecture",
"lastPlayed": "écouté récemment",
"artist": "$t(entity.artist_one)",
"genre": "$t(entity.genre_one)",
"songCount": "$t(entity.track_other)",
+10 -3
View File
@@ -404,7 +404,11 @@
"notify_description": "mostra una notifica quando cambia la traccia riprodotta",
"preventSleepOnPlayback": "non sospendere in riproduzione",
"preventSleepOnPlayback_description": "non sospendere il sistema quando la riproduzione è attiva",
"discordDisplayType": "stile dello stato su {{discord}}"
"discordDisplayType": "stile dello stato su {{discord}}",
"discordLinkType": "link di attività {{discord}}",
"discordLinkType_description": "aggiunge collegamenti esterni a {{lastfm}} o {{musicbrainz}} ai campi del brano e dell'artista nell'attività {{discord}}. {{musicbrainz}} è il più accurato, ma richiede tag e non fornisce collegamenti dell'artista mentre {{lastfm}} dovrebbe sempre fornire un link. non rende richieste di rete extra",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} con {{lastfm}} fallback"
},
"error": {
"remotePortWarning": "riavvia il server per applicare la nuova porta",
@@ -560,7 +564,8 @@
"newlyAdded": "nuovi rilasci aggiunti",
"title": "$t(common.home)",
"explore": "esplora dalla tua libreria",
"recentlyPlayed": "riprodotti recentemente"
"recentlyPlayed": "riprodotti recentemente",
"recentlyReleased": "appena pubblicato"
},
"albumDetail": {
"moreFromArtist": "di più da questo $t(entity.artist_one)",
@@ -656,7 +661,9 @@
"input_savePassword": "salva password",
"ignoreSsl": "ignora ssl ($t(common.restartRequired))",
"ignoreCors": "ignora cors ($t(common.restartRequired))",
"error_savePassword": "si è verificato un errore quando si è provato a salvare la password"
"error_savePassword": "si è verificato un errore quando si è provato a salvare la password",
"input_preferInstantMix": "preferisci mix istantaneo",
"input_preferInstantMixDescription": "usa solo mix istantaneo per ottenere canzoni simili. utile se si dispone di plugin che modificano questo comportamento"
},
"addToPlaylist": {
"success": "aggiunto $t(entity.trackWithCount, {\"count\": {{message}} }) a $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
+5 -1
View File
@@ -209,7 +209,11 @@
"moveToBottom": "末尾に移動",
"setRating": "評価",
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) エディタの切り替え",
"removeFromFavorites": "$t(entity.favorite_other) から削除"
"removeFromFavorites": "$t(entity.favorite_other) から削除",
"openIn": {
"lastfm": "Last.fmで開く",
"musicbrainz": "MusicBrainzで開く"
}
},
"common": {
"backward": "戻る",
+17 -4
View File
@@ -200,7 +200,8 @@
"badAlbum": "ta strona jest wyświetlana, ponieważ ten utwór nie jest częścią albumu. najprawdopodobniej ten problem występuje, jeśli utwór znajduje się w nadrzędnym folderze plików z muzyką. jellyfin grupuje utwory tylko wtedy, gdy znajdują się one w folderze.",
"networkError": "wystąpił błąd sieciowy",
"openError": "nie można otworzyć pliku",
"badValue": "niewłaściwa opcja \"{{value}}\". ta wartość już nie istnieje"
"badValue": "niewłaściwa opcja \"{{value}}\". ta wartość już nie istnieje",
"notificationDenied": "odmówiono uprawnień dla powiadomień. to ustawienie nie będzie miało efektu"
},
"filter": {
"mostPlayed": "najczęściej odtwarzane",
@@ -305,6 +306,11 @@
"success": "link do udostępniania skopiowany do schowka (lub kliknij tutaj, aby otworzyć)",
"createFailed": "nie udało się utworzyć linku do udostępniania (czy udostępnianie jest włączone?)",
"expireInvalid": "ustawiony czas wygaśnięcia musi być w przyszłości"
},
"privateMode": {
"enabled": "tryb prywatny włączony, status odtwarzania jest ukryty przed usługami zewnętrznymi",
"disabled": "tryb prywatny wyłączony, status odtwarzania jest widoczny dla usług zewnętrznych",
"title": "tryb prywatny"
}
},
"page": {
@@ -341,7 +347,9 @@
"openBrowserDevtools": "otwórz narzędzia deweloperskie przeglądarki",
"quit": "$t(common.quit)",
"goBack": "do tyłu",
"goForward": "do przodu"
"goForward": "do przodu",
"privateModeOff": "wyłącz tryb prywatny",
"privateModeOn": "włącz tryb prywatny"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
@@ -365,7 +373,9 @@
"download": "pobierz",
"playShuffled": "$t(player.shuffle)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"moveToNext": "$t(action.moveToNext)"
"moveToNext": "$t(action.moveToNext)",
"goToAlbum": "przejdź do $t(entity.album_one)",
"goToAlbumArtist": "przejdź do $t(entity.albumArtist_one)"
},
"albumDetail": {
"moreFromArtist": "więcej od $t(entity.artist_one)",
@@ -724,7 +734,10 @@
"lastfm_description": "pokazuj linki do last.fm na stronach artystów/albumów",
"notify": "włącz powiadomienia o piosenkach",
"musicbrainz": "pokazuj linki do musicbrainz",
"musicbrainz_description": "pokazuj linki do musicbrainz na stronach artystów/albumów, gdzie istnieje mbid"
"musicbrainz_description": "pokazuj linki do musicbrainz na stronach artystów/albumów, gdzie istnieje mbid",
"discordPausedStatus": "pokaż status podczas pauzy",
"discordServeImage": "wysyłaj obrazy dla {{discord}} z serwera",
"discordServeImage_description": "pokazuj okładki w statusie {{discord}} prosto z serwera, dostępne tylko dla jellyfin i navidrome"
},
"table": {
"config": {
+4 -2
View File
@@ -233,13 +233,15 @@
"ignoreCors": "cors'u $t(common.restartRequired) görmezden gel",
"ignoreSsl": "ssl bağlantısını görmezden gel $t(common.restartRequired)",
"input_legacyAuthentication": "eski kimlik doğrulamayı etkinleştir",
"input_name": "sucunu ismi",
"input_name": "sunucu ismi",
"input_password": "şifre",
"input_savePassword": "şifreyi kaydet",
"input_url": "URL",
"input_username": "kullanıcı ismi",
"success": "sunucu başarıyla eklendi",
"title": "sunucu ekle"
"title": "sunucu ekle",
"input_preferInstantMix": "anında mix tercih et",
"input_preferInstantMixDescription": "sadece benzer şarkılari bulmak icin anında mix kullan. Bu davranışı değiştiren eklentilere sahipseniz faydalı"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
+19 -4
View File
@@ -399,7 +399,7 @@
"lastfmApiKey": "{{lastfm}} API 密钥",
"lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需",
"discordServeImage": "从服务器提供 {{discord}} 图像",
"discordServeImage_description": "分享 {{discord}} 封面艺术图,来自 rich presence 服务器,仅适用于 jellyfin 和 navidrome",
"discordServeImage_description": "从服务器本身分享 {{discord}} rich presence 的封面艺术,仅适用于 jellyfin 和 navidrome。 {{discord}} 使用机器人来获取图像,因此您的服务器必须可通过公共互联网访问。",
"musicbrainz": "显示 musicbrainz 链接",
"musicbrainz_description": "在 mbid 的艺术家/专辑页面上显示 musicbrainz 的链接",
"lastfm": "显示 last.fm 链接",
@@ -418,7 +418,19 @@
"discordDisplayType_artistname": "艺术家名称",
"hotkey_navigateHome": "导航到主页",
"preventSleepOnPlayback": "防止播放时进入睡眠状态",
"preventSleepOnPlayback_description": "播放音乐时防止显示器进入睡眠状态"
"preventSleepOnPlayback_description": "播放音乐时防止显示器进入睡眠状态",
"discordLinkType": "{{discord}} 状态链接",
"discordLinkType_description": "在 {{discord}} 的歌曲和艺术家字段中添加 {{lastfm}} 或 {{musicbrainz}} 的外部链接。{{musicbrainz}} 最准确,但需要标签,且不提供艺术家链接,而 {{lastfm}} 则始终提供链接。无需额外的网络请求",
"discordLinkType_none": "$t(common.none)",
"discordLinkType_mbz_lastfm": "{{musicbrainz}} 和 {{lastfm}} 后备",
"artistBackground": "艺术家背景图片",
"artistBackground_description": "为包含艺术家作品的艺术家页面添加背景图片",
"artistBackgroundBlur": "艺术家背景图像模糊尺寸",
"artistBackgroundBlur_description": "调整应用于艺术家背景图像的模糊程度",
"releaseChannel_optionLatest": "stable",
"releaseChannel_optionBeta": "beta",
"releaseChannel": "发布渠道",
"releaseChannel_description": "选择稳定版本或测试版以进行自动更新"
},
"error": {
"remotePortWarning": "重启服务器使新端口生效",
@@ -548,7 +560,8 @@
"newlyAdded": "最近添加的发布",
"explore": "从库中搜索",
"recentlyPlayed": "最近播放",
"title": "$t(common.home)"
"title": "$t(common.home)",
"recentlyReleased": "最近发布"
},
"albumDetail": {
"moreFromArtist": "更多该$t(entity.artist_one)作品",
@@ -662,7 +675,9 @@
"ignoreSsl": "忽略 ssl $t(common.restartRequired)",
"ignoreCors": "忽略 cors $t(common.restartRequired)",
"error_savePassword": "保存密码时出现错误",
"input_url": "url"
"input_url": "url",
"input_preferInstantMixDescription": "仅使用即时混音来获取类似的歌曲。如果您有修改此行为的插件,则很有用",
"input_preferInstantMix": "首选即时混音"
},
"addToPlaylist": {
"success": "添加$t(entity.trackWithCount, {\"count\": {{message}} })到$t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
+34 -4
View File
@@ -23,6 +23,7 @@ import { access, constants, readFile, writeFile } from 'fs';
import path, { join } from 'path';
import { deflate, inflate } from 'zlib';
import packageJson from '../../package.json';
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
import { shutdownServer } from './features/core/remote';
import { store } from './features/core/settings';
@@ -43,11 +44,33 @@ export default class AppUpdater {
constructor() {
log.transports.file.level = 'info';
autoUpdater.logger = autoUpdaterLogInterface;
autoUpdater.checkForUpdatesAndNotify();
if (store.get('release_channel') === 'beta') {
autoUpdater.channel = 'beta';
const isBetaVersion = packageJson.version.includes('-beta');
const releaseChannel = store.get('release_channel');
const isNotConfigured = !releaseChannel;
console.log('Release channel: ', releaseChannel);
console.log('Is beta version: ', isBetaVersion);
if (isNotConfigured) {
console.log(
'Release channel not configured, setting to ',
isBetaVersion ? 'beta' : 'latest',
);
store.set('release_channel', isBetaVersion ? 'beta' : 'latest');
}
if (releaseChannel === 'beta') {
autoUpdater.channel = 'beta';
autoUpdater.allowPrerelease = true;
autoUpdater.disableDifferentialDownload = true;
} else if (releaseChannel === 'latest') {
autoUpdater.channel = 'latest';
autoUpdater.allowDowngrade = true;
autoUpdater.allowPrerelease = false;
}
autoUpdater.checkForUpdatesAndNotify();
}
}
@@ -525,7 +548,14 @@ async function createWindow(first = true): Promise<void> {
}
}
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
const enableWindowsMediaSession = store.get('mediaSession', false) as boolean;
const shouldDisableMediaFeatures = process.platform !== 'win32' || !enableWindowsMediaSession;
if (shouldDisableMediaFeatures) {
app.commandLine.appendSwitch(
'disable-features',
'HardwareMediaKeyHandling,MediaSessionService',
);
}
// https://github.com/electron/electron/issues/46538#issuecomment-2808806722
app.commandLine.appendSwitch('gtk-version', '3');
@@ -83,9 +83,9 @@ export const AlbumDetailHeader = forwardRef(
},
{
id: 'songCount',
value: `${detailQuery?.data?.songCount} ${t('entity.track_other', {
value: t('entity.trackWithCount', {
count: detailQuery?.data?.songCount as number,
})}`,
}),
},
{
id: 'duration',
@@ -11,21 +11,22 @@ import { PlayButton, PlayerButton } from '/@/renderer/features/player/components
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
import { openShuffleAllModal } from '/@/renderer/features/player/components/shuffle-all-modal';
import { useCenterControls } from '/@/renderer/features/player/hooks/use-center-controls';
import { useMediaSession } from '/@/renderer/features/player/hooks/use-media-session';
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
import {
useAppStore,
useAppStoreActions,
useCurrentPlayer,
useCurrentSong,
useCurrentStatus,
useCurrentTime,
useRepeatStatus,
useSetCurrentTime,
useShuffleStatus,
} from '/@/renderer/store';
import {
useHotkeySettings,
usePlaybackType,
useRepeatStatus,
useSetCurrentTime,
useSettingsStore,
} from '/@/renderer/store/settings.store';
useShuffleStatus,
} from '/@/renderer/store';
import { Icon } from '/@/shared/components/icon/icon';
import { Text } from '/@/shared/components/text/text';
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
@@ -38,16 +39,14 @@ interface CenterControlsProps {
export const CenterControls = ({ playersRef }: CenterControlsProps) => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const [isSeeking, setIsSeeking] = useState(false);
const currentSong = useCurrentSong();
const skip = useSettingsStore((state) => state.general.skipButtons);
const buttonSize = useSettingsStore((state) => state.general.buttonSize);
const playbackType = usePlaybackType();
const player1 = playersRef?.current?.player1;
const player2 = playersRef?.current?.player2;
const status = useCurrentStatus();
const player = useCurrentPlayer();
const setCurrentTime = useSetCurrentTime();
const repeat = useRepeatStatus();
const shuffle = useShuffleStatus();
const { bindings } = useHotkeySettings();
@@ -58,7 +57,6 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
handlePlay,
handlePlayPause,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
handleStop,
@@ -67,31 +65,6 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
} = useCenterControls({ playersRef });
const handlePlayQueueAdd = usePlayQueueAdd();
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
const currentTime = useCurrentTime();
const currentPlayerRef = player === 1 ? player1 : player2;
const duration = formatDuration(songDuration * 1000 || 0);
const formattedTime = formatDuration(currentTime * 1000 || 0);
useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (status === PlayerStatus.PLAYING && !isSeeking) {
if (!isElectron() || playbackType === PlaybackType.WEB) {
// Update twice a second for slightly better performance
interval = setInterval(() => {
if (currentPlayerRef) {
setCurrentTime(currentPlayerRef.getCurrentTime());
}
}, 500);
}
}
return () => clearInterval(interval);
}, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]);
const [seekValue, setSeekValue] = useState(0);
useHotkeys([
[bindings.playPause.isGlobal ? '' : bindings.playPause.hotkey, handlePlayPause],
[bindings.play.isGlobal ? '' : bindings.play.hotkey, handlePlay],
@@ -111,6 +84,8 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
],
]);
useMediaSession(playersRef);
return (
<>
<div className={styles.controlsContainer} style={{ zIndex: 50001 }}>
@@ -252,53 +227,108 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
/>
</div>
</div>
<div className={styles.sliderContainer}>
<div className={styles.sliderValueWrapper}>
<Text
className={PlaybackSelectors.elapsedTime}
fw={600}
isMuted
isNoSelect
size="xs"
>
{formattedTime}
</Text>
</div>
<div className={styles.sliderWrapper}>
<PlayerbarSlider
label={(value) => formatDuration(value * 1000)}
max={songDuration}
min={0}
onChange={(e) => {
setIsSeeking(true);
setSeekValue(e);
}}
onChangeEnd={(e) => {
// There is a timing bug in Mantine in which the onChangeEnd
// event fires before onChange. Add a small delay to force
// onChangeEnd to happen after onCHange
setTimeout(() => {
handleSeekSlider(e);
setIsSeeking(false);
}, 50);
}}
size={6}
value={!isSeeking ? currentTime : seekValue}
w="100%"
/>
</div>
<div className={styles.sliderValueWrapper}>
<Text
className={PlaybackSelectors.totalDuration}
fw={600}
isMuted
isNoSelect
size="xs"
>
{duration}
</Text>
</div>
</div>
<PlayerSeekSlider player1={player1} player2={player2} playersRef={playersRef} />
</>
);
};
const PlayerSeekSlider = ({
player1,
player2,
playersRef,
}: {
player1: any;
player2: any;
playersRef: any;
}) => {
const { handleSeekSlider } = useCenterControls({ playersRef });
const player = useCurrentPlayer();
const playbackType = usePlaybackType();
const setCurrentTime = useSetCurrentTime();
const status = useCurrentStatus();
const currentSong = useCurrentSong();
const songDuration = currentSong?.duration ? currentSong.duration / 1000 : 0;
const currentTime = useCurrentTime();
const currentPlayerRef = player === 1 ? player1 : player2;
const [isSeeking, setIsSeeking] = useState(false);
useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (status === PlayerStatus.PLAYING && !isSeeking) {
if (!isElectron() || playbackType === PlaybackType.WEB) {
// Update twice a second for slightly better performance
interval = setInterval(() => {
if (currentPlayerRef) {
setCurrentTime(currentPlayerRef.getCurrentTime());
}
}, 500);
}
}
return () => clearInterval(interval);
}, [currentPlayerRef, isSeeking, setCurrentTime, playbackType, status]);
const { showTimeRemaining } = useAppStore();
const { setShowTimeRemaining } = useAppStoreActions();
const formattedDuration = formatDuration(songDuration * 1000 || 0);
const formattedTimeRemaining = formatDuration((currentTime - songDuration) * 1000 || 0);
const formattedTime = formatDuration(currentTime * 1000 || 0);
const [seekValue, setSeekValue] = useState(0);
return (
<div className={styles.sliderContainer}>
<div className={styles.sliderValueWrapper}>
<Text
className={PlaybackSelectors.elapsedTime}
fw={600}
isMuted
isNoSelect
size="xs"
style={{ userSelect: 'none' }}
>
{formattedTime}
</Text>
</div>
<div className={styles.sliderWrapper}>
<PlayerbarSlider
label={(value) => formatDuration(value * 1000)}
max={songDuration}
min={0}
onChange={(e) => {
setIsSeeking(true);
setSeekValue(e);
}}
onChangeEnd={(e) => {
// There is a timing bug in Mantine in which the onChangeEnd
// event fires before onChange. Add a small delay to force
// onChangeEnd to happen after onCHange
setTimeout(() => {
handleSeekSlider(e);
setIsSeeking(false);
}, 50);
}}
size={6}
value={!isSeeking ? currentTime : seekValue}
w="100%"
/>
</div>
<div className={styles.sliderValueWrapper}>
<Text
className={PlaybackSelectors.totalDuration}
fw={600}
isMuted
isNoSelect
onClick={() => setShowTimeRemaining(!showTimeRemaining)}
role="button"
size="xs"
style={{ cursor: 'pointer', userSelect: 'none' }}
>
{showTimeRemaining ? formattedTimeRemaining : formattedDuration}
</Text>
</div>
</div>
);
};
@@ -0,0 +1,135 @@
import { useEffect } from 'react';
import { useCenterControls } from '/@/renderer/features/player/hooks/use-center-controls';
import {
useCurrentSong,
useCurrentStatus,
usePlaybackSettings,
useSettingsStore,
} from '/@/renderer/store';
import { PlayerStatus } from '/@/shared/types/types';
export const useMediaSession = (playersRef: { player1: any; player2: any }) => {
const { mediaSession: mediaSessionEnabled } = usePlaybackSettings();
const playerStatus = useCurrentStatus();
const currentSong = useCurrentSong();
const mediaSession = navigator.mediaSession;
const skip = useSettingsStore((state) => state.general.skipButtons);
const {
handleNextTrack,
handlePause,
handlePlay,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
handleStop,
} = useCenterControls({
playersRef,
});
useEffect(() => {
if (!mediaSessionEnabled || !mediaSession) {
return;
}
mediaSession.setActionHandler('nexttrack', () => {
console.log('nexttrack');
handleNextTrack();
});
mediaSession.setActionHandler('pause', () => {
console.log('pause');
handlePause();
});
mediaSession.setActionHandler('play', () => {
console.log('play');
handlePlay();
});
mediaSession.setActionHandler('previoustrack', () => {
console.log('previoustrack');
handlePrevTrack();
});
mediaSession.setActionHandler('seekto', (e) => {
handleSeekSlider(e.seekTime);
});
mediaSession.setActionHandler('stop', () => {
handleStop();
});
mediaSession.setActionHandler('seekbackward', (e) => {
handleSkipBackward(e.seekOffset || skip?.skipBackwardSeconds || 5);
});
mediaSession.setActionHandler('seekforward', (e) => {
handleSkipForward(e.seekOffset || skip?.skipForwardSeconds || 5);
});
return () => {
mediaSession.setActionHandler('nexttrack', null);
mediaSession.setActionHandler('pause', null);
mediaSession.setActionHandler('play', null);
mediaSession.setActionHandler('previoustrack', null);
mediaSession.setActionHandler('seekto', null);
mediaSession.setActionHandler('stop', null);
mediaSession.setActionHandler('seekbackward', null);
mediaSession.setActionHandler('seekforward', null);
};
}, [
handleNextTrack,
handlePause,
handlePlay,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
handleStop,
mediaSession,
mediaSessionEnabled,
skip?.skipBackwardSeconds,
skip?.skipForwardSeconds,
]);
useEffect(() => {
if (!mediaSessionEnabled || !mediaSession) {
return;
}
const updateMetadata = () => {
mediaSession.metadata = new MediaMetadata({
album: currentSong?.album ?? '',
artist: currentSong?.artistName ?? '',
artwork: currentSong?.imageUrl
? [{ src: currentSong.imageUrl, type: 'image/png' }]
: [],
title: currentSong?.name ?? '',
});
};
updateMetadata();
return () => {
mediaSession.metadata = null;
};
}, [currentSong, mediaSession, mediaSessionEnabled]);
useEffect(() => {
if (!mediaSessionEnabled || !mediaSession) {
return;
}
if (mediaSession) {
const status = playerStatus === PlayerStatus.PLAYING ? 'playing' : 'paused';
mediaSession.playbackState = status;
}
return () => {
mediaSession.playbackState = 'none';
};
}, [playerStatus, mediaSession, mediaSessionEnabled]);
};
@@ -1,9 +1,11 @@
import { StylesSettings } from '/@/renderer/features/settings/components/advanced/styles-settings';
import { UpdateSettings } from '/@/renderer/features/settings/components/window/update-settings';
import { Stack } from '/@/shared/components/stack/stack';
export const AdvancedTab = () => {
return (
<Stack gap="md">
<UpdateSettings />
<StylesSettings />
</Stack>
);
@@ -0,0 +1,45 @@
import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next';
import {
SettingOption,
SettingsSection,
} from '/@/renderer/features/settings/components/settings-section';
import { usePlaybackSettings, useSettingsStoreActions } from '/@/renderer/store/settings.store';
import { Switch } from '/@/shared/components/switch/switch';
const isWindows = window.api.utils.isWindows();
const isDesktop = isElectron();
export const MediaSessionSettings = () => {
const { t } = useTranslation();
const { mediaSession } = usePlaybackSettings();
const { toggleMediaSession } = useSettingsStoreActions();
function handleMediaSessionChange() {
const current = mediaSession;
toggleMediaSession();
window.api.ipc.send('settings-set', { property: 'mediaSession', value: !current });
}
const mediaSessionOptions: SettingOption[] = [
{
control: (
<Switch
aria-label="Toggle media Session"
defaultChecked={mediaSession}
onChange={handleMediaSessionChange}
/>
),
description: t('setting.mediaSession', {
context: 'description',
postProcess: 'sentenceCase',
}),
isHidden: isDesktop && !isWindows,
note: t('common.restartRequired', { postProcess: 'sentenceCase' }),
title: t('setting.mediaSession', { postProcess: 'sentenceCase' }),
},
];
return <SettingsSection divider options={mediaSessionOptions} />;
};
@@ -3,6 +3,7 @@ import { lazy, Suspense, useMemo } from 'react';
import { AudioSettings } from '/@/renderer/features/settings/components/playback/audio-settings';
import { LyricSettings } from '/@/renderer/features/settings/components/playback/lyric-settings';
import { MediaSessionSettings } from '/@/renderer/features/settings/components/playback/media-session-settings';
import { ScrobbleSettings } from '/@/renderer/features/settings/components/playback/scrobble-settings';
import { TranscodeSettings } from '/@/renderer/features/settings/components/playback/transcode-settings';
import { useSettingsStore } from '/@/renderer/store';
@@ -31,6 +32,7 @@ export const PlaybackTab = () => {
<AudioSettings hasFancyAudio={hasFancyAudio} />
<Suspense fallback={<></>}>{hasFancyAudio && <MpvSettings />}</Suspense>
<TranscodeSettings />
<MediaSessionSettings />
<ScrobbleSettings />
<LyricSettings />
</Stack>
@@ -10,7 +10,6 @@ import { Select } from '/@/shared/components/select/select';
import { Switch } from '/@/shared/components/switch/switch';
const localSettings = isElectron() ? window.api.localSettings : null;
const utils = isElectron() ? window.api.utils : null;
export const UpdateSettings = () => {
const { t } = useTranslation();
@@ -37,7 +36,9 @@ export const UpdateSettings = () => {
value: 'beta',
},
]}
defaultValue={'latest'}
defaultValue={
(localSettings?.get('release_channel') as string | undefined) || 'latest'
}
onChange={(value) => {
if (!value) return;
localSettings?.set('release_channel', value);
@@ -48,7 +49,6 @@ export const UpdateSettings = () => {
},
});
}}
value={settings.releaseChannel}
/>
),
description: t('setting.releaseChannel', {
@@ -85,5 +85,5 @@ export const UpdateSettings = () => {
},
];
return <SettingsSection divider={utils?.isLinux()} options={updateOptions} />;
return <SettingsSection divider={true} options={updateOptions} />;
};
@@ -2,7 +2,6 @@ import isElectron from 'is-electron';
import { DiscordSettings } from '/@/renderer/features/settings/components/window/discord-settings';
import { PasswordSettings } from '/@/renderer/features/settings/components/window/password-settings';
import { UpdateSettings } from '/@/renderer/features/settings/components/window/update-settings';
import { WindowSettings } from '/@/renderer/features/settings/components/window/window-settings';
import { Stack } from '/@/shared/components/stack/stack';
@@ -13,7 +12,6 @@ export const WindowTab = () => {
<Stack gap="md">
<WindowSettings />
<DiscordSettings />
<UpdateSettings />
{utils?.isLinux() && (
<>
<PasswordSettings />
+8
View File
@@ -9,6 +9,7 @@ export interface AppSlice extends AppState {
actions: {
setAppStore: (data: Partial<AppSlice>) => void;
setPrivateMode: (enabled: boolean) => void;
setShowTimeRemaining: (enabled: boolean) => void;
setSideBar: (options: Partial<SidebarProps>) => void;
setTitleBar: (options: Partial<TitlebarProps>) => void;
};
@@ -19,6 +20,7 @@ export interface AppState {
isReorderingQueue: boolean;
platform: Platform;
privateMode: boolean;
showTimeRemaining: boolean;
sidebar: SidebarProps;
titlebar: TitlebarProps;
}
@@ -57,6 +59,11 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
state.privateMode = privateMode;
});
},
setShowTimeRemaining: (showTimeRemaining) => {
set((state) => {
state.showTimeRemaining = showTimeRemaining;
});
},
setSideBar: (options) => {
set((state) => {
state.sidebar = { ...state.sidebar, ...options };
@@ -89,6 +96,7 @@ export const useAppStore = createWithEqualityFn<AppSlice>()(
isReorderingQueue: false,
platform: Platform.WINDOWS,
privateMode: false,
showTimeRemaining: false,
sidebar: {
collapsed: false,
expanded: [],
+8
View File
@@ -202,6 +202,7 @@ export interface SettingsSlice extends SettingsState {
setTable: (type: TableType, data: DataTableProps) => void;
setTranscodingConfig: (config: TranscodingConfig) => void;
toggleContextMenuItem: (item: ContextMenuItemType) => void;
toggleMediaSession: () => void;
toggleSidebarCollapseShare: () => void;
};
}
@@ -298,6 +299,7 @@ export interface SettingsState {
audioDeviceId?: null | string;
crossfadeDuration: number;
crossfadeStyle: CrossfadeStyle;
mediaSession: boolean;
mpvExtraParameters: string[];
mpvProperties: MpvSettings;
muted: boolean;
@@ -491,6 +493,7 @@ const initialState: SettingsState = {
audioDeviceId: undefined,
crossfadeDuration: 5,
crossfadeStyle: CrossfadeStyle.EQUALPOWER,
mediaSession: false,
mpvExtraParameters: [],
mpvProperties: {
audioExclusiveMode: 'no',
@@ -752,6 +755,11 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
!state.general.disabledContextMenu[item];
});
},
toggleMediaSession: () => {
set((state) => {
state.playback.mediaSession = !state.playback.mediaSession;
});
},
toggleSidebarCollapseShare: () => {
set((state) => {
state.general.sidebarCollapseShared =