Compare commits

..

112 Commits

Author SHA1 Message Date
jeffvli 2912cd72ef fix build artifactName to include arch 2025-06-02 20:49:55 -07:00
jeffvli 9d31d952b7 update to v0.14.0 2025-06-02 20:34:30 -07:00
jeffvli 8f692b6f4d call remote shutdown on app quit 2025-06-02 20:32:05 -07:00
jeffvli 7562c619d2 fix mpv path save dialog (#930) (#940) 2025-06-02 20:17:55 -07:00
Kendall Garner 6b91ee4a25 fix album genre filter 2025-06-02 19:38:39 -07:00
Kendall Garner 9e689468f9 info for playlists, show id, fix playlist duration 2025-06-02 00:26:36 -07:00
Kendall Garner 1cda4363ef add lockfile 2025-05-28 22:00:26 -07:00
Kendall Garner b6941df7a7 fix editorconfig and downgrade react player back to lazy 2025-05-28 21:57:47 -07:00
jeffvli 608b322f9e remove maintenance notice 2025-05-28 14:49:32 -07:00
jeffvli daee582e92 fix web player playback
- add missing forwardRef on AudioPlayer component
- bump react-player to latest
2025-05-28 10:39:25 -07:00
jeffvli 6525a8a725 fix remote dev path 2025-05-27 18:28:52 -07:00
jeffvli ee1896c345 fix version number 2025-05-27 01:47:18 -07:00
Jeff ec625c2c65 Merge pull request #933 from jeffvli/experimental/vite
Migrate from Webpack to Vite
2025-05-26 18:10:48 -07:00
jeffvli 52e4423cf1 update readme with new pnpm scripts 2025-05-26 17:20:45 -07:00
jeffvli a9f7e808cb update the docker build for pnpm 2025-05-26 17:20:45 -07:00
jeffvli 9c26187b45 fixes for pnpm v10 2025-05-26 17:20:45 -07:00
jeffvli 90afa11f20 set custom userData path for dev 2025-05-26 17:20:45 -07:00
jeffvli 6463ea937b add vite build for web 2025-05-26 17:20:45 -07:00
jeffvli ac682428e6 various cleanup 2025-05-26 17:20:45 -07:00
jeffvli 48917547b2 add vite build for remote 2025-05-26 17:20:45 -07:00
jeffvli 74554d9725 use lowercase in package productName 2025-05-26 17:20:45 -07:00
jeffvli 0d42a6ea49 update workflows for new build 2025-05-26 17:20:45 -07:00
jeffvli faadff0211 migrate to pnpm 2025-05-26 17:20:44 -07:00
jeffvli 91715ebf7d change build dir 2025-05-26 17:20:28 -07:00
jeffvli 1808f160b4 clean up dependencies 2025-05-26 17:20:28 -07:00
jeffvli e10a92a5c2 add legacy-peer-deps default 2025-05-26 17:20:02 -07:00
jeffvli 930165d006 fix all imports for new structure 2025-05-26 17:20:02 -07:00
jeffvli 249eaf89f8 update linter rules 2025-05-26 17:18:56 -07:00
jeffvli bf1cddae9d remove unusued paths from tsconfig node 2025-05-26 17:18:56 -07:00
jeffvli 9db2e51d2d reorganize global types to shared directory 2025-05-26 17:18:56 -07:00
jeffvli 26c02e03c5 update package.json and electron builder 2025-05-26 17:18:56 -07:00
jeffvli 1cf587bc8f restructure files onto electron-vite boilerplate 2025-05-26 17:18:55 -07:00
Hosted Weblate 91ce2cd8a1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (662 of 662 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-05-26 11:11:24 +02:00
Hosted Weblate 4f61e82068 Translated using Weblate (Finnish)
Currently translated at 100.0% (659 of 659 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: jonoafi <joona@jonottaa.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fi/
Translation: feishin/Translation
2025-05-26 11:11:24 +02:00
Hosted Weblate 1e6673fabd Translated using Weblate (French)
Currently translated at 100.0% (662 of 662 strings)

Co-authored-by: KosmoMoustache <hosted.weblate.org@kosmo.ovh>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translation: feishin/Translation
2025-05-26 11:11:24 +02:00
Hosted Weblate 02951c92af Translated using Weblate (Spanish)
Currently translated at 100.0% (662 of 662 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (659 of 659 strings)

Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translation: feishin/Translation
2025-05-26 11:11:24 +02:00
Hosted Weblate 05f8fb3114 Translated using Weblate (Czech)
Currently translated at 100.0% (662 of 662 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (659 of 659 strings)

Co-authored-by: Fjuro <git@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translation: feishin/Translation
2025-05-26 11:11:24 +02:00
jeffvli 169da10c1b fix release version 2025-05-26 02:11:14 -07:00
jeffvli 5a79fee77e use missing UTC transform on navidrome dates (#928) 2025-05-26 02:08:08 -07:00
jeffvli 7ef80f14b0 fix casing on filtered list route titels (#929) 2025-05-26 01:58:16 -07:00
jeffvli 36cc37e39f update to v0.13.0 2025-05-26 01:47:13 -07:00
Kendall Garner d4e7c6bd18 fix copypasta 2025-05-20 08:40:24 -07:00
Kendall Garner 90f79b4ae7 add multiselect with invalid data handling to jellyfin album 2025-05-18 18:27:17 -07:00
Kendall Garner cf74625bfc warn if a value in select no longer exists 2025-05-18 10:59:45 -07:00
Kendall Garner f068d6e4b8 actually add type to query key 2025-05-18 09:29:13 -07:00
Kendall Garner e1aa8d74f3 Tag filter support
- Jellyfin: Uses `/items/filters` to get list of boolean tags. Notably, does not use this same filter for genres. Separate filter for song/album
- Navidrome: Uses `/api/tags`, which appears to be album-level as multiple independent selects. Same filter for song/album
2025-05-18 09:23:52 -07:00
Kendall Garner b0d86ee5c9 Support tags, and better participants for servers
- Parses `tags` for Navidrome (mapping string: string[])
- Parses `Tags` (and fetches for it) for Jellyfin (map a string to empty, and display as a bool)
- Clean parsing of participants for Navidrome/Subsonic
- Only show `People` for Jellyfin, not clickable
2025-05-17 21:35:58 -07:00
Kendall Garner 89e27ec6ff remove console.log 2025-05-16 11:50:26 -07:00
Kendall Garner 39c714a137 navidrome cover art workaround 2025-05-15 19:10:15 -07:00
Kendall Garner a8fb7ff11e fullscreen header image on click 2025-05-14 08:25:02 -07:00
jeffvli 9b95f47a91 update to v0.12.7 2025-05-12 18:27:37 -07:00
Hosted Weblate 2267e9bc9d Translated using Weblate (Czech)
Currently translated at 100.0% (657 of 657 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translation: feishin/Translation
2025-05-13 03:24:48 +02:00
Kendall Garner 089311c673 add migrate from v8 (#925) 2025-05-12 18:24:42 -07:00
Kendall Garner 773f349b66 don't show song count if not present for home carousel 2025-05-09 19:08:36 -07:00
Kendall Garner 3980c8ea97 save the package-logk.json changes as well 2025-05-08 08:23:58 -07:00
Kendall Garner 257a5ceef0 force xmljs to 0.5.0 2025-05-08 08:08:31 -07:00
jeffvli fb022891fe update to v0.12.6 2025-05-08 00:44:13 -07:00
Kendall Garner 5d9906b8f2 include album artist song/album count for jellyfin, and disable playing/adding playinsts for artists with no albums 2025-05-07 21:16:47 -07:00
jeffvli 6f7cb468b2 fix regression on subsonic album artist play 2025-05-07 20:59:16 -07:00
Kendall Garner 076693e969 Merge branch 'development' of github.com:jeffvli/feishin into development 2025-05-07 20:01:04 -07:00
Kendall Garner 781d8055b5 minor artist count fixes 2025-05-07 19:53:23 -07:00
jeffvli 960bb5c660 fix navigation to detail page on artist list 2025-05-07 19:40:54 -07:00
jeffvli 42bb2bf66f fix regression on album artist play button 2025-05-07 19:25:25 -07:00
jeffvli f03d88cd8c batch jellyfin song list requests when fetching by albumId (#922) 2025-05-07 01:42:32 -07:00
jeffvli 58f6535ba6 revert electron to gtk 3 (#923) 2025-05-07 01:28:54 -07:00
jeffvli 9a59ce3613 fix casing on artist albums title 2025-05-07 01:15:00 -07:00
jeffvli 6f37e13611 additional fix to sticky table header to account for native 2025-05-06 18:54:10 -07:00
jeffvli 3c494f1c72 update to v0.12.5 2025-05-06 18:27:02 -07:00
jeffvli ec0e7256cb fix set rating click on context menu (#900)
- star rating icon overriddes the pointer click event
2025-05-06 18:23:14 -07:00
jeffvli 262203b62d handle arrow keys in global hotkey (#913) 2025-05-06 15:19:34 -07:00
jeffvli 41bdc1a7b7 disable toast overlay blocking playerbar (#888) 2025-05-06 15:00:43 -07:00
jeffvli d35e73792f fix item playback buttons on search list (#885) 2025-05-06 14:50:59 -07:00
jeffvli 4a3604b1a8 handle playback on new artist list 2025-05-06 14:43:42 -07:00
jeffvli b9611589ba fix casing on some translation strings 2025-05-06 13:23:29 -07:00
jeffvli 12c517f0ff update to v0.12.4 2025-05-06 03:40:42 -07:00
Hosted Weblate 01884ab656 Translated using Weblate (Finnish)
Currently translated at 100.0% (657 of 657 strings)

Translated using Weblate (Finnish)

Currently translated at 77.3% (508 of 657 strings)

Translated using Weblate (Finnish)

Currently translated at 69.2% (455 of 657 strings)

Translated using Weblate (Finnish)

Currently translated at 64.9% (427 of 657 strings)

Translated using Weblate (Finnish)

Currently translated at 64.9% (427 of 657 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Lunya <Lunya2@protonmail.com>
Co-authored-by: Ricky Tigg <ricky.tigg@gmail.com>
Co-authored-by: jonoafi <joona@jonottaa.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fi/
Translation: feishin/Translation
2025-05-06 12:28:46 +02:00
Hosted Weblate 0ba830d5d7 Translated using Weblate (Persian)
Currently translated at 74.8% (492 of 657 strings)

Co-authored-by: Hadi <xhopeter@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fa/
Translation: feishin/Translation
2025-05-06 12:28:46 +02:00
Hosted Weblate b08a0d178c Translated using Weblate (Spanish)
Currently translated at 100.0% (657 of 657 strings)

Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translation: feishin/Translation
2025-05-06 12:28:46 +02:00
Hosted Weblate 9afa64b537 Translated using Weblate (Polish)
Currently translated at 100.0% (657 of 657 strings)

Translated using Weblate (Polish)

Currently translated at 93.4% (614 of 657 strings)

Translated using Weblate (Polish)

Currently translated at 92.2% (606 of 657 strings)

Co-authored-by: Adam Perkowski <adas1per@protonmail.com>
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-05-06 12:28:46 +02:00
Hosted Weblate be8bc74ab5 Translated using Weblate (Russian)
Currently translated at 95.7% (629 of 657 strings)

Translated using Weblate (Russian)

Currently translated at 95.2% (626 of 657 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Rachel Podya <homicide@disroot.org>
Co-authored-by: zv0r <mount.dev.brain@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ru/
Translation: feishin/Translation
2025-05-06 12:28:46 +02:00
Hosted Weblate 2f4e228fa1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 61.1% (402 of 657 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Joao <joaohirasawaa@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pt_BR/
Translation: feishin/Translation
2025-05-06 12:28:46 +02:00
jeffvli 35ee7e4606 fix position of sticky table header when titlebar present 2025-05-06 03:27:30 -07:00
jeffvli b265f2817b bump to electron 36 (#921) 2025-05-06 02:58:22 -07:00
Kendall Garner 48af447838 fix entities version 2025-04-25 10:35:52 -07:00
Kendall Garner 397df0c9c6 npm update 2025-04-25 09:53:15 -07:00
Kendall Garner 68759a2613 artists fixes 2025-04-25 09:30:17 -07:00
Kendall Garner c376293f2f always add all artists to subsonic 2025-04-23 23:33:58 -07:00
Kendall Garner e84a4b20bc add artist list 2025-04-23 23:27:06 -07:00
Kendall Garner 14e9f6ac41 bump electronVersion 2025-04-22 07:41:56 -07:00
Kendall Garner 0115ecb59b fix repeat one/repeat all scrobble 2025-04-20 10:54:44 -07:00
Kendall Garner 1555b827ee fix: normalize artists with no album count 2025-03-10 14:20:19 -07:00
Kendall Garner 41c2a7da69 make artist stats for navidrome api sane 2025-03-09 19:21:16 -07:00
jeffvli 592376316f Update to v0.12.3 2025-03-09 17:06:40 -07:00
Kendall Garner c6d7dc0b32 prepare bfr changes (#882)
* prepare bfr changes

* contributors to subsonic/navidrome

* show performer roles

* Add BFR smart playlist fields

* Fix upload-artifact action to handle v4

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
2025-03-09 16:55:27 -07:00
Kendall Garner 571aacbaa0 Merge branch 'development' of github.com:jeffvli/feishin into development 2025-02-27 16:43:21 -08:00
Kendall Garner ef194424e3 support css variables in sanitize 2025-02-27 16:42:02 -08:00
jeffvli 233c24cea6 Update to v0.12.2 2025-01-24 23:19:55 -08:00
jeffvli b48b0b0d11 Fix page header overlay on fullscreen player (#867) 2025-01-24 17:37:58 -08:00
jeffvli 65fe42d30c Add new languages to config 2025-01-24 16:47:49 -08:00
jeffvli 90ae7130f6 Fix invalid args on nd normalize 2025-01-24 16:44:02 -08:00
jeffvli d866dd211a Bump electron from v31 -> v33 2025-01-24 16:44:02 -08:00
Hosted Weblate 81446561e0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 47.4% (312 of 657 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 41.0% (270 of 657 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 30.2% (199 of 657 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 20.2% (133 of 657 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: klodrik <klodrik@zoominn.no>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nb_NO/
Translation: feishin/Translation
2025-01-15 22:35:53 +01:00
Hosted Weblate 4ef7213cf5 Translated using Weblate (Finnish)
Currently translated at 59.8% (393 of 657 strings)

Translated using Weblate (Finnish)

Currently translated at 31.6% (208 of 657 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Lauri Koo <late91@gmail.com>
Co-authored-by: jonoafi <joona@jonottaa.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fi/
Translation: feishin/Translation
2025-01-15 22:35:53 +01:00
Hosted Weblate b639d7bfab Translated using Weblate (Persian)
Currently translated at 64.6% (425 of 657 strings)

Translated using Weblate (Persian)

Currently translated at 62.7% (412 of 657 strings)

Translated using Weblate (Persian)

Currently translated at 39.2% (258 of 657 strings)

Co-authored-by: Hadi <xhopeter@proton.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fa/
Translation: feishin/Translation
2025-01-15 22:35:53 +01:00
Hosted Weblate 35e0af5164 Translated using Weblate (French)
Currently translated at 100.0% (657 of 657 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-01-15 22:35:53 +01:00
Hosted Weblate 86cca7fca5 Translated using Weblate (Indonesian)
Currently translated at 100.0% (657 of 657 strings)

Translated using Weblate (Indonesian)

Currently translated at 6.0% (40 of 657 strings)

Added translation using Weblate (Indonesian)

Co-authored-by: Fadilah Riczky <friczky@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/id/
Translation: feishin/Translation
2025-01-15 22:35:53 +01:00
Hosted Weblate 132ebb6b52 Translated using Weblate (Hungarian)
Currently translated at 31.6% (208 of 657 strings)

Added translation using Weblate (Hungarian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: WilliamNT <hwbendeguz@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/hu/
Translation: feishin/Translation
2025-01-15 22:35:53 +01:00
Hosted Weblate e23906582a Translated using Weblate (Tamil)
Currently translated at 100.0% (657 of 657 strings)

Added translation using Weblate (Tamil)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ta/
Translation: feishin/Translation
2025-01-15 22:35:53 +01:00
jeffvli e57232f89c fix array parameter parsing for subsonic client 2024-12-31 05:56:34 -08:00
ももぴ f0978365d4 fix: fetch album art via Last.fm should use album artist name (#855) 2024-12-19 20:31:44 -08:00
Penelope Gwen / Pogmommy ae65922253 decoded plaintext credentials before params get re-encoded in ssApiClient (#862) 2024-12-19 20:31:07 -08:00
jeffvli ca58551b94 Make client param on SS/ND queries consistent 2024-12-19 17:15:07 -08:00
504 changed files with 24098 additions and 52462 deletions
+2 -5
View File
@@ -1,12 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
trim_trailing_whitespace = true
-7
View File
@@ -1,7 +0,0 @@
{
"rules": {
"no-console": "off",
"global-require": "off",
"import/no-dynamic-require": "off"
}
}
-64
View File
@@ -1,64 +0,0 @@
/**
* Base webpack config used across other specific configs
*/
import webpack from 'webpack';
import { dependencies as externals } from '../../release/app/package.json';
import webpackPaths from './webpack.paths';
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
const createStyledComponentsTransformer = require('typescript-plugin-styled-components').default;
const styledComponentsTransformer = createStyledComponentsTransformer();
const configuration: webpack.Configuration = {
externals: [...Object.keys(externals || {})],
module: {
rules: [
{
exclude: /node_modules/,
test: /\.[jt]sx?$/,
use: {
loader: 'ts-loader',
options: {
// Remove this line to enable type checking in webpack builds
transpileOnly: true,
getCustomTransformers: () => ({ before: [styledComponentsTransformer] }),
},
},
},
],
},
output: {
// https://github.com/webpack/webpack/issues/1114
library: {
type: 'commonjs2',
},
path: webpackPaths.srcPath,
},
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
}),
],
/**
* Determine the array of extensions that should be used to resolve modules.
*/
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
fallback: {
child_process: false,
},
plugins: [new TsconfigPathsPlugin({ baseUrl: webpackPaths.srcPath })],
modules: [webpackPaths.srcPath, 'node_modules'],
},
stats: 'errors-only',
};
export default configuration;
-3
View File
@@ -1,3 +0,0 @@
/* eslint import/no-unresolved: off, import/no-self-import: off */
module.exports = require('./webpack.config.renderer.dev').default;
-84
View File
@@ -1,84 +0,0 @@
/**
* Webpack config for production electron main process
*/
import path from 'path';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import deleteSourceMaps from '../scripts/delete-source-maps';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
checkNodeEnv('production');
deleteSourceMaps();
const devtoolsConfig =
process.env.DEBUG_PROD === 'true'
? {
devtool: 'source-map',
}
: {};
const configuration: webpack.Configuration = {
...devtoolsConfig,
mode: 'production',
target: 'electron-main',
entry: {
main: path.join(webpackPaths.srcMainPath, 'main.ts'),
preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
},
output: {
path: webpackPaths.distMainPath,
filename: '[name].js',
},
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
START_MINIMIZED: false,
}),
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false,
},
};
export default merge(baseConfig, configuration);
@@ -1,70 +0,0 @@
import path from 'path';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: 'electron-preload',
entry: path.join(webpackPaths.srcMainPath, 'preload.ts'),
output: {
path: webpackPaths.dllPath,
filename: 'preload.js',
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overriden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
}),
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false,
},
watch: true,
};
export default merge(baseConfig, configuration);
-127
View File
@@ -1,127 +0,0 @@
import 'webpack-dev-server';
import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
const { version } = require('../../package.json');
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: ['web'],
entry: {
remote: path.join(webpackPaths.srcRemotePath, 'index.tsx'),
worker: path.join(webpackPaths.srcRemotePath, 'service-worker.ts'),
},
output: {
path: webpackPaths.dllPath,
publicPath: '/',
filename: '[name].js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?css$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overriden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
}),
new HtmlWebpackPlugin({
filename: path.join('index.html'),
template: path.join(webpackPaths.srcRemotePath, 'index.ejs'),
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: true,
env: process.env.NODE_ENV,
isDevelopment: process.env.NODE_ENV !== 'production',
nodeModules: webpackPaths.appNodeModulesPath,
templateParameters: {
version,
prod: false,
},
}),
],
node: {
__dirname: false,
__filename: false,
},
watch: true,
};
export default merge(baseConfig, configuration);
-142
View File
@@ -1,142 +0,0 @@
/**
* Build config for electron renderer process
*/
import path from 'path';
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import deleteSourceMaps from '../scripts/delete-source-maps';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
const { version } = require('../../package.json');
checkNodeEnv('production');
deleteSourceMaps();
const devtoolsConfig =
process.env.DEBUG_PROD === 'true'
? {
devtool: 'source-map',
}
: {};
const configuration: webpack.Configuration = {
...devtoolsConfig,
mode: 'production',
target: ['web'],
entry: {
remote: path.join(webpackPaths.srcRemotePath, 'index.tsx'),
worker: path.join(webpackPaths.srcRemotePath, 'service-worker.ts'),
},
output: {
path: webpackPaths.distRemotePath,
publicPath: './',
filename: '[name].js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?(a|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
new CssMinimizerPlugin(),
],
},
plugins: [
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
}),
new MiniCssExtractPlugin({
filename: 'remote.css',
}),
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(webpackPaths.srcRemotePath, 'index.ejs'),
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: true,
env: process.env.NODE_ENV,
isDevelopment: process.env.NODE_ENV !== 'production',
templateParameters: {
version,
prod: true,
},
}),
],
};
export default merge(baseConfig, configuration);
@@ -1,79 +0,0 @@
/**
* Builds the DLL for development electron renderer process
*/
import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import { dependencies } from '../../package.json';
import checkNodeEnv from '../scripts/check-node-env';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
checkNodeEnv('development');
const dist = webpackPaths.dllPath;
const configuration: webpack.Configuration = {
context: webpackPaths.rootPath,
devtool: 'eval',
mode: 'development',
target: 'electron-renderer',
externals: ['fsevents', 'crypto-browserify'],
/**
* Use `module` from `webpack.config.renderer.dev.js`
*/
module: require('./webpack.config.renderer.dev').default.module,
entry: {
renderer: Object.keys(dependencies || {}),
},
output: {
path: dist,
filename: '[name].dev.dll.js',
library: {
name: 'renderer',
type: 'var',
},
},
plugins: [
new webpack.DllPlugin({
path: path.join(dist, '[name].json'),
name: '[name]',
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
options: {
context: webpackPaths.srcPath,
output: {
path: webpackPaths.dllPath,
},
},
}),
],
};
export default merge(baseConfig, configuration);
-201
View File
@@ -1,201 +0,0 @@
import 'webpack-dev-server';
import { execSync, spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import chalk from 'chalk';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const port = process.env.PORT || 4343;
const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const requiredByDLLConfig = module.parent!.filename.includes('webpack.config.renderer.dev.dll');
/**
* Warn if the DLL is not built
*/
if (!requiredByDLLConfig && !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest))) {
console.log(
chalk.black.bgYellow.bold(
'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"',
),
);
execSync('npm run postinstall');
}
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: ['web', 'electron-renderer'],
entry: [
`webpack-dev-server/client?http://localhost:${port}/dist`,
'webpack/hot/only-dev-server',
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
],
output: {
path: webpackPaths.distRendererPath,
publicPath: '/',
filename: 'renderer.dev.js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
exportLocalsConvention: 'camelCaseOnly',
},
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?css$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
plugins: [
...(requiredByDLLConfig
? []
: [
new webpack.DllReferencePlugin({
context: webpackPaths.dllPath,
manifest: require(manifest),
sourceType: 'var',
}),
]),
new webpack.NoEmitOnErrorsPlugin(),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overriden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
}),
new ReactRefreshWebpackPlugin(),
new HtmlWebpackPlugin({
filename: path.join('index.html'),
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
env: process.env.NODE_ENV,
isDevelopment: process.env.NODE_ENV !== 'production',
nodeModules: webpackPaths.appNodeModulesPath,
templateParameters: {
web: false,
},
}),
],
node: {
__dirname: false,
__filename: false,
},
devServer: {
port,
compress: true,
hot: true,
headers: { 'Access-Control-Allow-Origin': '*' },
static: {
publicPath: '/',
},
historyApiFallback: {
verbose: true,
},
setupMiddlewares(middlewares) {
console.log('Starting preload.js builder...');
const preloadProcess = spawn('npm', ['run', 'start:preload'], {
shell: true,
stdio: 'inherit',
})
.on('close', (code: number) => process.exit(code!))
.on('error', (spawnError) => console.error(spawnError));
console.log('Starting remote.js builder...');
const remoteProcess = spawn('npm', ['run', 'start:remote'], {
shell: true,
stdio: 'inherit',
})
.on('close', (code: number) => process.exit(code!))
.on('error', (spawnError) => console.error(spawnError));
console.log('Starting Main Process...');
spawn('npm', ['run', 'start:main'], {
shell: true,
stdio: 'inherit',
})
.on('close', (code: number) => {
preloadProcess.kill();
remoteProcess.kill();
process.exit(code!);
})
.on('error', (spawnError) => console.error(spawnError));
return middlewares;
},
},
};
export default merge(baseConfig, configuration);
@@ -1,137 +0,0 @@
/**
* Build config for electron renderer process
*/
import path from 'path';
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import deleteSourceMaps from '../scripts/delete-source-maps';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
checkNodeEnv('production');
deleteSourceMaps();
const devtoolsConfig =
process.env.DEBUG_PROD === 'true'
? {
devtool: 'source-map',
}
: {};
const configuration: webpack.Configuration = {
...devtoolsConfig,
mode: 'production',
target: ['web', 'electron-renderer'],
entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
output: {
path: webpackPaths.distRendererPath,
publicPath: './',
filename: 'renderer.js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
exportLocalsConvention: 'camelCaseOnly',
},
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?(a|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
new CssMinimizerPlugin(),
],
},
plugins: [
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
}),
new MiniCssExtractPlugin({
filename: 'style.css',
}),
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
isDevelopment: process.env.NODE_ENV !== 'production',
templateParameters: {
web: false,
},
}),
],
};
export default merge(baseConfig, configuration);
-147
View File
@@ -1,147 +0,0 @@
import 'webpack-dev-server';
import path from 'path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const port = process.env.PORT || 4343;
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: ['web', 'electron-renderer'],
entry: [
`webpack-dev-server/client?http://localhost:${port}/dist`,
'webpack/hot/only-dev-server',
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
],
output: {
path: webpackPaths.distRendererPath,
publicPath: '/',
filename: 'renderer.dev.js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
exportLocalsConvention: 'camelCaseOnly',
},
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?css$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overriden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
}),
new ReactRefreshWebpackPlugin(),
new HtmlWebpackPlugin({
filename: path.join('index.html'),
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
env: process.env.NODE_ENV,
isDevelopment: process.env.NODE_ENV !== 'production',
nodeModules: webpackPaths.appNodeModulesPath,
templateParameters: {
web: false, // with hot reload, we don't have NGINX injecting variables
},
}),
],
node: {
__dirname: false,
__filename: false,
},
devServer: {
port,
compress: true,
hot: true,
headers: { 'Access-Control-Allow-Origin': '*' },
static: {
publicPath: '/',
},
historyApiFallback: {
verbose: true,
},
setupMiddlewares(middlewares) {
return middlewares;
},
},
};
export default merge(baseConfig, configuration);
-138
View File
@@ -1,138 +0,0 @@
/**
* Build config for electron renderer process
*/
import path from 'path';
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import deleteSourceMaps from '../scripts/delete-source-maps';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
checkNodeEnv('production');
deleteSourceMaps();
const devtoolsConfig =
process.env.DEBUG_PROD === 'true'
? {
devtool: 'source-map',
}
: {};
const configuration: webpack.Configuration = {
...devtoolsConfig,
mode: 'production',
target: ['web'],
entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
output: {
path: webpackPaths.distWebPath,
publicPath: 'auto',
filename: 'renderer.js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]--[hash:base64:5]',
exportLocalsConvention: 'camelCaseOnly',
},
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?(a|c)ss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
new CssMinimizerPlugin(),
],
},
plugins: [
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
}),
new MiniCssExtractPlugin({
filename: 'style.css',
}),
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
isDevelopment: process.env.NODE_ENV !== 'production',
templateParameters: {
web: true,
},
}),
],
};
export default merge(baseConfig, configuration);
-46
View File
@@ -1,46 +0,0 @@
const path = require('path');
const rootPath = path.join(__dirname, '../..');
const dllPath = path.join(__dirname, '../dll');
const srcPath = path.join(rootPath, 'src');
const assetsPath = path.join(rootPath, 'assets');
const srcMainPath = path.join(srcPath, 'main');
const srcRemotePath = path.join(srcPath, 'remote');
const srcRendererPath = path.join(srcPath, 'renderer');
const releasePath = path.join(rootPath, 'release');
const appPath = path.join(releasePath, 'app');
const appPackagePath = path.join(appPath, 'package.json');
const appNodeModulesPath = path.join(appPath, 'node_modules');
const srcNodeModulesPath = path.join(srcPath, 'node_modules');
const distPath = path.join(appPath, 'dist');
const distMainPath = path.join(distPath, 'main');
const distRemotePath = path.join(distPath, 'remote');
const distRendererPath = path.join(distPath, 'renderer');
const distWebPath = path.join(distPath, 'web');
const buildPath = path.join(releasePath, 'build');
export default {
assetsPath,
rootPath,
dllPath,
srcPath,
srcMainPath,
srcRemotePath,
srcRendererPath,
releasePath,
appPath,
appPackagePath,
appNodeModulesPath,
srcNodeModulesPath,
distPath,
distMainPath,
distRemotePath,
distRendererPath,
distWebPath,
buildPath,
};
-1
View File
@@ -1 +0,0 @@
export default 'test-file-stub';
-8
View File
@@ -1,8 +0,0 @@
{
"rules": {
"no-console": "off",
"global-require": "off",
"import/no-dynamic-require": "off",
"import/no-extraneous-dependencies": "off"
}
}
-33
View File
@@ -1,33 +0,0 @@
// Check if the renderer and main bundles are built
import path from 'path';
import chalk from 'chalk';
import fs from 'fs';
import webpackPaths from '../configs/webpack.paths';
const mainPath = path.join(webpackPaths.distMainPath, 'main.js');
const remotePath = path.join(webpackPaths.distMainPath, 'remote.js');
const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');
if (!fs.existsSync(mainPath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The main process is not built yet. Build it by running "npm run build:main"',
),
);
}
if (!fs.existsSync(remotePath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The remote process is not built yet. Build it by running "npm run build:remote"',
),
);
}
if (!fs.existsSync(rendererPath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The renderer process is not built yet. Build it by running "npm run build:renderer"',
),
);
}
-54
View File
@@ -1,54 +0,0 @@
import fs from 'fs';
import chalk from 'chalk';
import { execSync } from 'child_process';
import { dependencies } from '../../package.json';
if (dependencies) {
const dependenciesKeys = Object.keys(dependencies);
const nativeDeps = fs
.readdirSync('node_modules')
.filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
if (nativeDeps.length === 0) {
process.exit(0);
}
try {
// Find the reason for why the dependency is installed. If it is installed
// because of a devDependency then that is okay. Warn when it is installed
// because of a dependency
const { dependencies: dependenciesObject } = JSON.parse(
execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
);
const rootDependencies = Object.keys(dependenciesObject);
const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
dependenciesKeys.includes(rootDependency)
);
if (filteredRootDependencies.length > 0) {
const plural = filteredRootDependencies.length > 1;
console.log(`
${chalk.whiteBright.bgYellow.bold(
'Webpack does not work with native dependencies.'
)}
${chalk.bold(filteredRootDependencies.join(', '))} ${
plural ? 'are native dependencies' : 'is a native dependency'
} and should be installed inside of the "./release/app" folder.
First, uninstall the packages from "./package.json":
${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
${chalk.bold(
'Then, instead of installing the package to the root "./package.json":'
)}
${chalk.whiteBright.bgRed.bold('npm install your-package')}
${chalk.bold('Install the package to "./release/app/package.json"')}
${chalk.whiteBright.bgGreen.bold(
'cd ./release/app && npm install your-package'
)}
Read more about native dependencies at:
${chalk.bold(
'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure'
)}
`);
process.exit(1);
}
} catch (e) {
console.log('Native dependencies could not be checked');
}
}
-16
View File
@@ -1,16 +0,0 @@
import chalk from 'chalk';
export default function checkNodeEnv(expectedEnv) {
if (!expectedEnv) {
throw new Error('"expectedEnv" not set');
}
if (process.env.NODE_ENV !== expectedEnv) {
console.log(
chalk.whiteBright.bgRed.bold(
`"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
)
);
process.exit(2);
}
}
-16
View File
@@ -1,16 +0,0 @@
import chalk from 'chalk';
import detectPort from 'detect-port';
const port = process.env.PORT || '4343';
detectPort(port, (err, availablePort) => {
if (port !== String(availablePort)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
`Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`
)
);
} else {
process.exit(0);
}
});
-17
View File
@@ -1,17 +0,0 @@
import rimraf from 'rimraf';
import process from 'process';
import webpackPaths from '../configs/webpack.paths';
const args = process.argv.slice(2);
const commandMap = {
dist: webpackPaths.distPath,
release: webpackPaths.releasePath,
dll: webpackPaths.dllPath,
};
args.forEach((x) => {
const pathToRemove = commandMap[x];
if (pathToRemove !== undefined) {
rimraf.sync(pathToRemove);
}
});
-9
View File
@@ -1,9 +0,0 @@
import path from 'path';
import rimraf from 'rimraf';
import webpackPaths from '../configs/webpack.paths';
export default function deleteSourceMaps() {
rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map'));
rimraf.sync(path.join(webpackPaths.distRemotePath, '*.js.map'));
rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map'));
}
-20
View File
@@ -1,20 +0,0 @@
import { execSync } from 'child_process';
import fs from 'fs';
import { dependencies } from '../../release/app/package.json';
import webpackPaths from '../configs/webpack.paths';
if (
Object.keys(dependencies || {}).length > 0 &&
fs.existsSync(webpackPaths.appNodeModulesPath)
) {
const electronRebuildCmd =
'../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .';
const cmd =
process.platform === 'win32'
? electronRebuildCmd.replace(/\//g, '\\')
: electronRebuildCmd;
execSync(cmd, {
cwd: webpackPaths.appPath,
stdio: 'inherit',
});
}
-9
View File
@@ -1,9 +0,0 @@
import fs from 'fs';
import webpackPaths from '../configs/webpack.paths';
const { srcNodeModulesPath } = webpackPaths;
const { appNodeModulesPath } = webpackPaths;
if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
}
-30
View File
@@ -1,30 +0,0 @@
const { notarize } = require('electron-notarize');
const { build } = require('../../package.json');
exports.default = async function notarizeMacos(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
return;
}
if (process.env.CI !== 'true') {
console.warn('Skipping notarizing step. Packaging is not running in CI');
return;
}
if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) {
console.warn(
'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set'
);
return;
}
const appName = context.packager.appInfo.productFilename;
await notarize({
appBundleId: build.appId,
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASS,
});
};
-34
View File
@@ -1,34 +0,0 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Coverage directory used by tools like istanbul
coverage
.eslintcache
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# OSX
.DS_Store
src/i18n
release/app/dist
release/build
.erb/dll
.idea
npm-debug.log.*
*.css.d.ts
*.sass.d.ts
*.scss.d.ts
# eslint ignores hidden directories by default:
# https://github.com/eslint/eslint/issues/8429
!.erb
-97
View File
@@ -1,97 +0,0 @@
module.exports = {
extends: ['erb', 'plugin:typescript-sort-keys/recommended'],
ignorePatterns: ['.erb/*', 'server'],
parser: '@typescript-eslint/parser',
parserOptions: {
createDefaultProgram: true,
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
project: './tsconfig.json',
sourceType: 'module',
tsconfigRootDir: './',
},
plugins: ['@typescript-eslint', 'import', 'sort-keys-fix'],
rules: {
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-shadow': ['off'],
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/no-use-before-define': ['error'],
'default-case': 'off',
'import/extensions': 'off',
'import/no-absolute-path': 'off',
// A temporary hack related to IDE not resolving correct package.json
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 'error',
'import/order': [
'error',
{
alphabetize: {
caseInsensitive: true,
order: 'asc',
},
groups: ['builtin', 'external', 'internal', ['parent', 'sibling']],
'newlines-between': 'never',
pathGroups: [
{
group: 'external',
pattern: 'react',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react'],
},
],
'import/prefer-default-export': 'off',
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/interactive-supports-focus': 'off',
'jsx-a11y/media-has-caption': 'off',
'no-await-in-loop': 'off',
'no-console': 'off',
'no-nested-ternary': 'off',
'no-restricted-syntax': 'off',
'no-shadow': 'off',
'no-underscore-dangle': 'off',
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'prefer-destructuring': 'off',
'react/function-component-definition': 'off',
'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
'react/jsx-no-useless-fragment': 'off',
'react/jsx-props-no-spreading': 'off',
'react/jsx-sort-props': [
'error',
{
callbacksLast: true,
ignoreCase: false,
noSortAlphabetically: false,
reservedFirst: true,
shorthandFirst: true,
shorthandLast: false,
},
],
'react/no-array-index-key': 'off',
'react/react-in-jsx-scope': 'off',
'react/require-default-props': 'off',
'sort-keys-fix/sort-keys-fix': 'warn',
},
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': {
// See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
webpack: {
config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),
},
},
},
};
+12 -16
View File
@@ -14,17 +14,15 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and NPM
uses: actions/setup-node@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
node-version: 18
cache: npm
version: 9
- name: Install dependencies
run: |
npm install --legacy-peer-deps
run: pnpm install
- name: Publish releases
- name: Build and Publish releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
@@ -33,12 +31,11 @@ jobs:
max_attempts: 3
retry_on: error
command: |
npm run postinstall
npm run build
npm exec electron-builder -- --publish always --linux
on_retry_command: npm cache clean --force
pnpm run package:linux
pnpm run publish:linux
on_retry_command: pnpm cache delete
- name: Publish releases (arm64)
- name: Build and Publish releases (arm64)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
@@ -47,7 +44,6 @@ jobs:
max_attempts: 3
retry_on: error
command: |
npm run postinstall
npm run build
npm exec electron-builder -- --publish always --arm64
on_retry_command: npm cache clean --force
pnpm run package:linux-arm64
pnpm run publish:linux-arm64
on_retry_command: pnpm cache delete
+8 -11
View File
@@ -14,17 +14,15 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and NPM
uses: actions/setup-node@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
node-version: 18
cache: npm
version: 9
- name: Install dependencies
run: |
npm install --legacy-peer-deps
run: pnpm install
- name: Publish releases
- name: Build and Publish releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
@@ -33,7 +31,6 @@ jobs:
max_attempts: 3
retry_on: error
command: |
npm run postinstall
npm run build
npm exec electron-builder -- --publish always --mac
on_retry_command: npm cache clean --force
pnpm run package:mac
pnpm run publish:mac
on_retry_command: pnpm cache delete
+56 -23
View File
@@ -11,50 +11,83 @@ jobs:
strategy:
matrix:
os: [macos-latest]
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Install Node and NPM
uses: actions/setup-node@v3
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
node-version: 18
cache: npm
version: 9
- name: Install dependencies
run: |
npm install --legacy-peer-deps
run: pnpm install
- name: Build releases
- name: Build for Windows
if: ${{ matrix.os == 'windows-latest' }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
npm run postinstall
npm run build
npm run package:pr
on_retry_command: npm cache clean --force
pnpm run package:win:pr
- uses: actions/upload-artifact@v3
- name: Build for Linux
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:linux:pr
- name: Build for MacOS
if: ${{ matrix.os == 'macos-latest' }}
uses: nick-invision/retry@v2.8.2
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
pnpm run package:mac:pr
- name: Zip Windows Binaries
if: ${{ matrix.os == 'windows-latest' }}
shell: pwsh
run: |
Compress-Archive -Path "dist/*.exe" -DestinationPath "dist/windows-binaries.zip" -Force
- name: Zip Linux Binaries
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
zip -r dist/linux-binaries.zip dist/*.{AppImage,deb,rpm}
- name: Zip MacOS Binaries
if: ${{ matrix.os == 'macos-latest' }}
run: |
zip -r dist/macos-binaries.zip dist/*.dmg
- name: Upload Windows Binaries
if: ${{ matrix.os == 'windows-latest' }}
uses: actions/upload-artifact@v4
with:
name: windows-binaries
path: |
release/build/*.exe
path: dist/windows-binaries.zip
- uses: actions/upload-artifact@v3
- name: Upload Linux Binaries
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v4
with:
name: linux-binaries
path: |
release/build/*.AppImage
release/build/*.deb
release/build/*.rpm
path: dist/linux-binaries.zip
- uses: actions/upload-artifact@v3
- name: Upload MacOS Binaries
if: ${{ matrix.os == 'macos-latest' }}
uses: actions/upload-artifact@v4
with:
name: macos-binaries
path: |
release/build/*.dmg
path: dist/macos-binaries.zip
+8 -11
View File
@@ -14,17 +14,15 @@ jobs:
- name: Checkout git repo
uses: actions/checkout@v1
- name: Install Node and NPM
uses: actions/setup-node@v1
- name: Install Node and PNPM
uses: pnpm/action-setup@v4
with:
node-version: 18
cache: npm
version: 9
- name: Install dependencies
run: |
npm install --legacy-peer-deps
run: pnpm install
- name: Publish releases
- name: Build and Publish releases
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: nick-invision/retry@v2.8.2
@@ -33,7 +31,6 @@ jobs:
max_attempts: 3
retry_on: error
command: |
npm run postinstall
npm run build
npm exec electron-builder -- --publish always --win
on_retry_command: npm cache clean --force
pnpm run package:win
pnpm run publish:win
on_retry_command: pnpm cache delete
+7 -15
View File
@@ -14,21 +14,13 @@ jobs:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js and NPM
uses: actions/setup-node@v2
- name: Install Node.js and PNPM
uses: pnpm/action-setup@v4
with:
node-version: 16
cache: npm
version: 9
- name: npm install
run: |
npm install --legacy-peer-deps
- name: Install dependencies
run: pnpm install
- name: npm test
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
npm run lint
npm run package
npm exec tsc
npm test
- name: Lint Files
run: pnpm run lint
+5 -29
View File
@@ -1,31 +1,7 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Coverage directory used by tools like istanbul
coverage
.eslintcache
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# OSX
dist
out
.DS_Store
release/app/dist
release/build
.erb/dll
.idea
npm-debug.log.*
*.css.d.ts
*.sass.d.ts
*.scss.d.ts
.env*
.eslintcache
*.log*
release
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+2
View File
@@ -0,0 +1,2 @@
legacy-peer-deps=true
only-built-dependencies=electron,esbuild
+6
View File
@@ -0,0 +1,6 @@
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
-22
View File
@@ -1,22 +0,0 @@
{
"printWidth": 100,
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"useTabs": false,
"overrides": [
{
"files": ["**/*.css", "**/*.scss", "**/*.html"],
"options": {
"singleQuote": true
}
}
],
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"proseWrap": "never",
"htmlWhitespaceSensitivity": "strict",
"endOfLine": "lf",
"singleAttributePerLine": true
}
+14
View File
@@ -0,0 +1,14 @@
singleQuote: true
semi: true
printWidth: 100
tabWidth: 4
trailingComma: all
useTabs: false
arrowParens: always
proseWrap: never
htmlWhitespaceSensitivity: strict
endOfLine: lf
singleAttributePerLine: true
bracketSpacing: true
plugins:
- prettier-plugin-packagejson
+1 -8
View File
@@ -1,10 +1,3 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"stylelint.vscode-stylelint",
"esbenp.prettier-vscode",
"clinyong.vscode-css-modules",
"Huuums.vscode-fast-folder-structure"
]
"recommendations": ["dbaeumer.vscode-eslint"]
}
+37 -26
View File
@@ -1,28 +1,39 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Electron: Main",
"type": "node",
"request": "launch",
"protocol": "inspector",
"runtimeExecutable": "npm",
"runtimeArgs": ["run start:main --inspect=5858 --remote-debugging-port=9223"],
"preLaunchTask": "Start Webpack Dev"
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"webRoot": "${workspaceFolder}",
"timeout": 15000
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": ["Electron: Main", "Electron: Renderer"]
}
]
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
},
"runtimeArgs": ["--sourcemap"],
"env": {
"REMOTE_DEBUGGING_PORT": "9222"
}
},
{
"name": "Debug Renderer Process",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}/src/renderer",
"timeout": 60000,
"presentation": {
"hidden": true
}
}
],
"compounds": [
{
"name": "Debug All",
"configurations": ["Debug Main Process", "Debug Renderer Process"],
"presentation": {
"order": 1
}
}
]
}
+13 -3
View File
@@ -1,4 +1,13 @@
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.associations": {
".eslintrc": "jsonc",
".prettierrc": "jsonc",
@@ -9,7 +18,7 @@
{ "directory": "./", "changeProcessCWD": true },
{ "directory": "./server", "changeProcessCWD": true }
],
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit",
@@ -33,14 +42,15 @@
"npm-debug.log.*": true,
"test/**/__snapshots__": true,
"package-lock.json": true,
"*.{css,sass,scss}.d.ts": true
"*.{css,sass,scss}.d.ts": true,
"out/**/*": true,
"dist/**/*": true
},
"i18n-ally.localesPaths": ["src/i18n", "src/i18n/locales"],
"typescript.tsdk": "node_modules\\typescript\\lib",
"typescript.preferences.importModuleSpecifier": "non-relative",
"stylelint.validate": ["css", "scss", "typescript", "typescriptreact"],
"typescript.updateImportsOnFileMove.enabled": "always",
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true,
"folderTemplates.structures": [
-25
View File
@@ -1,25 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"label": "Start Webpack Dev",
"script": "start:renderer",
"options": {
"cwd": "${workspaceFolder}"
},
"isBackground": true,
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": "____________"
},
"background": {
"activeOnStart": true,
"beginsPattern": "Compiling\\.\\.\\.$",
"endsPattern": "(Compiled successfully|Failed to compile)\\.$"
}
}
}
]
}
+6 -5
View File
@@ -2,13 +2,14 @@
FROM node:18-alpine as builder
WORKDIR /app
#Copy package.json first to cache node_modules
# Copy package.json first to cache node_modules
COPY package.json package-lock.json .
# Scripts include electron-specific dependencies, which we don't need
RUN npm install --legacy-peer-deps --ignore-scripts
#Copy code and build with cached modules
RUN pnpm install
# Copy code and build with cached modules
COPY . .
RUN npm run build:web
RUN pnpm run build:web
# --- Production stage
FROM nginx:alpine-slim
+46 -31
View File
@@ -29,25 +29,17 @@
---
## MAINTENANCE NOTICE
Feishin is currently undergoing a major rewrite. New feature requests will not be accepted. The rewrite is being actively developed at the [audioling](https://github.com/audioling/audioling) repository.
Follow the repository or join the discord/matrix server for updates.
---
Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
## Features
- [x] MPV player backend
- [x] Web player backend
- [x] Modern UI
- [x] Scrobble playback to your server
- [x] Smart playlist editor (Navidrome)
- [x] Synchronized and unsynchronized lyrics support
- [ ] [Request a feature](https://github.com/jeffvli/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
- [x] MPV player backend
- [x] Web player backend
- [x] Modern UI
- [x] Scrobble playback to your server
- [x] Smart playlist editor (Navidrome)
- [x] Synchronized and unsynchronized lyrics support
- [ ] [Request a feature](https://github.com/jeffvli/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
## Screenshots
@@ -109,8 +101,8 @@ services:
2. After restarting the app, you will be prompted to select a server. Click the `Open menu` button and select `Manage servers`. Click the `Add server` button in the popup and fill out all applicable details. You will need to enter the full URL to your server, including the protocol and port if applicable (e.g. `https://navidrome.my-server.com` or `http://192.168.0.1:4533`).
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
- **Linux users** - The default password store uses `libsecret`. `kwallet4/5/6` are also supported, but must be explicitly set in Settings > Window > Passwords/secret score.
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
- **Linux users** - The default password store uses `libsecret`. `kwallet4/5/6` are also supported, but must be explicitly set in Settings > Window > Passwords/secret score.
3. _Optional_ - If you want to host Feishin on a subpath (not `/`), then pass in the following environment variable: `PUBLIC_PATH=PATH`. For example, to host on `/feishin`, pass in `PUBLIC_PATH=/feishin`.
@@ -126,18 +118,18 @@ First thing to do is check that your MPV binary path is correct. Navigate to the
Feishin supports any music server that implements a [Navidrome](https://www.navidrome.org/) or [Jellyfin](https://jellyfin.org/) API. **Subsonic API is not currently supported**. This will likely be added in [later when the new Subsonic API is decided on](https://support.symfonium.app/t/subsonic-servers-participation/1233).
- [Navidrome](https://github.com/navidrome/navidrome)
- [Jellyfin](https://github.com/jellyfin/jellyfin)
- Subsonic-compatible servers
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
- [Ampache](https://ampache.org)
- [Astiga](https://asti.ga/)
- [Funkwhale](https://www.funkwhale.audio/)
- [Gonic](https://github.com/sentriz/gonic)
- [LMS](https://github.com/epoupon/lms)
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
- [Supysonic](https://github.com/spl0k/supysonic)
- More (?)
- [Navidrome](https://github.com/navidrome/navidrome)
- [Jellyfin](https://github.com/jellyfin/jellyfin)
- Subsonic-compatible servers
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
- [Ampache](https://ampache.org)
- [Astiga](https://asti.ga/)
- [Funkwhale](https://www.funkwhale.audio/)
- [Gonic](https://github.com/sentriz/gonic)
- [LMS](https://github.com/epoupon/lms)
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
- [Supysonic](https://github.com/spl0k/supysonic)
- More (?)
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
@@ -152,9 +144,32 @@ Ubunutu 24.04 specifically introduced breaking changes that affect how namespace
## Development
Built and tested using Node `v16.15.0`.
Built and tested using Node `v23.11.0`.
This project is built off of [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate) v4.6.0.
This project is built off of [electron-vite](https://github.com/alex8088/electron-vite)
- `pnpm run dev` - Start the development server
- `pnpm run dev:watch` - Start the development server in watch mode (for main / preload HMR)
- `pnpm run start` - Starts the app in production preview mode
- `pnpm run build` - Builds the app for desktop
- `pnpm run build:electron` - Build the electron app (main, preload, and renderer)
- `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 publish:linux` - Publish the project for Linux
- `pnpm run publish:linux-arm64` - Publish the project for Linux ARM64
- `pnpm run publish:mac` - Publish the project for Mac
- `pnpm run publish:win` - Publish the project for Windows
- `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
- `pnpm run lint` - Lint the project
- `pnpm run lint:fix` - Lint the project and fix linting errors
- `pnpm run i18next` - Generate i18n files
## Translation
+3 -1
View File
@@ -2,9 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>
+3
View File
@@ -0,0 +1,3 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: feishin-updater
+75
View File
@@ -0,0 +1,75 @@
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:
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 }]
deb:
depends:
- libgssapi_krb5.so.2
- libavahi-common.so.3
- libavahi-client.so.3
- libkrb5.so.3
- libkrb5support.so.0
- libkeyutils.so.1
- libcups.so.2
rpm:
depends:
- libgssapi_krb5.so.2
- libavahi-common.so.3
- libavahi-client.so.3
- libkrb5.so.3
- libkrb5support.so.0
- libkeyutils.so.1
- libcups.so.2
freebsd:
depends:
- libgssapi_krb5.so.2
- libavahi-common.so.3
- libavahi-client.so.3
- libkrb5.so.3
- libkrb5support.so.0
- libkeyutils.so.1
- libcups.so.2
linux:
target:
- AppImage
- tar.xz
category: AudioVideo;Audio;Player
icon: assets/icons/icon.png
npmRebuild: false
publish:
provider: github
owner: jeffvli
repo: feishin
+65
View File
@@ -0,0 +1,65 @@
import react from '@vitejs/plugin-react';
import { externalizeDepsPlugin, UserConfig } from 'electron-vite';
import { resolve } from 'path';
import conditionalImportPlugin from 'vite-plugin-conditional-import';
import dynamicImportPlugin from 'vite-plugin-dynamic-import';
import { ViteEjsPlugin } from 'vite-plugin-ejs';
const currentOSEnv = process.platform;
const config: UserConfig = {
main: {
build: {
rollupOptions: {
external: ['source-map-support'],
},
},
define: {
'import.meta.env.IS_LINUX': JSON.stringify(currentOSEnv === 'linux'),
'import.meta.env.IS_MACOS': JSON.stringify(currentOSEnv === 'darwin'),
'import.meta.env.IS_WIN': JSON.stringify(currentOSEnv === 'win32'),
},
plugins: [
externalizeDepsPlugin(),
dynamicImportPlugin(),
conditionalImportPlugin({
currentEnv: currentOSEnv,
envs: ['win32', 'linux', 'darwin'],
}),
],
resolve: {
alias: {
'/@/main': resolve('src/main'),
'/@/shared': resolve('src/shared'),
},
},
},
preload: {
plugins: [externalizeDepsPlugin()],
resolve: {
alias: {
'/@/preload': resolve('src/preload'),
'/@/shared': resolve('src/shared'),
},
},
},
renderer: {
css: {
modules: {
generateScopedName: '[name]__[local]__[hash:base64:5]',
localsConvention: 'camelCase',
},
},
plugins: [react(), ViteEjsPlugin({ web: false })],
resolve: {
alias: {
'/@/i18n': resolve('src/i18n'),
'/@/remote': resolve('src/remote'),
'/@/renderer': resolve('src/renderer'),
'/@/shared': resolve('src/shared'),
},
},
},
};
export default config;
+52
View File
@@ -0,0 +1,52 @@
import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier';
import tseslint from '@electron-toolkit/eslint-config-ts';
import perfectionist from 'eslint-plugin-perfectionist';
import eslintPluginReact from 'eslint-plugin-react';
import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
import eslintPluginReactRefresh from 'eslint-plugin-react-refresh';
export default tseslint.config(
{ ignores: ['**/node_modules', '**/dist', '**/out'] },
tseslint.configs.recommended,
perfectionist.configs['recommended-natural'],
eslintPluginReact.configs.flat.recommended,
eslintPluginReact.configs.flat['jsx-runtime'],
{
settings: {
react: {
version: 'detect',
},
},
},
{
files: ['**/*.{ts,tsx}'],
plugins: {
'react-hooks': eslintPluginReactHooks,
'react-refresh': eslintPluginReactRefresh,
},
rules: {
...eslintPluginReactHooks.configs.recommended.rules,
...eslintPluginReactRefresh.configs.vite.rules,
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-duplicate-enum-values': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'warn',
curly: ['error', 'all'],
indent: [
'error',
'tab',
{
offsetTernaryExpressions: true,
SwitchCase: 1,
},
],
'no-unused-vars': 'off',
'no-use-before-define': 'off',
quotes: ['error', 'single'],
'react-refresh/only-export-components': 'off',
'react/display-name': 'off',
semi: ['error', 'always'],
},
},
eslintConfigPrettier,
);
-27
View File
@@ -1,27 +0,0 @@
server {
listen 9180;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
location ${PUBLIC_PATH} {
alias /usr/share/nginx/html/;
try_files $uri $uri/ /index.html =404;
}
location ${PUBLIC_PATH}settings.js {
alias /etc/nginx/conf.d/settings.js;
}
location ${PUBLIC_PATH}/settings.js {
alias /etc/nginx/conf.d/settings.js;
}
}
-41090
View File
File diff suppressed because it is too large Load Diff
+107 -314
View File
@@ -1,294 +1,52 @@
{
"name": "feishin",
"productName": "Feishin",
"description": "Feishin music server",
"version": "0.12.1",
"scripts": {
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:remote\"",
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
"build:remote": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.remote.prod.ts",
"build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
"build:web": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.web.prod.ts",
"build:docker": "docker build -t jeffvli/feishin .",
"rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
"lint": "concurrently \"npm run lint:code\" \"npm run lint:styles\"",
"lint:code": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:styles": "npx stylelint **/*.tsx --fix",
"package": "node --import tsx ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never",
"package:pr": "node --import tsx ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --win --mac --linux",
"package:dev": "node --import tsx ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --dir",
"postinstall": "node --import tsx .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts",
"start": "node --import tsx ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
"start:main": "cross-env NODE_ENV=development NODE_OPTIONS=\"--import tsx\" electron -r ts-node/register/transpile-only ./src/main/main.ts",
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
"start:remote": "cross-env NODE_ENV=developemnt TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.remote.dev.ts",
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
"start:web": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.web.ts",
"test": "jest",
"prepare": "husky install",
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
"prod:buildserver": "pwsh -c \"./scripts/server-build.ps1\"",
"prod:publishserver": "pwsh -c \"./scripts/server-publish.ps1\""
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"cross-env NODE_ENV=development eslint --cache"
],
"*.json,.{eslintrc,prettierrc}": [
"prettier --ignore-path .eslintignore --parser json --write"
],
"*.{css,scss}": [
"prettier --ignore-path .eslintignore --single-quote --write"
],
"*.{html,md,yml}": [
"prettier --ignore-path .eslintignore --single-quote --write"
]
},
"build": {
"productName": "Feishin",
"appId": "org.jeffvli.feishin",
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
"asar": true,
"asarUnpack": "**\\*.{node,dll}",
"files": [
"dist",
"node_modules",
"package.json"
],
"afterSign": ".erb/scripts/notarize.js",
"electronVersion": "31.2.0",
"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
},
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"win": {
"target": [
"nsis",
"zip"
],
"icon": "assets/icons/icon.ico"
},
"deb": {
"depends": [
"libgssapi_krb5.so.2",
"libavahi-common.so.3",
"libavahi-client.so.3",
"libkrb5.so.3",
"libkrb5support.so.0",
"libkeyutils.so.1",
"libcups.so.2"
]
},
"rpm": {
"depends": [
"libgssapi_krb5.so.2",
"libavahi-common.so.3",
"libavahi-client.so.3",
"libkrb5.so.3",
"libkrb5support.so.0",
"libkeyutils.so.1",
"libcups.so.2"
]
},
"freebsd": {
"depends": [
"libgssapi_krb5.so.2",
"libavahi-common.so.3",
"libavahi-client.so.3",
"libkrb5.so.3",
"libkrb5support.so.0",
"libkeyutils.so.1",
"libcups.so.2"
]
},
"linux": {
"target": [
"AppImage",
"tar.xz"
],
"icon": "assets/icons/icon.png",
"category": "AudioVideo;Audio;Player"
},
"directories": {
"app": "release/app",
"buildResources": "assets",
"output": "release/build"
},
"extraResources": [
"./assets/**"
],
"publish": {
"provider": "github",
"owner": "jeffvli",
"repo": "feishin"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/jeffvli/feishin.git"
},
"author": {
"name": "jeffvli",
"url": "https://github.com/jeffvli/"
},
"contributors": [],
"license": "GPL-3.0",
"bugs": {
"url": "https://github.com/jeffvli/feishin/issues"
},
"version": "0.14.0",
"description": "A modern self-hosted music player.",
"keywords": [
"subsonic",
"navidrome",
"airsonic",
"jellyfin",
"react",
"electron"
],
"homepage": "https://github.com/jeffvli/feishin",
"jest": {
"testURL": "http://localhost/",
"testEnvironment": "jsdom",
"transform": {
"\\.(ts|tsx|js|jsx)$": "ts-jest"
},
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/.erb/mocks/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"js",
"jsx",
"ts",
"tsx",
"json"
],
"moduleDirectories": [
"node_modules",
"release/app/node_modules"
],
"testPathIgnorePatterns": [
"release/app/dist"
],
"setupFiles": [
"./.erb/scripts/check-build-exists.ts"
]
"bugs": {
"url": "https://github.com/jeffvli/feishin/issues"
},
"devDependencies": {
"@electron/rebuild": "^3.6.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
"@stylelint/postcss-css-in-js": "^0.38.0",
"@teamsupercell/typings-for-css-modules-loader": "^2.5.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.0",
"@types/dompurify": "^3.0.5",
"@types/electron-localshortcut": "^3.1.0",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.188",
"@types/md5": "^2.3.2",
"@types/node": "^17.0.23",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.8",
"@types/react-test-renderer": "^17.0.1",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6",
"@types/styled-components": "^5.1.26",
"@types/terser-webpack-plugin": "^5.0.4",
"@types/webpack-bundle-analyzer": "^4.4.1",
"@types/webpack-env": "^1.16.3",
"@typescript-eslint/eslint-plugin": "^5.47.0",
"@typescript-eslint/parser": "^5.47.0",
"browserslist-config-erb": "^0.0.3",
"chalk": "^4.1.2",
"concurrently": "^7.1.0",
"core-js": "^3.21.1",
"cross-env": "^7.0.3",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"detect-port": "^1.3.0",
"electron": "^31.2.0",
"electron-builder": "^24.13.3",
"electron-devtools-installer": "^3.2.0",
"electron-notarize": "^1.2.1",
"electronmon": "^2.0.2",
"eslint": "^8.30.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-erb": "^4.0.3",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-compat": "^4.2.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^26.1.3",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.4.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-typescript-sort-keys": "^2.1.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"i18next-parser": "^9.0.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.5.1",
"lint-staged": "^12.3.7",
"mini-css-extract-plugin": "^2.6.0",
"postcss-scss": "^4.0.4",
"postcss-styled-syntax": "^0.5.0",
"postcss-syntax": "^0.36.2",
"prettier": "^3.3.3",
"react-refresh": "^0.12.0",
"react-refresh-typescript": "^2.0.4",
"react-test-renderer": "^18.0.0",
"rimraf": "^3.0.2",
"sass": "^1.49.11",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"stylelint": "^15.10.3",
"stylelint-config-css-modules": "^4.3.0",
"stylelint-config-recess-order": "^4.3.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^4.0.0",
"stylelint-config-styled-components": "^0.1.1",
"terser-webpack-plugin": "^5.3.1",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.8",
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.0.0",
"tsx": "^4.16.2",
"typescript": "^5.2.2",
"typescript-plugin-styled-components": "^3.0.0",
"url-loader": "^4.1.1",
"webpack": "^5.94.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.8.0",
"webpack-merge": "^5.8.0"
"license": "GPL-3.0",
"author": {
"name": "jeffvli",
"url": "https://github.com/jeffvli/"
},
"main": "./out/main/index.js",
"scripts": {
"build": "pnpm run typecheck && pnpm run build:electron && pnpm run build:remote",
"build:electron": "electron-vite build",
"build:remote": "vite build --config remote.vite.config.ts",
"build:web": "vite build --config web.vite.config.ts",
"dev": "electron-vite dev",
"dev:watch": "electron-vite dev --watch",
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
"postinstall": "electron-builder install-app-deps",
"lint": "eslint --cache .",
"lint:fix": "eslint --cache --fix .",
"package": "pnpm run build && electron-builder",
"package:dev": "pnpm run build && electron-builder --dir",
"package:linux": "pnpm run build && electron-builder --linux",
"package:linux-arm64:pr": "pnpm run build && electron-builder --linux --arm64 --publish never",
"package:linux:pr": "pnpm run build && electron-builder --linux --publish never",
"package:mac": "pnpm run build && electron-builder --mac",
"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",
"start": "electron-vite preview",
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "^28.2.1",
@@ -296,14 +54,16 @@
"@ag-grid-community/infinite-row-model": "^28.2.1",
"@ag-grid-community/react": "^28.2.1",
"@ag-grid-community/styles": "^28.2.1",
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0",
"@emotion/react": "^11.10.4",
"@mantine/core": "^6.0.17",
"@mantine/dates": "^6.0.17",
"@mantine/form": "^6.0.17",
"@mantine/hooks": "^6.0.17",
"@mantine/modals": "^6.0.17",
"@mantine/notifications": "^6.0.17",
"@mantine/utils": "^6.0.17",
"@mantine/core": "^6.0.22",
"@mantine/dates": "^6.0.22",
"@mantine/form": "^6.0.22",
"@mantine/hooks": "^6.0.22",
"@mantine/modals": "^6.0.22",
"@mantine/notifications": "^6.0.22",
"@mantine/utils": "^6.0.22",
"@tanstack/react-query": "^4.32.1",
"@tanstack/react-query-devtools": "^4.32.1",
"@tanstack/react-query-persist-client": "^4.32.1",
@@ -312,6 +72,7 @@
"audiomotion-analyzer": "^4.5.0",
"auto-text-size": "^0.2.3",
"axios": "^1.6.0",
"cheerio": "^1.0.0",
"clsx": "^2.0.0",
"cmdk": "^0.2.0",
"dayjs": "^1.11.6",
@@ -320,12 +81,11 @@
"electron-localshortcut": "^3.2.1",
"electron-log": "^5.1.1",
"electron-store": "^8.1.0",
"electron-updater": "^6.3.1",
"electron-updater": "^6.3.9",
"fast-average-color": "^9.3.0",
"format-duration": "^2.0.0",
"framer-motion": "^11.0.0",
"fuse.js": "^6.6.2",
"history": "^5.3.0",
"i18next": "^21.10.0",
"idb-keyval": "^6.2.1",
"immer": "^9.0.21",
@@ -333,13 +93,12 @@
"lodash": "^4.17.21",
"md5": "^2.3.0",
"memoize-one": "^6.0.0",
"mpris-service": "^2.1.2",
"nanoid": "^3.3.3",
"net": "^1.0.2",
"node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f",
"overlayscrollbars": "^2.2.1",
"overlayscrollbars-react": "^0.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"overlayscrollbars": "^2.11.1",
"overlayscrollbars-react": "^0.5.6",
"qs": "^6.14.0",
"react-error-boundary": "^3.1.4",
"react-i18next": "^11.18.6",
"react-icons": "^4.10.1",
@@ -353,29 +112,63 @@
"semver": "^7.5.4",
"styled-components": "^6.0.8",
"swiper": "^9.3.1",
"ws": "^8.18.2",
"zod": "^3.22.3",
"zustand": "^4.3.9"
},
"resolutions": {
"styled-components": "^6"
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
"@electron-toolkit/eslint-config-ts": "^3.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@types/electron-localshortcut": "^3.1.0",
"@types/lodash": "^4.14.188",
"@types/md5": "^2.3.2",
"@types/node": "^22.14.1",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@types/react-window": "^1.8.5",
"@types/react-window-infinite-loader": "^1.0.6",
"@types/source-map-support": "^0.5.10",
"@types/styled-components": "^5.1.26",
"@types/ws": "^8.18.1",
"@vitejs/plugin-react": "^4.3.4",
"concurrently": "^7.1.0",
"cross-env": "^7.0.3",
"electron": "^35.1.5",
"electron-builder": "^26.0.12",
"electron-devtools-installer": "^3.2.0",
"electron-vite": "^3.1.0",
"eslint": "^9.24.0",
"eslint-plugin-perfectionist": "^4.13.0",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"i18next-parser": "^9.0.2",
"postcss-styled-syntax": "^0.5.0",
"prettier": "^3.5.3",
"prettier-plugin-packagejson": "^2.5.14",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass-embedded": "^1.89.0",
"stylelint": "^15.10.3",
"stylelint-config-css-modules": "^4.3.0",
"stylelint-config-recess-order": "^4.3.0",
"stylelint-config-standard": "^34.0.0",
"stylelint-config-standard-scss": "^4.0.0",
"stylelint-config-styled-components": "^0.1.1",
"typescript": "^5.8.3",
"typescript-plugin-styled-components": "^3.0.0",
"vite": "^6.3.5",
"vite-plugin-conditional-import": "^0.1.7",
"vite-plugin-dynamic-import": "^1.6.0",
"vite-plugin-ejs": "^1.7.0"
},
"devEngines": {
"runtime": {
"name": "node",
"version": ">=18.x",
"onFail": "error"
},
"packageManager": {
"name": "npm",
"version": ">=7.x",
"onFail": "error"
}
},
"browserslist": [],
"electronmon": {
"patterns": [
"!server",
"!src/renderer"
"pnpm": {
"onlyBuiltDependencies": [
"electron",
"esbuild"
]
}
},
"productName": "feishin"
}
+10233
View File
File diff suppressed because it is too large Load Diff
-2348
View File
File diff suppressed because it is too large Load Diff
-24
View File
@@ -1,24 +0,0 @@
{
"name": "feishin",
"version": "0.12.1",
"description": "",
"main": "./dist/main/main.js",
"author": {
"name": "jeffvli",
"url": "https://github.com/jeffvli/"
},
"scripts": {
"electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js",
"link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts",
"postinstall": "npm run electron-rebuild && npm run link-modules"
},
"dependencies": {
"cheerio": "^1.0.0-rc.12",
"mpris-service": "^2.1.2",
"ws": "^8.18.0"
},
"devDependencies": {
"electron": "31.1.0"
},
"license": "GPL-3.0"
}
+44
View File
@@ -0,0 +1,44 @@
import react from '@vitejs/plugin-react';
import path from 'path';
import { defineConfig, normalizePath } from 'vite';
import { ViteEjsPlugin } from 'vite-plugin-ejs';
import { version } from './package.json';
export default defineConfig({
build: {
emptyOutDir: true,
outDir: path.resolve(__dirname, './out/remote'),
rollupOptions: {
input: {
favicon: normalizePath(path.resolve(__dirname, './assets/icons/favicon.ico')),
index: normalizePath(path.resolve(__dirname, './src/remote/index.html')),
manifest: normalizePath(path.resolve(__dirname, './src/remote/manifest.json')),
remote: normalizePath(path.resolve(__dirname, './src/remote/index.tsx')),
worker: normalizePath(path.resolve(__dirname, './src/remote/service-worker.ts')),
},
output: {
assetFileNames: '[name].[ext]',
chunkFileNames: '[name].js',
entryFileNames: '[name].js',
},
},
},
plugins: [
react(),
ViteEjsPlugin({
prod: process.env.NODE_ENV === 'production',
root: normalizePath(path.resolve(__dirname, './src/remote')),
version,
}),
],
resolve: {
alias: {
'/@/i18n': path.resolve(__dirname, './src/i18n'),
'/@/remote': path.resolve(__dirname, './src/remote'),
'/@/renderer': path.resolve(__dirname, './src/renderer'),
'/@/shared': path.resolve(__dirname, './src/shared'),
},
},
root: path.resolve(__dirname, './src/remote'),
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

-9
View File
@@ -1,9 +0,0 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import { App } from '../renderer/app';
describe('App', () => {
it('should render', () => {
expect(render(<App />)).toBeTruthy();
});
});
+50 -25
View File
@@ -1,44 +1,53 @@
import { PostProcessorModule, TOptions, StringMap } from 'i18next';
import { PostProcessorModule, StringMap, TOptions } from 'i18next';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
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 fa from './locales/fa.json';
import fi from './locales/fi.json';
import fr from './locales/fr.json';
import ja from './locales/ja.json';
import pl from './locales/pl.json';
import zhHans from './locales/zh-Hans.json';
import de from './locales/de.json';
import hu from './locales/hu.json';
import id from './locales/id.json';
import it from './locales/it.json';
import ru from './locales/ru.json';
import ptBr from './locales/pt-BR.json';
import sr from './locales/sr.json';
import sv from './locales/sv.json';
import cs from './locales/cs.json';
import ja from './locales/ja.json';
import ko from './locales/ko.json';
import nbNO from './locales/nb-NO.json';
import nl from './locales/nl.json';
import pl from './locales/pl.json';
import ptBr from './locales/pt-BR.json';
import ru from './locales/ru.json';
import sr from './locales/sr.json';
import sv from './locales/sv.json';
import ta from './locales/ta.json';
import zhHans from './locales/zh-Hans.json';
import zhHant from './locales/zh-Hant.json';
import fa from './locales/fa.json';
import ko from './locales/ko.json';
const resources = {
cs: { translation: cs },
de: { translation: de },
en: { translation: en },
es: { translation: es },
de: { translation: de },
it: { translation: it },
ru: { translation: ru },
'pt-BR': { translation: ptBr },
fa: { translation: fa },
fi: { translation: fi },
fr: { translation: fr },
hu: { translation: hu },
id: { translation: id },
it: { translation: it },
ja: { translation: ja },
ko: { translation: ko },
'nb-NO': { translation: nbNO },
nl: { translation: nl },
pl: { translation: pl },
'zh-Hans': { translation: zhHans },
'zh-Hant': { translation: zhHant },
'pt-BR': { translation: ptBr },
ru: { translation: ru },
sr: { translation: sr },
sv: { translation: sv },
cs: { translation: cs },
nl: { translation: nl },
'nb-NO': { translation: nbNO },
ta: { translation: ta },
'zh-Hans': { translation: zhHans },
'zh-Hant': { translation: zhHant },
};
export const languages = [
@@ -62,6 +71,18 @@ export const languages = [
label: 'Français',
value: 'fr',
},
{
label: 'Bahasa Indonesia',
value: 'id',
},
{
label: 'Suomeksi',
value: 'fi',
},
{
label: 'Magyar',
value: 'hu',
},
{
label: 'Italiano',
value: 'it',
@@ -106,6 +127,10 @@ export const languages = [
label: 'Svenska',
value: 'sv',
},
{
label: 'Tamil',
value: 'ta',
},
{
label: '简体中文',
value: 'zh-Hans',
@@ -117,35 +142,34 @@ export const languages = [
];
const lowerCasePostProcessor: PostProcessorModule = {
type: 'postProcessor',
name: 'lowerCase',
process: (value: string) => {
return value.toLocaleLowerCase();
},
type: 'postProcessor',
};
const upperCasePostProcessor: PostProcessorModule = {
type: 'postProcessor',
name: 'upperCase',
process: (value: string) => {
return value.toLocaleUpperCase();
},
type: 'postProcessor',
};
const titleCasePostProcessor: PostProcessorModule = {
type: 'postProcessor',
name: 'titleCase',
process: (value: string) => {
return value.replace(/\S\S*/g, (txt) => {
return txt.charAt(0).toLocaleUpperCase() + txt.slice(1).toLowerCase();
});
},
type: 'postProcessor',
};
const ignoreSentenceCaseLanguages = ['de'];
const sentenceCasePostProcessor: PostProcessorModule = {
type: 'postProcessor',
name: 'sentenceCase',
process: (value: string, _key: string, _options: TOptions<StringMap>, translator: any) => {
const sentences = value.split('. ');
@@ -161,6 +185,7 @@ const sentenceCasePostProcessor: PostProcessorModule = {
})
.join('. ');
},
type: 'postProcessor',
};
i18n.use(lowerCasePostProcessor)
.use(upperCasePostProcessor)
+1 -1
View File
@@ -5,7 +5,7 @@ module.exports = {
createOldCatalogs: true,
customValueTemplate: null,
defaultNamespace: 'translation',
defaultValue: function (locale, namespace, key, value) {
defaultValue: function (locale, namespace, key) {
return key;
},
failOnUpdate: false,
+10 -5
View File
@@ -257,7 +257,9 @@
"translationTargetLanguage": "cílový jazyk překladu",
"translationTargetLanguage_description": "cílový jazyk pro překlad",
"lastfmApiKey": "klíč API {{lastfm}}",
"lastfmApiKey_description": "klíč API pro {{lastfm}}. vyžadováno pro obaly alb"
"lastfmApiKey_description": "klíč API pro {{lastfm}}. vyžadováno pro obaly alb",
"discordServeImage": "načítat obrázky {{discord}} ze serveru",
"discordServeImage_description": "sdílet obaly alb pro {{discord}} rich presence ze samotného serveru, dostupné pouze pro jellyfin a navidrome"
},
"action": {
"editPlaylist": "upravit $t(entity.playlist_one)",
@@ -375,7 +377,9 @@
"codec": "kodek",
"trackPeak": "vrchol skladby",
"preview": "náhled",
"translation": "překlad"
"translation": "překlad",
"additionalParticipants": "další přispívající",
"tags": "štítky"
},
"table": {
"config": {
@@ -474,7 +478,8 @@
"loginRateError": "příliš mnoho pokusů o přihlášení, zkuste to znovu za pár vteřin",
"badAlbum": "tuto stránku vidíte, protože tato skladba není součástí alba. tento problém může nastat, pokud máte skladbu na nejvyšší úrovni vaší složky s hudbou. jellyfin seskupuje skladby pouze, pokud se nacházejí ve složce.",
"networkError": "vyskytla se chyba sítě",
"openError": "nepodařilo se otevřít soubor"
"openError": "nepodařilo se otevřít soubor",
"badValue": "neplatná možnost „{{value}}“. tato možnost již neexistuje"
},
"filter": {
"mostPlayed": "nejvíce přehráváno",
@@ -747,8 +752,8 @@
"folderWithCount_few": "{{count}} složky",
"folderWithCount_other": "{{count}} složek",
"albumArtist_one": "umělec alba",
"albumArtist_few": "umělci alba",
"albumArtist_other": "umělců alba",
"albumArtist_few": "umělci alb",
"albumArtist_other": "umělci alb",
"track_one": "skladba",
"track_few": "skladby",
"track_other": "skladby",
+5
View File
@@ -27,6 +27,7 @@
"action_one": "action",
"action_other": "actions",
"add": "add",
"additionalParticipants": "additional participants",
"albumGain": "album gain",
"albumPeak": "album peak",
"areYouSure": "are you sure?",
@@ -106,6 +107,7 @@
"share": "share",
"size": "size",
"sortOrder": "order",
"tags": "tags",
"title": "title",
"trackNumber": "track",
"trackGain": "track gain",
@@ -158,6 +160,7 @@
"audioDeviceFetchError": "an error occurred when trying to get audio devices",
"authenticationFailed": "authentication failed",
"badAlbum": "you are seeing this page because this song is not part of an album. you are most likely seeing this issue if you have a song at the top level of your music folder. jellyfin only groups tracks if they are in a folder.",
"badValue": "invalid option \"{{value}}\". this value no longer exists",
"credentialsRequired": "credentials required",
"endpointNotImplementedError": "endpoint {{endpoint}} is not implemented for {{serverType}}",
"genericError": "an error occurred",
@@ -513,6 +516,8 @@
"discordListening_description": "show status as listening instead of playing",
"discordRichPresence": "{{discord}} rich presence",
"discordRichPresence_description": "enable playback status in {{discord}} rich presence. Image keys are: {{icon}}, {{playing}}, and {{paused}} ",
"discordServeImage": "serve {{discord}} images from server",
"discordServeImage_description": "share cover art for {{discord}} rich presence from server itself, only available for jellyfin and navidrome",
"discordUpdateInterval": "{{discord}} rich presence update interval",
"discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)",
"doubleClickBehavior": "queue all searched tracks when double clicking",
+62 -57
View File
@@ -21,8 +21,8 @@
"repeat_off": "repetir desactivado",
"queue_clear": "limpiar cola",
"muted": "silenciado",
"unfavorite": "no favorito",
"queue_moveToTop": "mover seleccionado al fondo",
"unfavorite": "no favorita",
"queue_moveToTop": "mover seleccionado al final",
"queue_moveToBottom": "mover seleccionado al principio",
"shuffle_off": "mezclar desactivado",
"addLast": "añadir último",
@@ -37,7 +37,7 @@
"remotePort_description": "establece el puerto para el control remoto del servidor",
"hotkey_skipBackward": "retroceder",
"replayGainMode_description": "ajusta el volumen de ganancia acorde a los valores de {{ReplayGain}} almacenados en los metadatos del archivo",
"audioDevice_description": "selecciona el dispositivo de audio para usar en la reproducción (solo reproductor web)",
"audioDevice_description": "selecciona el dispositivo de audio a usar durante la reproducción (solo reproductor web)",
"theme_description": "establece el tema a usar por la aplicación",
"hotkey_playbackPause": "pausa",
"replayGainFallback": "{{ReplayGain}} alternativa",
@@ -68,7 +68,7 @@
"scrobble_description": "hace scrobble de las reproducciones en tu servidor de medios",
"audioExclusiveMode_description": "activa el modo de audio exclusivo. En este modo, el sistema es normalmente bloqueado, y solo se permitirá mpv en la salida de audio",
"discordUpdateInterval": "intervalo de actualización del estado de actividad de {{discord}}",
"themeLight": "tema (luminoso)",
"themeLight": "tema (claro)",
"fontType_optionBuiltIn": "fuente incorporada",
"hotkey_playbackPlayPause": "play / pausa",
"hotkey_rate1": "calificar con 1 estrella",
@@ -82,8 +82,8 @@
"hotkey_playbackPlay": "reproducir",
"hotkey_togglePreviousSongFavorite": "cambia $t(common.previousSong) a favorito",
"hotkey_volumeDown": "bajar volumen",
"hotkey_unfavoritePreviousSong": "$t(common.previousSong) no favorito",
"audioPlayer_description": "selecciona el reproductor de audio a usar en la reproducción",
"hotkey_unfavoritePreviousSong": "$t(common.previousSong) no favorita",
"audioPlayer_description": "selecciona el reproductor de audio a usar durante la reproducción",
"globalMediaHotkeys": "teclas de acceso rápido globales a medios",
"hotkey_globalSearch": "búsqueda global",
"gaplessAudio_description": "establece la configuración de audio sin pausas para mpv",
@@ -106,11 +106,11 @@
"font": "fuente",
"mpvExtraParameters": "parámetros de mpv",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"themeLight_description": "establece el tema luminoso a usar por la aplicación",
"themeLight_description": "establece el tema claro a usar por la aplicación",
"hotkey_toggleFullScreenPlayer": "cambia el reproductor a pantalla completa",
"hotkey_localSearch": "búsqueda en la página",
"hotkey_toggleQueue": "cambia la cola",
"remotePassword_description": "establece la contraseña para el control remoto del servidor. Esas credenciales son transferidas de forma insegura por defecto, por lo que deberías usar una contraseña única para que no tengas nada de qué preocuparte",
"remotePassword_description": "establece la contraseña para el control remoto del servidor. Esas credenciales son transferidas de forma insegura por defecto, por lo que deberías usar una contraseña única para que no tengas nada de lo que preocuparte",
"hotkey_rate5": "calificar con 5 estrellas",
"hotkey_playbackPrevious": "pista anterior",
"showSkipButtons_description": "muestra o esconde los botones de saltar en la barra del reproductor",
@@ -126,7 +126,7 @@
"hotkey_rate2": "calificar con 2 estrellas",
"playButtonBehavior_description": "establece el comportamiento por defecto del botón de reproducción cuando se añaden canciones a la cola",
"minimumScrobblePercentage_description": "el porcentaje mínimo de la canción que debe ser reproducido antes de hacer scrobble",
"exitToTray": "salida a bandeja",
"exitToTray": "salir a la bandeja",
"hotkey_rate4": "calificar con 4 estrellas",
"enableRemote": "activar control remoto del servidor",
"showSkipButton_description": "muestra o esconde los botones de saltar en la barra del reproductor",
@@ -142,13 +142,13 @@
"replayGainFallback_description": "ganancia en db a aplicar si el archivo no tiene etiquetas de {{ReplayGain}}",
"replayGainPreamp_description": "ajusta la ganancia del preamplificador aplicada a los valores de {{ReplayGain}}",
"hotkey_toggleRepeat": "alterna repetir",
"lyricOffset_description": "desfasa la letra por la cantidad de milisegundos especificada",
"lyricOffset_description": "desfasa la letra en la cantidad de milisegundos especificada",
"sidebarConfiguration_description": "selecciona los elementos y el orden en que aparecerán en la barra lateral",
"fontType": "tipo de fuente",
"remotePort": "puerto del control remoto del servidor",
"applicationHotkeys": "teclas de acceso rápido de la aplicación",
"hotkey_playbackNext": "pista siguiente",
"useSystemTheme_description": "sigue la preferencia luminosa u oscura definida por el sistema",
"useSystemTheme_description": "sigue la preferencia clara u oscura definida por el sistema",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"lyricFetch_description": "busca letras en varias fuentes de Internet",
"lyricFetchProvider_description": "selecciona los proveedores para buscar letras. el orden de los proveedores es el orden en el que se consultarán",
@@ -160,13 +160,13 @@
"sidePlayQueueStyle_optionDetached": "separada",
"audioPlayer": "reproductor de audio",
"hotkey_zoomOut": "reducir",
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) no favorito",
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) no favorita",
"hotkey_rate0": "Limpiar calificación",
"discordApplicationId": "id de aplicación {{discord}}",
"applicationHotkeys_description": "configura las teclas de acceso rápido de la aplicación. marca la casilla para establecerlas como teclas de acceso rápido globales (solo escritorio)",
"floatingQueueArea_description": "muestra un icono flotante en el lado derecho de la pantalla para ver la cola de reproducción",
"hotkey_volumeMute": "silenciar volumen",
"hotkey_toggleCurrentSongFavorite": "cambia $t(common.currentSong) a favorito",
"hotkey_toggleCurrentSongFavorite": "$t(common.currentSong) cambia a favorita",
"remoteUsername": "nombre de usuario del control remoto del servidor",
"showSkipButton": "mostrar botones de saltar",
"sidebarPlaylistList": "listas de reproducción de la barra lateral",
@@ -195,12 +195,12 @@
"hotkey_browserBack": "retroceso",
"clearCache": "Limpiar la caché del navegador",
"clearQueryCache": "Limpiar la caché de Feishin",
"clearQueryCache_description": "Una 'limpieza suave' de Feishin. Esto refrescará las listas de reproducción, metadatos de pistas y restablecerá las letras guardadas. Se mantienen los ajustes, credenciales del servidor y las imágenes en caché",
"clearQueryCache_description": "Una 'limpieza suave' de Feishin. Esto refrescará las listas de reproducción, los metadatos de las pistas y restablecerá las letras guardadas. Se mantienen los ajustes, credenciales del servidor y las imágenes en caché",
"buttonSize": "tamaño del botón de la barra de reproducción",
"clearCache_description": "Una 'limpieza fuerte' de Feishin. Para limpiar la caché de Feishin, vacía la caché del navegador (imágenes guardadas y otros elementos). Se mantienen las credenciales y ajustes del servidor",
"buttonSize_description": "el tamaño de los botones de la barra de reproducción",
"passwordStore_description": "qué método de almacenamiento de contraseñas/claves secretas utilizar. cambie esta opción si tiene problemas para guardar contraseñas.",
"startMinimized_description": "iniciar la aplicación en la bandeja del sistema",
"passwordStore_description": "qué método de almacenamiento de contraseñas/claves secretas utilizar. cambia esta opción si tienes problemas para guardar contraseñas.",
"startMinimized_description": "inicia la aplicación en la bandeja del sistema",
"startMinimized": "iniciar minimizado",
"passwordStore": "contraseñas/almacenamiento secreto",
"playerAlbumArtResolution_description": "la resolución para la vista previa de la carátula del álbum del reproductor grande. más grande hace que parezca más nítido, pero puede ralentizar la carga. El valor predeterminado es 0, lo que significa automático",
@@ -208,8 +208,8 @@
"homeConfiguration": "Configuración de la página de inicio",
"mpvExtraParameters_help": "Uno por línea",
"genreBehavior": "Comportamiento predeterminado de la página de géneros",
"externalLinks_description": "Permite mostrar enlaces externos (Last.fm, MusicBrainz) en páginas de artista/álbum",
"genreBehavior_description": "Determina si al pulsar en un género se abre por defecto la lista de pistas o de álbumes",
"externalLinks_description": "Permite mostrar enlaces externos (Last.fm, MusicBrainz) en las páginas del artista/álbum",
"genreBehavior_description": "Determina si al hacer clic en un género se abre por defecto la lista de pistas o de álbumes",
"homeConfiguration_description": "Configura qué elementos son mostrados y en qué orden en la página de inicio",
"clearCacheSuccess": "Caché limpiada correctamente",
"externalLinks": "Mostrar enlaces externos",
@@ -218,15 +218,15 @@
"imageAspectRatio_description": "Si está habilitado, la portada será mostrada usando su relación de aspecto nativa. Para arte que no es 1:1, el espacio restante estará vacío",
"imageAspectRatio": "Usar relación de aspecto nativa de portada",
"doubleClickBehavior": "poner en cola todas las pistas buscadas al hacer doble clic",
"doubleClickBehavior_description": "si es true, se pondrán en cola todas las pistas que coincidan en una búsqueda de pistas. De lo contrario, solo se pondrá en cola la pista seleccionada",
"doubleClickBehavior_description": "si está activado, se pondrán en cola todas las pistas que coincidan en una búsqueda de pistas. De lo contrario, solo se pondrán en cola las pistas seleccionadas",
"volumeWidth": "Ancho del deslizador de volumen",
"volumeWidth_description": "La anchura del deslizador de volumen",
"discordListening_description": "mostrar el estado como escuchando en lugar de jugando",
"discordListening_description": "muestra el estado como Escuchando en lugar de Jugando a",
"discordListening": "Mostrar estado como escuchando",
"contextMenu": "Configuración del menú de contexto (clic derecho)",
"contextMenu_description": "Te permite esconder elementos que son mostrados en el menú cuando haces clic derecho en un elemento. Los elementos que no estén seleccionados serán escondidos",
"customCssEnable": "Habilitar CSS personalizado",
"customCssEnable_description": "Permite la escritura de CSS personalizado.",
"customCssEnable_description": "Permite escribir CSS personalizado.",
"customCss": "CSS personalizado",
"customCssNotice": "Aviso: mientras hay alguna sanitización (rechazar url() y content:), usar CSS personalizado puede aún entrañar riesgos cambiando la interfaz.",
"customCss_description": "Content CSS personalizado. Nota: content y urls remotas son propiedades rechazadas. Una vista previa de tu content se muestra debajo. Las entradas adicionales que no estableciste están presentes debido a la sanitización.",
@@ -236,20 +236,20 @@
"transcode_description": "permite la transcodificación a distintos formatos",
"transcodeBitrate": "tasa de bits a transcodificar",
"transcodeBitrate_description": "selecciona el bitrate a transcodificar. 0 significa dejar que el servidor elija",
"transcodeNote": "Se mostrará después de 1 (web) - 2 (mpv) pistas",
"transcodeNote": "tendrá efecto después de 1 (web) - 2 (mpv) canciones",
"transcodeFormat": "formato a transcodificar",
"transcodeFormat_description": "selecciona el formato a transcodificar. dejar vacío para que el servidor decida",
"albumBackground": "imagen de fondo del álbum",
"albumBackground_description": "Agregar una imagen de fondo a las páginas del álbum que contienen la carátula del álbum",
"albumBackground_description": "Añade una imagen de fondo a las páginas del álbum que contienen la carátula del álbum",
"albumBackgroundBlur": "Tamaño de desenfoque de la imagen de fondo del álbum",
"albumBackgroundBlur_description": "Ajustar el nivel de desenfoque de la imagen de fondo del álbum",
"albumBackgroundBlur_description": "Ajusta la cantidad de desenfoque aplicado a la imagen de fondo del álbum",
"playerbarOpenDrawer": "Cambiar la barra del reproductor a pantalla completa",
"playerbarOpenDrawer_description": "Permitir hacer clic en la barra del reproductor para abrir el reproductor en pantalla completa",
"playerbarOpenDrawer_description": "Permite hacer clic en la barra del reproductor para abrir el reproductor a pantalla completa",
"artistConfiguration": "Configuración de la página del artista del álbum",
"artistConfiguration_description": "Configurar qué elementos se muestran y en qué orden en la página del artista del álbum",
"artistConfiguration_description": "Configura qué elementos se muestran y en qué orden en la página del artista del álbum",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"trayEnabled": "Mostrar en el área de notificación",
"trayEnabled_description": "mostrar/ocultar el icono/menú del área de notificación. si está deshabilitado, también deshabilita minimizar/salir a la bandeja",
"trayEnabled_description": "muestra/oculta el icono/menú del área de notificación. si está deshabilitado, también deshabilita minimizar/salir a la bandeja",
"translationApiProvider": "Proveedor de API de traducción",
"translationApiProvider_description": "Proveedor de API para traducción",
"translationApiKey": "clave api de traducción",
@@ -257,7 +257,9 @@
"translationTargetLanguage": "idioma final de la traducción",
"translationTargetLanguage_description": "lengua de destino de la traducción",
"lastfmApiKey_description": "la clave API para {{lastfm}}. Requerida para la portada",
"lastfmApiKey": "Clave API para {{lastfm}}"
"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"
},
"action": {
"editPlaylist": "editar $t(entity.playlist_one)",
@@ -273,7 +275,7 @@
"deletePlaylist": "eliminar $t(entity.playlist_one)",
"removeFromQueue": "eliminar de la cola",
"deselectAll": "desmarcar todo",
"moveToBottom": "mover al fondo",
"moveToBottom": "mover al final",
"setRating": "establecer calificación",
"toggleSmartPlaylistEditor": "cambiar editor $t(entity.smartPlaylist)",
"removeFromFavorites": "eliminar de $t(entity.favorite_other)",
@@ -296,7 +298,7 @@
"left": "izquierda",
"save": "guardar",
"right": "derecha",
"currentSong": "actual $t(entity.track_one)",
"currentSong": "$t(entity.track_one) actual",
"collapse": "contraer",
"trackNumber": "pista",
"descending": "descendiente",
@@ -334,7 +336,7 @@
"saveAndReplace": "guardar y reemplazar",
"playerMustBePaused": "el reproductor debe pausarse",
"confirm": "confirmar",
"resetToDefault": "restablecer a valor por defecto",
"resetToDefault": "restablecer al valor predeterminado",
"home": "inicio",
"comingSoon": "próximamente…",
"reset": "restablecer",
@@ -365,8 +367,8 @@
"channel_one": "Canal",
"channel_many": "Canales",
"channel_other": "Canales",
"trackPeak": "la más alta de la canción",
"albumPeak": "lo más destacado del álbum",
"trackPeak": "pico de pista",
"albumPeak": "pico del álbum",
"albumGain": "Ganancia de álbum",
"mbid": "ID de MusicBrainz",
"codec": "Códec",
@@ -375,7 +377,9 @@
"share": "Compartir",
"trackGain": "Ganancia de pista",
"preview": "Vista previa",
"translation": "traducción"
"translation": "traducción",
"additionalParticipants": "Participantes adicionales",
"tags": "Etiquetas"
},
"error": {
"remotePortWarning": "reiniciar el servidor para aplicar el nuevo puerto",
@@ -397,13 +401,14 @@
"audioDeviceFetchError": "un error ocurrió cuando se intentó obtener los dispositivos de audio",
"invalidServer": "servidor inválido",
"loginRateError": "demasiados intentos de inicio de sesión, por favor inténtalo en unos segundos",
"badAlbum": "Estás viendo esta página porque esta canción no forma parte de un álbum. Este problema puede ocurrir si tiene una canción en el nivel superior de su carpeta de música. Jellyfin solo agrupa pistas si están en una carpeta.",
"badAlbum": "Estás viendo esta página porque esta canción no forma parte de un álbum. Este problema puede ocurrir si tienes una canción en el nivel superior de tu carpeta de música. Jellyfin solo agrupa pistas si están en una carpeta.",
"networkError": "Ocurrió un error de red",
"openError": "No se pudo abrir el archivo"
"openError": "No se pudo abrir el archivo",
"badValue": "Opción inválida \"{{value}}\". Este valor ya no existe"
},
"filter": {
"mostPlayed": "más reproducido",
"isCompilation": "es compilación",
"isCompilation": "es una compilación",
"recentlyPlayed": "recientemente reproducido",
"isRated": "es clasificado",
"title": "título",
@@ -434,7 +439,7 @@
"criticRating": "calificación de la crítica",
"trackNumber": "pista",
"comment": "comentarios",
"playCount": "número de reproducción",
"playCount": "número de reproducciones",
"recentlyUpdated": "actualizado recientemente",
"channels": "$t(common.channel_other)",
"owner": "$t(common.owner)",
@@ -500,8 +505,8 @@
"mostPlayed": "más reproducidos",
"newlyAdded": "nuevos lanzamientos añadidos",
"title": "$t(common.home)",
"explore": "explorar desde tu biblioteca",
"recentlyPlayed": "recientemente reproducidos"
"explore": "explora desde tu biblioteca",
"recentlyPlayed": "reproducidos recientemente"
},
"fullscreenPlayer": {
"upNext": "siguiente",
@@ -519,7 +524,7 @@
"lyricGap": "desfase de letra",
"dynamicImageBlur": "tamaño de desenfoque de imagen",
"dynamicIsImage": "habilitar imagen de fondo",
"lyricOffset": "compensación de letras (ms)"
"lyricOffset": "desplazamiento de letras (ms)"
},
"lyrics": "letras",
"related": "relacionado",
@@ -529,7 +534,7 @@
"albumDetail": {
"moreFromArtist": "más de este $t(entity.artist_one)",
"moreFromGeneric": "más de {{item}}",
"released": "publicado"
"released": "publicado el"
},
"setting": {
"playbackTab": "reproducción",
@@ -549,7 +554,7 @@
"trackList": {
"title": "$t(entity.track_other)",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"artistTracks": "pistas por {{artist}}"
"artistTracks": "Pistas de {{artist}}"
},
"globalSearch": {
"commands": {
@@ -565,11 +570,11 @@
"albumList": {
"title": "$t(entity.album_other)",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"artistAlbums": "álbumes de {{artist}}"
"artistAlbums": "Álbumes de {{artist}}"
},
"albumArtistDetail": {
"viewAllTracks": "ver todo de $t(entity.track_other)",
"relatedArtists": "$t(entity.artist_other) similar",
"viewAllTracks": "ver todas las $t(entity.track_other)",
"relatedArtists": "$t(entity.artist_other) similares",
"topSongs": "mejores canciones",
"topSongsFrom": "las mejores canciones de {{title}}",
"viewAll": "Ver todo",
@@ -675,7 +680,7 @@
"songCount": "$t(entity.track_other)",
"trackNumber": "pista",
"genre": "$t(entity.genre_one)",
"albumArtist": "artista de álbum",
"albumArtist": "artista del álbum",
"path": "ruta",
"discNumber": "disco",
"channels": "$t(common.channel_other)",
@@ -706,7 +711,7 @@
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"playCount": "número de reproducción",
"playCount": "número de reproducciones",
"genre": "$t(entity.genre_one)",
"favorite": "$t(common.favorite)",
"year": "$t(common.year)",
@@ -747,15 +752,15 @@
"folderWithCount_one": "{{count}} carpeta",
"folderWithCount_many": "{{count}} carpetas",
"folderWithCount_other": "{{count}} carpetas",
"albumArtist_one": "artista de álbum",
"albumArtist_many": "artistas de álbum",
"albumArtist_other": "artistas de álbum",
"albumArtist_one": "artista del álbum",
"albumArtist_many": "artistas del álbum",
"albumArtist_other": "artistas del álbum",
"track_one": "pista",
"track_many": "pistas",
"track_other": "pistas",
"albumArtistCount_one": "{{count}} artista de álbum",
"albumArtistCount_many": "{{count}} artistas de álbum",
"albumArtistCount_other": "{{count}} artistas de álbum",
"albumArtistCount_one": "{{count}} artista del álbum",
"albumArtistCount_many": "{{count}} artistas del álbum",
"albumArtistCount_other": "{{count}} artistas del álbum",
"albumWithCount_one": "{{count}} álbum",
"albumWithCount_many": "{{count}} álbumes",
"albumWithCount_other": "{{count}} álbumes",
@@ -777,9 +782,9 @@
"trackWithCount_one": "{{count}} pista",
"trackWithCount_many": "{{count}} pistas",
"trackWithCount_other": "{{count}} pistas",
"play_one": "Reproducir {{count}}",
"play_many": "Reproducir {{count}}",
"play_other": "Reproducir {{count}}",
"play_one": "{{count}} reproducción",
"play_many": "{{count}} reproducciones",
"play_other": "{{count}} reproducciones",
"song_one": "canción",
"song_many": "canciones",
"song_other": "canciones"
+354 -33
View File
@@ -6,12 +6,31 @@
"skip": "رد کن",
"toggleFullscreenPlayer": "تغییر به پخش‌کنندهٔ تمام‌صفحه",
"skip_back": "برو عقب",
"shuffle": "شافل",
"shuffle": "پخش تصادفی",
"repeat_off": "تکرار غیرفعال",
"pause": "pause",
"pause": "ایست",
"unfavorite": "حذف از موردعلاقه‌ها",
"shuffle_off": "شافل غیرفعال",
"skip_forward": "برو جلو"
"shuffle_off": "پخش تصادفی غیر فعال",
"skip_forward": "برو جلو",
"queue_moveToTop": "جابجا کردن انتخاب شده به پایین",
"queue_clear": "خالی کردن صف",
"queue_remove": "حذف انتخاب شده",
"addLast": "افزودن به پایان",
"next": "پسین",
"play": "پخش",
"playbackSpeed": "تندی پخش",
"playRandom": "پخش تصادفی",
"previous": "پیشین",
"mute": "بی‌صدا کردن",
"playbackFetchCancel": "دارد طول می‌کشد... برای لفو کردن اعلان را ببندید",
"playbackFetchInProgress": "بارگذاری قطعه‌ها…",
"queue_moveToBottom": "جابجا کردن انتخاب شده به بالا",
"addNext": "افزودن به پسین",
"favorite": "مورد علاقه",
"playSimilarSongs": "پخش آهنگ‌های همگون",
"playbackFetchNoResults": "هیچ آهنگی پیدا نشد",
"viewQueue": "دیدن صف",
"muted": "بی‌صدا"
},
"action": {
"editPlaylist": "ویرایش $t(entity.playlist_one)",
@@ -29,8 +48,13 @@
"deselectAll": "لغو انتخاب همه",
"moveToBottom": "انتقال به پایین",
"setRating": "تعیین امتیاز",
"toggleSmartPlaylistEditor": "تغییر $t(entity.smartPlaylist) ویرایشگر",
"removeFromFavorites": "حذف از $t(entity.favorite_other)"
"toggleSmartPlaylistEditor": "تغییر ویرایشگر $t(entity.smartPlaylist)",
"removeFromFavorites": "حذف از $t(entity.favorite_other)",
"openIn": {
"lastfm": "باز کردن در Last.fm",
"musicbrainz": "باز کردن در MusicBranz"
},
"moveToNext": "جابجا کردن به بعدی"
},
"setting": {
"hotkey_skipBackward": "برو عقب",
@@ -72,7 +96,7 @@
"mpvExecutablePath": "مسیر اجرای MPV",
"audioDevice": "دستگاه صوتی",
"hotkey_rate2": "امتیاز ۲ ستاره",
"playButtonBehavior_description": "رفتار پیش‌فرض دکمهٔ پخش را هنگامی که آهنگی به صف اضافه می‌شود معین می‌کند",
"playButtonBehavior_description": "رفتار پیش‌فرض دکمهٔ پخش را هنگامی که آهنگی به صف افزوده می‌شود را معین می‌کند",
"exitToTray": "خروج به tray",
"hotkey_rate4": "امتیاز ۴ ستاره",
"enableRemote": "فعال کردن کنترل از راه دور سرویس‌دهنده",
@@ -93,7 +117,64 @@
"customFontPath_description": "مسیر قلم سفارشی را برای استفاده در اپلیکیشن مشخص کنید",
"gaplessAudio_optionWeak": "ضعیف (توصیه شده)",
"hotkey_playbackStop": "توقف",
"font_description": "قلم مورد استفادهٔ اپلیکیشن را معین می‌کند"
"font_description": "قلم مورد استفادهٔ اپلیکیشن را معین می‌کند",
"accentColor_description": "رنگ شاخص را برای نرم‌افزار مشخص می‌کند",
"applicationHotkeys": "کلیدهای میان‌بر نرم‌افزار",
"accentColor": "رنگ شاخص",
"albumBackgroundBlur": "اندازه‌ی مبهمی نگاره‌ی پس‌زمینه‌ی آلبوم",
"albumBackgroundBlur_description": "مقدار مبهمی‌ای که روی نگاره‌ی پس‌زمینه‌ی آلبوم اعمال می‌شود را تنظیم می‌کند",
"albumBackground": "نگاره‌ی پس‌زمینه‌ی آلبوم",
"albumBackground_description": "یک نگاره‌ی پس‌زمینه برای صفحات آلبوم دارای نگار آلبوم هستند، می‌افزاید",
"artistConfiguration": "پیکربندی صفحه‌ی هنرمند آلبوم",
"applicationHotkeys_description": "پیکربندی کلیدهای میان‌بر نرم‌افزار. برای تنظیم یک کلید میان‌بر عمومی مربع چک را فعال کنید (فقط پخش‌کننده‌ی میزکار)",
"clearCache": "پاک‌سازی کَش مرورگر",
"clearQueryCache": "پاک‌سازی کَش فیشین",
"clearCacheSuccess": "با موفقیت کَش پاک شد",
"artistConfiguration_description": "پیکربندی اینکه چه آیتمی‌هایی و در چه ترتیبی در صفحه‌ی هنرمند آلبوم نمایش داده شوند",
"buttonSize": "اندازه‌ی دکمه‌ی پخش نوار",
"contextMenu": "پیکربندی فهرست زمینه (کلیک راست)",
"buttonSize_description": "اندازه‌ی دکمه‌های پخش نوار",
"audioExclusiveMode_description": "حالت اختصاصی خروجی را فعال می‌کند. در این حالت، سامانه معمولاً قفل است و فقط mpv می‌تواند خروجی صدا دهد",
"clearQueryCache_description": "یک 'پاک‌سازی نرم' از فیشین. این فهرست‌های پخش و فراداده‌ی قطعه‌ها را تازه می‌کند و متن شعرهای ذخیره شده را بازنشانی می‌کند. پیکربندی‌ها، اعتبارنامه‌های سرویس‌دهنده و نگاره‌های کَش شده حفظ می‌شوند",
"clearCache_description": "یک 'پاک‌سازی سخت' فیشین. افزون بر پاک‌سازی کَش فیشین، کَش مرورگر هم تهی می‌شود (نگاره‌های ذخیره شده و باقی دارایی‌ها). اعتبارنامه‌ها و پیکربندی‌ها حفظ می‌شوند",
"contextMenu_description": "به شما اجازه می‌دهد که آیتم‌های نمایش داده شده در فهرستی که وقتی روی یک آیتم کلیک راست می‌کنید پدیدار می‌شود، را پنهان کنید. آیتم‌هایی که منتخب نیستند پنهان می‌شوند",
"crossfadeStyle": "شیوه‌ی crossfade",
"customCssEnable_description": "اجازه دادن برای نوشتن css سفارشی.",
"translationApiKey": "کلید API ترجمه",
"webAudio_description": "از صدای وب بهره‌مند می‌شود. این قابلیت‌های پیشرفته‌ای مانند گین بازپخش (replygain) را فعال می‌کند. غیرفعال کنید اگر غیر از این را تجربه می‌کنید",
"windowBarStyle_description": "گزینش سبک نوار پنجره",
"translationApiKey_description": "کلید API برای ترجمه (پشتیبانی فقط برای نقطه‌ی پایانی سرویس‌دهنده‌ی جهانی)",
"theme": "تم",
"hotkey_togglePreviousSongFavorite": "تغییر وضعیت برای مورد علاقه‌ی $t(common.previousSong)",
"transcode": "فعال‌سازی رمزگردانی",
"transcode_description": "رمزگردانی به فرمت‌های گوناگون را فعال می‌کند",
"transcodeBitrate": "نرخ انتقال رمزگردانی",
"startMinimized": "پنهان‌شده آغاز کن",
"theme_description": "تم مورد استفاده در نرم‌افزار را می‌گزیند",
"themeLight": "تم (روشن)",
"transcodeBitrate_description": "نرخ انتقال برای رمزگردانی را انتخاب می‌کند. 0 بدان معناست سرور آن را انتخاب کند",
"transcodeFormat": "فرمت رمزگردانی",
"transcodeFormat_description": "فرمت رمزگردانی را انتخاب می‌کند. برای اینکه سرور آن را انتخاب کند، خالی بگذارید",
"customCssEnable": "فعال کردن css سفارشی",
"translationTargetLanguage": "زبان هدف ترجمه",
"hotkey_toggleCurrentSongFavorite": "تغییر وضعیت مورد علاقه برای $t(common.currentSong)",
"themeDark_description": "تم تاریک را برای استفاده‌ی نرم‌افزار می‌گزیند",
"volumeWheelStep_description": "اندازه‌ای از حجم صدا را در زمان اسکرول کردن روی نوار لغزنده تغییر داده شود",
"trayEnabled": "نمایش سینی",
"trayEnabled_description": "نمایش/پنهان کردن آیکون/فهرست در سینی. اگر غیرفعال باشد، کوچک کردن/خروج به سینی را نیز غیرفعال می‌کند",
"useSystemTheme_description": "از روشنی یا تاریکی که سیستم تعریف کرده است، پیروی می‌کند",
"crossfadeDuration": "زمان محو کردن گذار قطعه به قطعه‌ی بعدی",
"themeLight_description": "تم روشن را برای استفاده‌ی نرم‌افزار می‌گزیند",
"volumeWidth": "عرض نوار لغزنده‌ی حجم صدا",
"crossfadeStyle_description": "شیوه‌ی crossfade که می‌خواهید پخش‌کننده از آن استفاده کند را انتخاب کنید",
"startMinimized_description": "نرم‌افزار را در سینی اجرا کن",
"volumeWidth_description": "عرضی که نوار لغزنده‌ی حجم صدا داشته باشد",
"themeDark": "تم (تاریک)",
"useSystemTheme": "استفاده از تم سیستم",
"volumeWheelStep": "گام چرخ حجم صدا",
"webAudio": "استفاده از صدای وب",
"windowBarStyle": "سبک نوار پنجره",
"crossfadeDuration_description": "زمان افکت crossfade را مشخص می‌کند"
},
"common": {
"backward": "به عقب",
@@ -140,9 +221,9 @@
"forceRestartRequired": "برای اعمال تغییرها دوباره راه‌اندازی کنید… اعلان را برای راه‌اندازی دوباره ببندید",
"version": "نسخه",
"title": "عنوان",
"filter_one": "فیلتر",
"filter_other": "فیلتر",
"filters": "فیلتر",
"filter_one": "پالایش",
"filter_other": "پالایش",
"filters": "پالایش",
"create": "ساختن",
"bitrate": "بیت‌ریت",
"saveAndReplace": "ذخیره و جایگزین",
@@ -162,17 +243,29 @@
"menu": "منو",
"restartRequired": "راه‌اندازی دوباره لازم است",
"previousSong": "$t(entity.track_one) پیشین",
"noResultsFromQuery": "جست و جو نتیجه‌ای نداشت",
"noResultsFromQuery": "جست‌وجو نتیجه‌ای نداشت",
"quit": "خروج",
"expand": "گسترش",
"search": "جست و جو",
"search": "جست‌وجو",
"saveAs": "ذخیره کن با اسم",
"disc": "دیسک",
"yes": "بله",
"random": "تصادفی",
"size": "حجم",
"biography": "زندگی‌نامه",
"note": "توجه"
"note": "توجه",
"albumGain": "گین آلبوم",
"close": "بستن",
"albumPeak": "اوج آلبوم",
"mbid": "شناسه‌ی MusicBrainz",
"reload": "بارگذاری مجدد",
"setting": "پیکربندی",
"trackGain": "گین قطعه",
"trackPeak": "اوج قطعه",
"translation": "ترجمه",
"preview": "پیش‌نمایش",
"share": "اشتراک‌گذاری",
"codec": "کدک"
},
"error": {
"remotePortWarning": "برای تعیین port تازه، سرویس دهنده را دوباره راه‌اندازی کنید",
@@ -188,7 +281,15 @@
"serverNotSelectedError": "سرویس‌دهنده‌ای انتخاب نشده",
"remoteDisableError": "هنگام $t(common.disable) سرویس دهنده خطایی رخ داد",
"mpvRequired": "وجود MPV ضروری است",
"audioDeviceFetchError": "هنگام دسترسی به دستگاه صوتی خطایی رخ داد"
"audioDeviceFetchError": "هنگام دسترسی به دستگاه صوتی خطایی رخ داد",
"localFontAccessDenied": "دسترسی به فونت‌های محلی پذیرفته نشد",
"loginRateError": "تلاش‌های بسیار برای ورود انجام داده‌اید،‌لطفاً بعد از چند ثانیه دوباره امتحان کنید",
"networkError": "خطای شبکه رخ داد",
"badAlbum": "شما این صفحه را می‌بینید چون‌که این آهنگ قسمتی از یک آلبوم نیست. شما احتمالا این مسأله را به این خاطر می‌بینید که آهنگی در پوشه‌ی سطح بالای آهنگ‌هایتان دارید. جلی‌فین فقط قطعه‌هایی را گروه‌بندی می‌کند که در یک پوشه قرار دارند.",
"invalidServer": "سرویس‌دهنده‌ی نامعتبر",
"openError": "نمی‌توان پرونده را باز کرد",
"endpointNotImplementedError": "نقطه‌ی پایان {{endpoint}} برای {{serverType}} قرار داده نشده است",
"systemFontError": "خطایی هنگام تلاش برای دریافت فونت‌های سیستم رخ داد"
},
"filter": {
"mostPlayed": "بیشتر پخش شده",
@@ -201,13 +302,13 @@
"owner": "$t(common.owner)",
"title": "عنوان",
"rating": "امتیاز",
"search": "جست و جو",
"search": "جست‌وجو",
"bitrate": "بیت‌ریت",
"genre": "$t(entity.genre_one)",
"recentlyAdded": "به تازگی اضافه شده",
"recentlyAdded": "به تازگی افزوده شده",
"note": "توجه",
"name": "نام",
"dateAdded": "تاریخ اضافه شدن",
"dateAdded": "تاریخ افزوده شدن",
"releaseDate": "تاریخ انتشار",
"albumCount": "$t(entity.album_other) عدد",
"path": "مسیر",
@@ -230,7 +331,9 @@
"fromYear": "از سال",
"criticRating": "امتیاز منتقدین",
"album": "$t(entity.album_one)",
"trackNumber": "قطعه"
"trackNumber": "قطعه",
"communityRating": "رتبه بندی جامعه",
"isCompilation": "مخلوط است"
},
"form": {
"deletePlaylist": {
@@ -252,38 +355,61 @@
"input_url": "نشانی",
"input_password": "رمز عبور",
"input_name": "نام سرویس‌دهنده",
"success": "سرویس‌دهنده اضافه شد",
"success": "سرویس‌دهنده افزوده شد",
"input_savePassword": "ذخیرهٔ رمز",
"error_savePassword": "هنگام ذخیره رمز خطایی رخ داد"
"error_savePassword": "هنگام ذخیره رمز خطایی رخ داد",
"ignoreCors": "نادیده گرفتن هسته‌ها ($t(common.restartRequired))",
"input_legacyAuthentication": "فعال‌سازی احراز هویت سنتی",
"ignoreSsl": "نادیده گرفتن ssl ($t(common.restartRequired))"
},
"addToPlaylist": {
"success": "$t(entity.song_other) به {{numOfPlaylists}}$t(entity.playlist_other) اضافه شد",
"success": "$t(entity.song_other) به {{numOfPlaylists}}$t(entity.playlist_other) افزوده شد",
"title": "افزودن به $t(entity.playlist_one)",
"input_playlists": "$t(entity.playlist_other)"
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "پرش از تکراری‌ها"
},
"lyricSearch": {
"input_name": "$t(common.name)",
"input_artist": "$t(entity.artist_one)"
"input_artist": "$t(entity.artist_one)",
"title": "جست‌وجو در متن شعر"
},
"editPlaylist": {
"title": "ویرایش $t(entity.playlist_one)"
"title": "ویرایش $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) با موفقیت بروزرسانی شد",
"publicJellyfinNote": "جلی‌فین به دلیلی این‌که فهرست پخش عمومی‌ست یا خصوصی را فاش نمی‌کند. اگر می‌خواهید این عمومی باقی بماند، لطفاٌ ورودی پیش‌رو را منتخب داشته باشید"
},
"queryEditor": {
"input_optionMatchAny": "همخوانی داشتن هر کدام",
"input_optionMatchAll": "همخوانی داشتن همه"
},
"shareItem": {
"expireInvalid": "انقضا باید در آینده باشد",
"description": "بازنمود",
"setExpiration": "تنظیم انقضا",
"success": "پیوند اشتراک‌گذاری در کلیپ‌بورد کپی شد (یا اینجا را کلیک کنید تا باز شود)",
"allowDownloading": "اجازه دادن بارگیری",
"createFailed": "ناکامی در ساخت پیوند اشتراک‌گذاری (آیا اشتراک‌گذاری فعال است؟)"
},
"updateServer": {
"success": "سرویس‌دهنده با موفقیت بروزرسانی شد",
"title": "بروزرسانی سرویس‌دهنده"
}
},
"entity": {
"genre_one": "ژانر",
"genre_other": "ژانر",
"genre_other": "ژانرها",
"playlistWithCount_one": "{{count}} فهرست پخش",
"playlistWithCount_other": "{{count}} فهرست پخش",
"playlist_one": "فهرست پخش",
"playlist_other": "فهرست پخش",
"playlist_other": "فهرست‌های پخش",
"artist_one": "هنرمند",
"artist_other": "هنرمند",
"artist_other": "هنرمندان",
"folderWithCount_one": "{{count}} پوشه",
"folderWithCount_other": "{{count}} پوشه",
"albumArtist_one": "هنرمند آلبوم",
"albumArtist_other": "هنرمند آلبوم",
"albumArtist_other": "هنرمندان آلبوم",
"track_one": "قطعه",
"track_other": "قطعه",
"track_other": "قطعه‌ها",
"albumArtistCount_one": "{{count}} هنرمند آلبوم",
"albumArtistCount_other": "{{count}} هنرمند آلبوم",
"albumWithCount_one": "{{count}} آلبوم",
@@ -293,13 +419,208 @@
"artistWithCount_one": "{{count}} هنرمند",
"artistWithCount_other": "{{count}} هنرمند",
"folder_one": "پوشه",
"folder_other": "پوشه",
"folder_other": "پوشه‌ها",
"smartPlaylist": "$t(entity.playlist_one) هوشمند",
"album_one": "آلبوم",
"album_other": "آلبوم",
"album_other": "آلبوم‌ها",
"genreWithCount_one": "{{count}} ژانر",
"genreWithCount_other": "{{count}} ژانر",
"trackWithCount_one": "{{count}} قطعه",
"trackWithCount_other": "{{count}} قطعه"
"trackWithCount_other": "{{count}} قطعه",
"play_one": "{{count}} بار پخش",
"play_other": "{{count}} بار پخش",
"song_one": "آهنگ",
"song_other": "آهنگ‌ها"
},
"page": {
"albumList": {
"title": "$t(entity.album_other)",
"artistAlbums": "آلبوم‌های {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)"
},
"appMenu": {
"settings": "$t(common.setting_other)",
"selectServer": "گزینش سرویس‌دهنده",
"expandSidebar": "گسترش نوار کناری",
"collapseSidebar": "فروکش نوار کناری",
"goBack": "بازگشت",
"openBrowserDevtools": "باز کردن ابزارهای توسعه مرورگر",
"quit": "$t(common.quit)",
"goForward": "پیش رفتن",
"manageServers": "مدیریت سرویس‌دهنده‌ها",
"version": "نسخه‌ی {{version}}"
},
"albumArtistDetail": {
"appearsOn": "مشاهده می‌شود در",
"about": "درباره‌ی {{artist}}",
"recentReleases": "عرضه‌های اخیر",
"viewAllTracks": "نمایش همه‌ی $t(entity.track_other)",
"topSongsFrom": "قطعه‌های برتر از {{title}}",
"viewAll": "نمایش همه",
"viewDiscography": "نمایش کاتالوگ",
"relatedArtists": "$t(entity.artist_other) مربوطه",
"topSongs": "قطعه‌های برتر"
},
"contextMenu": {
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"numberSelected": "{{count}} تا انتخاب شده",
"play": "$t(player.play)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"deselectAll": "$t(action.deselectAll)",
"download": "بارگیری",
"shareItem": "اشتراک‌گذاری آیتم",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"showDetails": "دریافت داده",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromQueue": "$t(action.removeFromQueue)",
"playShuffled": "$t(player.shuffle)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"setRating": "$t(action.setRating)",
"deletePlaylist": "$t(action.deletePlaylist)",
"moveToNext": "$t(action.moveToNext)"
},
"fullscreenPlayer": {
"related": "موارد مربوطه",
"visualizer": "تجسم یافته",
"config": {
"dynamicImageBlur": "اندازه مبهمی نگاره",
"dynamicIsImage": "فعال‌سازی نگاره به عنوان پس‌زمینه",
"lyricOffset": "انحراف متن شعر (میلی‌ثانیه)",
"unsynchronized": "همگام نشده",
"dynamicBackground": "پس‌زمینه پویا",
"followCurrentLyric": "دنبال کردن متن شعر کنونی",
"lyricAlignment": "هم‌ترازی متن شعر",
"lyricGap": "فاصله‌ی متن شعر",
"showLyricProvider": "نمایش فراهم‌گر متن شعر",
"useImageAspectRatio": "استفاده از نسبت نمای نگاره",
"lyricSize": "اندازه‌ی متن شعر",
"opacity": "شفافی",
"showLyricMatch": "نمایش همخوانی متن شعر",
"synchronized": "همگام شده"
},
"noLyrics": "هیچ متن شعری پیدا نشد",
"lyrics": "متن شعر",
"upNext": "در ادامه"
},
"home": {
"mostPlayed": "بیشترین پخش‌شده‌ها",
"title": "$t(common.home)",
"explore": "در کتاب‌خانه‌ی خود کاوش کنید",
"newlyAdded": "عرضه‌های تازه افزوده شده",
"recentlyPlayed": "تازه پخش شده‌ها"
},
"playlist": {
"reorder": "مرتب کردن دوباره زمانی فقط زمانی فعال شود که مرتب‌سازی بر اساس شناسه است"
},
"setting": {
"advanced": "پیشرفته",
"windowTab": "پنجره",
"generalTab": "همگانی",
"hotkeysTab": "کلیدهای میان‌بر",
"playbackTab": "پخش"
},
"sidebar": {
"genres": "$t(entity.genre_other)",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"folders": "$t(entity.folder_other)",
"artists": "$t(entity.artist_other)",
"home": "$t(common.home)",
"nowPlaying": "پخش کنونی",
"tracks": "$t(entity.track_other)",
"settings": "$t(common.setting_other)",
"shared": "$t(entity.playlist_other) اشتراک‌گذاری شده"
},
"albumDetail": {
"moreFromArtist": "موارد بیشتر از این $t(entity.artist_one)",
"moreFromGeneric": "موارد بیشتر از {{item}}",
"released": "عرضه شده"
},
"manageServers": {
"title": "مدیریت سرویس‌دهنده‌ها",
"url": "آدرس",
"serverDetails": "ریزگان سرویس‌دهنده",
"removeServer": "حذف سرویس‌دهنده",
"username": "نام کاربری",
"editServerDetailsTooltip": "ویرایش ریزگان سرویس‌دهنده"
},
"genreList": {
"showAlbums": "نمایش $t(entity.genre_one) $t(entity.album_other)",
"title": "$t(entity.genre_other)",
"showTracks": "نمایش $t(entity.genre_one) $t(entity.track_other)"
},
"globalSearch": {
"commands": {
"goToPage": "رفتن به صفحه‌ی",
"searchFor": "جست‌و‌جو برای {{query}}",
"serverCommands": "فرمان‌های سرویس‌دهنده"
},
"title": "فرمان‌ها"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"trackList": {
"title": "$t(entity.track_other)",
"artistTracks": "قطعه‌های {{artist}}",
"genreTracks": "$t(entity.track_other) \"{{genre}}\""
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"itemDetail": {
"copyPath": "کپی کردن مسیر در کلیپ‌بورد",
"copiedPath": "مسیر با موفقیت کپی شد",
"openFile": "نمایش قطعه در مدیر پرونده"
}
},
"table": {
"column": {
"size": "$t(common.size)",
"lastPlayed": "آخرین بار پخش شده",
"discNumber": "دیسک",
"songCount": "$t(entity.track_other)",
"title": "عنوان",
"trackNumber": "قطعه",
"favorite": "مورد علاقه",
"genre": "$t(entity.genre_one)",
"comment": "دیدگاه",
"playCount": "تعداد پخش",
"rating": "امتیاز",
"path": "مسیر",
"releaseYear": "سال",
"dateAdded": "تاریخ افزوده شدن",
"releaseDate": "تاریخ عرضه"
},
"config": {
"general": {
"followCurrentSong": "آهنگ کنونی را دنبال کن",
"displayType": "نوع نمایش",
"itemSize": "اندازه‌ی آیتم (px)",
"size": "$t(common.size)",
"tableColumns": "ستون‌های جدول",
"autoFitColumns": "تطبیق دادن ستون‌ها به شیوه‌ی خودکار",
"gap": "$t(common.gap)",
"itemGap": "فاصله‌ی آیتم (px)"
},
"view": {
"card": "کارت"
},
"label": {
"playCount": "تعداد پخش",
"dateAdded": "تاریخ افزوده شدن",
"discNumber": "شماره‌ی دیسک",
"lastPlayed": "آخرین بار پخش شده",
"actions": "$t(common.action_other)"
}
}
}
}
+647 -22
View File
@@ -24,7 +24,7 @@
"dismiss": "hylkää",
"favorite": "suosikki",
"filter_one": "suodatin",
"filter_other": "suodatinta",
"filter_other": "suodattimet",
"filters": "suodattimet",
"forceRestartRequired": "käynnistä uudelleen ottaaksesi muutokset käyttöön… sulje ilmoitus käynnistääksesi uudelleen",
"gap": "väli",
@@ -38,15 +38,15 @@
"name": "nimi",
"no": "ei",
"none": "ei mitään",
"noResultsFromQuery": "kysely palautti ei tuloksia",
"noResultsFromQuery": "kysely ei tuottanut tuloksia",
"note": "huomautus",
"ok": "ok",
"owner": "omistaja",
"path": "reitti",
"path": "polku",
"preview": "esikatsele",
"previousSong": "edellinen $t(entity.track_one)",
"resetToDefault": "palauta oletusarvoihin",
"restartRequired": "uudelleen käynnistys vaaditaan",
"restartRequired": "vaatii uudelleenkäynnistyksen",
"right": "oikea",
"save": "tallenna",
"saveAndReplace": "tallenna ja korvaa",
@@ -57,7 +57,7 @@
"yes": "kyllä",
"close": "sulje",
"descending": "laskeva",
"biography": "elämänkerta",
"biography": "biografia",
"cancel": "peruuta",
"bpm": "bpm",
"decrease": "pienennä",
@@ -83,45 +83,54 @@
"rating": "arvostelu",
"refresh": "virkistä",
"reset": "nollaa",
"playerMustBePaused": "soitin täytyy olla pysäytetty"
"playerMustBePaused": "soittimen täytyy olla pysäytetty",
"translation": "käännös",
"albumGain": "albumin vahvistus (gain)",
"albumPeak": "albumin huippu (peak)",
"trackGain": "raidan vahvistus (gain)",
"trackPeak": "kappaleen huippu (peak)"
},
"entity": {
"album_one": "albumi",
"album_other": "albumia",
"albumArtist_one": "albumi artisti",
"albumArtist_other": "albumi artistia",
"album_other": "albumit",
"albumArtist_one": "albumin artisti",
"albumArtist_other": "albumin artistit",
"artistWithCount_one": "{{count}} artisti",
"artistWithCount_other": "{{count}} artistia",
"playlist_one": "soittolista",
"playlist_other": "soittolistaa",
"playlist_other": "soittolistat",
"playlistWithCount_one": "{{count}} soittolista",
"playlistWithCount_other": "{{count}} soittolistaa",
"albumArtistCount_one": "{{count}} albumi artisti",
"albumArtistCount_other": "{{count}} albumi artistia",
"albumArtistCount_one": "{{count}} albumin artisti",
"albumArtistCount_other": "{{count}} albumin artistia",
"albumWithCount_one": "{{count}} albumi",
"albumWithCount_other": "{{count}} albumia",
"artist_one": "artisti",
"artist_other": "artistia",
"artist_other": "artistit",
"favorite_one": "suosikki",
"favorite_other": "suosikkia",
"favorite_other": "suosikit",
"folder_one": "kansio",
"folder_other": "kansiota",
"folder_other": "kansiot",
"folderWithCount_one": "{{count}} kansio",
"folderWithCount_other": "{{count}} kansiota",
"genre_one": "genre",
"genre_other": "genreä",
"genre_other": "genret",
"genreWithCount_one": "{{count}} genre",
"genreWithCount_other": "{{count}} genreä",
"smartPlaylist": "älykäs $t(entity.playlist_one)",
"track_one": "raita",
"track_other": "raitaa",
"track_other": "raidat",
"trackWithCount_one": "{{count}} raita",
"trackWithCount_other": "{{count}} raitaa"
"trackWithCount_other": "{{count}} raitaa",
"play_one": "{{count}} toisto",
"play_other": "{{count}} toistoa",
"song_one": "kappale",
"song_other": "kappaleet"
},
"action": {
"clearQueue": "tyhjennä jono",
"createPlaylist": "luo $t(entity.playlist_one)",
"deselectAll": "poista valinta kaikista",
"deselectAll": "poista kaikkien valinta",
"editPlaylist": "muokkaa $t(entity.playlist_one)",
"removeFromQueue": "poista jonosta",
"viewPlaylists": "katsele $t(entity.playlist_other)",
@@ -132,13 +141,629 @@
"goToPage": "mene sivulle",
"moveToBottom": "siirry pohjalle",
"moveToTop": "siirry ylös",
"addToFavorites": "lisää $t(entity.favorite_other)",
"addToPlaylist": "lisää $t(entity.playlist_one)",
"addToFavorites": "lisää kohteeseen $t(entity.favorite_other)",
"addToPlaylist": "lisää kohteeseen $t(entity.playlist_one)",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "poista kohteesta $t(entity.favorite_other)",
"toggleSmartPlaylistEditor": "kytke $t(entity.smartPlaylist) editori",
"deletePlaylist": "poista $t(entity.playlist_one)",
"removeFromPlaylist": "poista kohteesta $t(entity.playlist_one)",
"setRating": "aseta arvostelu"
"setRating": "aseta arvostelu",
"moveToNext": "siirry seuraavaan"
},
"error": {
"remoteEnableError": "virhe tapahtui yrittäessä $t(common.enable) etäpalvelinta",
"remotePortError": "virhe tapahtui etäpalvelimen porttia määrittäessä",
"serverNotSelectedError": "palvelinta ei ole valittu",
"remoteDisableError": "virhe tapahtui yrittäessä $t(common.disable) etäpalvelinta",
"serverRequired": "palvelin vaadittu",
"systemFontError": "virhe tapahtui yrittäessä hakea järjestelmän fontteja",
"sessionExpiredError": "istuntosi on vanhentunut",
"genericError": "tapahtui virhe",
"invalidServer": "virheellinen palvelin",
"audioDeviceFetchError": "äänentoistolaitteita haettaessa tapahtui virhe",
"authenticationFailed": "tunnistautuminen epäonnistui",
"badAlbum": "näet tämän sivun koska tämä kappale ei ole osa albumia. Näet tämän todennäköisesti jos kappaleesi on päämusiikkikansiosi juuressa. jellyfin ryhmittää kappaleet vain jos ne ovat kansiossa.",
"apiRouteError": "pyynnön reititys epäonnistui",
"credentialsRequired": "käyttäjätunnuksia vaaditaan",
"loginRateError": "liian monta kirjautumisyritystä, kokeile muutaman sekuntin päästä uudestaan",
"mpvRequired": "MPV vaadittu",
"networkError": "verkkoyhteysvirhe",
"openError": "tiedostoa ei voitu avata",
"localFontAccessDenied": "paikallisiin fontteihin pääsy on kielletty",
"playbackError": "mediaa toistaessa tapahtui virhe",
"remotePortWarning": "käynnistä palvelin uudestaan ottaaksesi uuden portin käyttöön",
"endpointNotImplementedError": "päätepiste {{endpoint}} ei ole toteutettu {{serverType}} varten"
},
"filter": {
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"bitrate": "bittinopeus",
"bpm": "lyöntiä minuutissa (bpm)",
"channels": "$t(common.channel_other)",
"title": "otsikko",
"playCount": "toistomäärä",
"dateAdded": "lisätty päivänä",
"lastPlayed": "viimeksi toistettu",
"mostPlayed": "eniten toistettu",
"isRecentlyPlayed": "on äskettäin toistettu",
"rating": "arvostelu",
"recentlyAdded": "äskettäin lisätty",
"recentlyUpdated": "äskettäin päivitetty",
"releaseDate": "julkaisupäivä",
"toYear": "vuoteen",
"releaseYear": "julkaisuvuosi",
"search": "haku",
"trackNumber": "raita",
"isPublic": "on julkinen",
"genre": "$t(entity.genre_one)",
"favorited": "suosikeissa",
"fromYear": "vuodelta",
"isRated": "on arvosteltu",
"recentlyPlayed": "äskettäin toistetut",
"albumCount": "$t(entity.album_other) määrä",
"disc": "levy",
"duration": "kesto",
"id": "tunnus",
"random": "satunnainen",
"isFavorited": "on suosikeissa",
"isCompilation": "on osa kokoelmaa",
"comment": "kommentti",
"communityRating": "yhteisön arvostelu",
"criticRating": "kriitikon arvostelu",
"name": "nimi",
"note": "muistiinpano",
"owner": "$t(common.owner)",
"path": "polku",
"songCount": "kappalemäärä"
},
"form": {
"addServer": {
"input_legacyAuthentication": "käytä vanhaa kirjautumistapaa",
"ignoreCors": "ohita CORS ($t(common.restartRequired))",
"input_name": "palvelimen nimi",
"ignoreSsl": "ohita SSL ($t(common.restartRequired))",
"input_savePassword": "tallenna salasana",
"input_url": "url-osoite",
"title": "lisää palvelin",
"error_savePassword": "salasanaa tallentaessa tapahtui virhe",
"input_password": "salasana",
"input_username": "käyttäjänimi",
"success": "palvelin lisätty onnistuneesti"
},
"createPlaylist": {
"input_public": "julkinen",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"success": "$t(entity.playlist_one) luotu onnistuneesti",
"title": "luo $t(entity.playlist_one)",
"input_description": "$t(common.description)"
},
"addToPlaylist": {
"input_skipDuplicates": "ohita kaksoiskappaleet",
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) lisätty $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "lisää soittolistalle $t(entity.playlist_one)",
"input_playlists": "$t(entity.playlist_other)"
},
"updateServer": {
"success": "palvelin on päivitetty onnistuneesti",
"title": "päivitä palvelin"
},
"deletePlaylist": {
"success": "$t(entity.playlist_one) poistettu onnistuneesti",
"title": "poista $t(entity.playlist_one)",
"input_confirm": "kirjoita soittolistan $t(entity.playlist_one) nimi vahvistaaksesi"
},
"editPlaylist": {
"success": "$t(entity.playlist_one) päivitetty onnistuneesti",
"title": "muokkaa $t(entity.playlist_one)",
"publicJellyfinNote": "Jellyfin ei jostain syystä kerro onko soittolista julkinen vai ei. Jos haluat sen pysyvän julkisena, pidä seuraava valinta valittuna"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "sanojen haku"
},
"shareItem": {
"createFailed": "jaon luonti epäonnistui (onko jako päällä?)",
"allowDownloading": "salli lataus",
"description": "kuvaus",
"setExpiration": "aseta vanheneminen",
"success": "jakolinkki kopioitu leikepöydälle (tai klikkaa tästä avataksesi)",
"expireInvalid": "vanhetumisen pitää olla tulevaisuudessa"
},
"queryEditor": {
"input_optionMatchAny": "sovita joku",
"input_optionMatchAll": "sovita kaikki"
}
},
"setting": {
"clearCacheSuccess": "välimuisti on tyhjennetty onnistuneesti",
"artistConfiguration_description": "valise näytettävät asiat ja niiden järjestys albumin artistin sivulla",
"audioDevice": "äänilaite",
"clearQueryCache_description": "feishinin 'pehmeä tyhjennys'. tämä tyhjentää soittolistat, raitojen metadatat ja tallennetut sanoitukset. asetukset, palvelimien käyttäjätunnukset ja välimuistissa olevat kuvat säilyvät",
"crossfadeDuration": "ristihäivytyksen kesto",
"audioPlayer_description": "valitse toistossa käytettävä soitin",
"buttonSize": "soittimen palkin nappien koko",
"buttonSize_description": "soittimen palkin nappien koko",
"clearCache": "tyhjennä selaimen välimuisti",
"clearQueryCache": "tyhjennä feishinin välimuisti",
"crossfadeDuration_description": "aseta ristihäivytystehosteen kesto",
"applicationHotkeys_description": "aseta sovelluksen pikanäppäimet. vaihda valintaruutua asettaaksesi valinta globaaliksi pikanäppäimeksi (vain työpöydällä)",
"crossfadeStyle": "ristihäivytyksen tyyli",
"crossfadeStyle_description": "valitse soittimessa käytettävän ristihäivytyksen tyyli",
"contextMenu_description": "mahdollistaa sinun piilottaa asiat, jotka näytetään valikossa klikatessasi objektia hiiren väärällä painikkella. poistetut valinnat piilotetaan",
"customCssEnable_description": "mahdollista oman css:n kirjoittaminen.",
"accentColor": "korostusväri",
"customCssEnable": "käytä omaa css:ää",
"albumBackgroundBlur_description": "säätää albumin taustakuvan sumennuksen määrää",
"audioExclusiveMode_description": "käytä yksinomaista ulostulotilaa. Tässä tilassa järjestelmä on yleensä lukittuna ja vain mpv voi tuottaa ääntä",
"albumBackgroundBlur": "albumin taustakuvan sumennuksen koko",
"clearCache_description": "feishinin 'kova tyhjennys'. feishinin välimuistin lisäksi tyhjennä selaimen välimuisti (tallennetut kuvat ja muut kohteet). palvelimien käyttäjättunnukset ja asetukset säilyvät",
"audioExclusiveMode": "äänen yksinomainen tila",
"audioPlayer": "soitin",
"contextMenu": "kontekstivalikon (hiiren väärä näppäin) asetukset",
"accentColor_description": "aseta sovelluksen korostusväri",
"albumBackground_description": "lisää taustakuva albumin sivuille, jotka sisältävät albumin kuvitusta",
"artistConfiguration": "albumin artistin sivun hallinta",
"audioDevice_description": "valitse toistossa käytettävä äänilaite (vain verkkosoittimessa)",
"applicationHotkeys": "sovelluksen pikanäppäimet",
"albumBackground": "albumin taustakuva",
"customCss": "oma css",
"customFontPath_description": "asettaa polun mukautetulle fontille jota sovellus käyttää",
"homeConfiguration": "koti sivun muokkaus",
"homeConfiguration_description": "määritä mitä osioita näkyy, ja missä järjestyksessä, koti sivulla",
"gaplessAudio_optionWeak": "heikko (suositus)",
"genreBehavior_description": "määrittää avautuuko generä painettaessa oletuksena ääniraita vaiko albumi listassa",
"hotkey_browserBack": "selain takaisin",
"hotkey_playbackPlay": "toista",
"hotkey_playbackPlayPause": "toista / tauko",
"hotkey_playbackPrevious": "edellinen ääniraita",
"hotkey_rate3": "arvostelu 3 tähteä",
"hotkey_playbackStop": "lopeta",
"hotkey_rate4": "arvostelu 4 tähteä",
"hotkey_rate1": "arvostelu 1 tähti",
"hotkey_rate2": "arvostelu 2 tähteä",
"hotkey_unfavoriteCurrentSong": "poista suosikeista $t(common.currentSong)",
"fontType_description": "sisäänrakennettu fontti valitsee yhden Feishinin tuomista fonteista. järjestelmän fontti antaa sinun valita minkä tahansa käyttöjärjestelmään asennetun fontin. mukautettu antaa sinun tuoda oman fontin",
"fontType_optionBuiltIn": "sisäänrakennettu fontti",
"fontType_optionSystem": "järjestelmän fontti",
"fontType_optionCustom": "mukautettu fontti",
"hotkey_favoriteCurrentSong": "lisää suosikiksi $t(common.currentSong)",
"hotkey_favoritePreviousSong": "lisää suosikiksi $t(common.previousSong)",
"hotkey_rate5": "arvostelu 5 tähteä",
"hotkey_skipBackward": "ohita taaksepäin",
"hotkey_skipForward": "ohita eteenpäin",
"font": "kirjaisin",
"font_description": "asettaa fontin jota sovellus käyttää",
"discordApplicationId": "{{discord}} sovelluksen tunnus",
"hotkey_globalSearch": "globaali haku",
"hotkey_playbackNext": "seuraava ääniraita",
"hotkey_browserForward": "selain eteenpäin",
"hotkey_playbackPause": "tauko",
"hotkey_localSearch": "hae sivulta",
"customFontPath": "mukautetun fontin polku",
"fontType": "fonttityyppi",
"hotkey_unfavoritePreviousSong": "poista suosikeista $t(common.previousSong)",
"customCss_description": "mukautettu CSS-sisältö. Huomautus: content- ja etä-URL-osoitteet ovat estettyjä ominaisuuksia. Esikatselu sisällöstäsi on alla. Lisäkenttiä, joita et ole määrittänyt, on näkyvissä puhdistuksen vuoksi.",
"customCssNotice": "Varoitus: vaikka jonkinlainen puhdistus onkin tehty (url()- ja content:-komentojen estäminen), mukautetun CSS:n käyttäminen voi silti aiheuttaa riskejä muuttamalla käyttöliittymää.",
"disableLibraryUpdateOnStartup": "poista uusimman version tarkistus käynnistyksen yhteydessä käytöstä",
"disableAutomaticUpdates": "poista automaattiset päivitykset käytöstä",
"discordIdleStatus": "näytä rich presencen käyttämätön tila",
"discordIdleStatus_description": "kun käytössä, päivitä tila kun soitin on käyttämättömänä",
"doubleClickBehavior": "lisää kaikki haetut kappaleet soittojonoon tuplaklikkauksella",
"discordUpdateInterval_description": "päivitysväli sekunnteina (vähintään 15 sekunttia)",
"discordRichPresence": "{{discord}} rich presence",
"discordRichPresence_description": "ota toiston tila käyttöön {{discord}}n rich presence-toiminnossa. Kuvakkeiden avaimet ovat {{icon}}, {{playing}} ja {{paused}}. ",
"discordUpdateInterval": "{{discord}} rich presencen päivitysväli",
"enableRemote": "aktivoi etäohjauspalvelin",
"externalLinks_description": "ottaa ulkoiset linkit (Last.fm, MusicBrainz) artistien/albumien sivuilla",
"exitToTray": "sulje tehtäväpalkkiin",
"doubleClickBehavior_description": "jos päällä, kaikki hakutuloksissa olevat kappaleet lisätään soittojonoon. muuten vain napsautettu kappale lisätään jonoon",
"discordApplicationId_description": "{{discord}}n ohjelma-ID rich presenceä varten (oletuksena {{defaultId}})",
"enableRemote_description": "aktivoi etäohjauspalvelimen, jolla muut laitteet voivat ohjata sovellusta",
"externalLinks": "näytä ulkoiset linkit",
"exitToTray_description": "sovellus suljetaan tehtäväpalkkiin",
"discordListening_description": "näytä status kuuntelee pelaa sijaan",
"discordListening": "näytä status kuuntelee",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"lastfmApiKey_description": "API-avain {{lastfm}}:lle. tarvitaan kansikuvia varten",
"passwordStore_description": "mitä salasanojen/avaimien tallennusta käytetään. muuta tätä, jos sinulla on ongelmia salasanojen tallennuksessa.",
"floatingQueueArea_description": "näyttää ikonin ikkunan oikealla reunalla jonon katselua varten",
"homeFeature_description": "ohjaa näytetäänkö suuri esittelykaruselli kotisivulla",
"hotkey_rate0": "arvostelun tyhjennys",
"hotkey_togglePreviousSongFavorite": "vaihda $t(common.previousSong) suosikkiasetus",
"imageAspectRatio_description": "jos käytössä, kansikuvat näytetään niiden alkuperäisellä kuvasuhteella. jos kuvasuhde ei ole 1:1, jäljelle jäävä tila jää tyhjäksi",
"language_description": "asettaa sovelluksen kielen $t(common.restartRequired)",
"lyricFetch": "hae sanoitukset internetistä",
"lyricFetchProvider_description": "valitse lähteet sanoituksien hakua varten. lähteiden järjestys on se järjestys, jossa ne tiedustellaan",
"minimumScrobblePercentage": "pienin skrobblauksen kesto (prosenttia)",
"mpvExecutablePath": "mpv:n suoritettavan tiedoston polku",
"mpvExecutablePath_description": "asettaa mpv:n suoritettavan tiedoston polun. ollessa tyhjä, käytetään oletuspolkua",
"mpvExtraParameters_help": "yksi per rivi",
"playButtonBehavior_optionPlay": "$t(player.play)",
"genreBehavior": "genre-sivun oletustoiminta",
"globalMediaHotkeys": "globaalit median pikanäppäimet",
"globalMediaHotkeys_description": "ota käyttöön tai poista käytöstä järjestelmän median pikanäppäinten käyttö toiston hallintaa",
"hotkey_toggleCurrentSongFavorite": "vaihda $t(common.currentSong) suosikkiasetus",
"imageAspectRatio": "käytä alkuperäistä kansikuvan kuvasuhdetta",
"language": "kieli",
"lyricOffset_description": "siirrä sanoituksia valitun ajan millisekuntteina",
"minimizeToTray": "pienennä ilmaisinalueelle",
"gaplessAudio_description": "asettaa tauottoman toiston asetukset mpv:hen",
"hotkey_volumeDown": "äänenvoimakkuuden vähentäminen",
"hotkey_zoomIn": "lähennä",
"lyricFetch_description": "hae sanoitukset eri lähteistä internetissä",
"lyricFetchProvider": "lähteet sanoituksia varten",
"lyricOffset": "sanotuksien siirto (ms)",
"mpvExtraParameters": "mpv:n parametrit",
"followLyric": "seuraa lyriikoita",
"followLyric_description": "vieritä lyriikat tämänhetkiseen paikkaan",
"hotkey_toggleQueue": "vaihda jono",
"minimumScrobblePercentage_description": "vähimmäisprosentti kappaleesta, joka on soitettava ennen kuin se skrobblataan",
"minimumScrobbleSeconds": "pienin skrobblaus (sekunttia)",
"minimumScrobbleSeconds_description": "vähimmäisaika kappaleesta, joka on soitettava ennen kuin se skrobblataan",
"passwordStore": "salasanojen/avaimien tallennus",
"hotkey_volumeUp": "äänenvoimakkuuden lisääminen",
"hotkey_toggleShuffle": "vaihda sekoitus",
"hotkey_volumeMute": "mykistäminen",
"lastfmApiKey": "{{lastfm}} API-avain",
"minimizeToTray_description": "pienennä sovellus ilmaisinalueelle",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"hotkey_zoomOut": "loitonna",
"floatingQueueArea": "näytä kelluvan jonon avausalue",
"homeFeature": "kodin esittelykaruselli",
"hotkey_toggleFullScreenPlayer": "vaihda kokonäytön toistin",
"hotkey_toggleRepeat": "vaihda kertaus",
"gaplessAudio": "tauoton toisto",
"transcodeFormat_description": "valitsee transkoodattavan formaatin. jätä tyhjäksi palvelimen valintaa varten",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"themeDark": "teema (tumma)",
"transcodeNote": "tulee voimaan 1 (web) - 2 (mpv) kappaleen jälkeen",
"translationApiKey_description": "API-avain käännöstä varten (tukee vain globaalia palvelun palvelupistettä)",
"playbackStyle_description": "valitse toiston tyyli, jota käytetään soittimessa",
"transcode_description": "ottaa transkoodaksen käyttöön eri formaateille",
"transcodeBitrate": "transkoodattava bittinopeus",
"translationApiProvider": "käännös-API:n palveluntarjoaja",
"trayEnabled_description": "näytä/piilota järjestelmäpalkin kuvake/valikko. jos poistettu käytöstä, myös pienennä/sulje järjestelmäpalkkiin -toiminto poistetaan käytöstä",
"windowBarStyle_description": "valitse ikkunapalkin tyyli",
"webAudio": "käytä web-ääntä",
"windowBarStyle": "ikkunapalkin tyyli",
"zoom": "zoomausprosentti",
"playbackStyle": "toiston tyyli",
"remotePassword": "kauko-ohjauspalvelimen salasana",
"remoteUsername_description": "asettaa käyttäjänimen kauko-ohjauspalvelimelle. jos sekä käyttäjätunnus, että salasana ovat tyhjänä, todennus poistetaan käytöstä",
"skipPlaylistPage": "ohita soittolistojen sivu",
"themeDark_description": "asettaa tumman teeman käytettäväksi sovelluksessa",
"playbackStyle_optionCrossFade": "ristivaihto",
"playbackStyle_optionNormal": "normaali",
"playButtonBehavior": "toistopainikkeen toiminta",
"playButtonBehavior_description": "asettaa toistopainikkeen oletustoiminnan lisättäessä kappaleita jonoon",
"remotePort": "kauko-ohjauspalvelimen portti",
"replayGainMode": "{{ReplayGain}} tila",
"sampleRate_description": "valitse käytettävä näytteenottotaajuus, jos valittu näytetaajuus poikkeaa nykyisen median taajuudesta. arvo, joka on alle 8 000, käyttää oletustaajuutta",
"skipDuration": "ohituksen kesto",
"sidePlayQueueStyle_description": "asettaa tyylin sivupalkin toistojonolle",
"sidePlayQueueStyle_optionAttached": "liitetty",
"sidePlayQueueStyle_optionDetached": "irrotettu",
"startMinimized_description": "käynnistä sovellus järjestelmäpalkissa",
"theme": "teema",
"useSystemTheme_description": "seuraa järjestelmän määrittämää asetusta vaalealle tai tummalle asetukselle",
"remoteUsername": "kauko-ohjauspalvelimen käyttäjänimi",
"remotePort_description": "asettaa kauko-ohjauspalvelimen portin",
"remotePassword_description": "asettaa kauko-ohjauspalvelimen salasanan. Nämä tunnukset siirretään oletuksena turvattomasti, joten sinun kuuluisi käyttää uniikkia salasanaa, josta et välitä",
"replayGainClipping": "{{ReplayGain}} leikkaus",
"replayGainClipping_description": "Estää {{ReplayGain}}n aiheuttaman leikkauksen laskemalla vahvistusta automaatisesti",
"replayGainFallback": "{{ReplayGain}} palautus",
"playerAlbumArtResolution_description": "suurien kansikuvien resoluutio soittimen esikatselussa. suurempi tekee niistä terävempiä, mutta voi hidastaa latausta. oletuksena on 0, joka tarkoittaa automaattista",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainPreamp": "{{ReplayGain}} esivahvistus (dB)",
"scrobble_description": "skrobblaa toistot mediapalvelimellesi",
"replayGainPreamp_description": "säätää esivahvistuksen määrää {{ReplayGain}} arvoon",
"showSkipButtons": "näytä ohituspainikkeet",
"showSkipButtons_description": "näytä tai piilota soitinpalkin ohituspainikkeet",
"showSkipButton": "näytä ohituspainikkeet",
"showSkipButton_description": "näytä tai piilota soitinpalkin ohituspainikkeet",
"sidebarPlaylistList": "sivupakin soittolistojen lista",
"skipDuration_description": "asettaa ohitettavan ajan käytettäessä soitinpalkin ohituspainikkeita",
"volumeWidth": "äänenvoimakkuuden säätimen leveys",
"sidebarCollapsedNavigation_description": "näytä tai piilota navigointi romautetussa sivupalkissa",
"sidebarConfiguration": "sivupalkin asetukset",
"sidebarConfiguration_description": "valitse kohteet ja niiden järjestys sivupalkissa",
"volumeWidth_description": "äänenvoimakkuuden säätimen leveys",
"playerAlbumArtResolution": "soittimen kansikuvien resoluutio",
"playerbarOpenDrawer": "toistipalkin kokoruudun kytkin",
"playerbarOpenDrawer_description": "sallii toistopalkin klikkaamisen avaamaan kokonäytön soittimen",
"replayGainFallback_description": "asetettava vahvistus desibelinä (dB), jos tiedostolla ei ole {{ReplayGain}} tageja",
"replayGainMode_description": "säätää äänenvoimmakkuutta {{ReplayGain}} arvojen mukaisesti tiedoston metadatasta",
"sampleRate": "näytteenottotaajuus",
"savePlayQueue": "tallenna toistojono",
"savePlayQueue_description": "tallenna toistojono, kun sovellus suljetaan ja avaa se uudestaan, kun sovellus avataan",
"scrobble": "skrobblaus",
"sidebarCollapsedNavigation": "sivupalkin (romautettu) navigointi",
"sidebarPlaylistList_description": "näytä tai piilota soittolistojen lista sivupalkissa",
"sidePlayQueueStyle": "sivupalkin jonon tyyli",
"skipPlaylistPage_description": "navigoidessa soittolistaan, mene soittolistan kappaleiden listaan oletussivun sijaan",
"theme_description": "asettaa ohjelmassa käytettävän teeman",
"themeLight": "teema (vaalea)",
"themeLight_description": "asettaa vaalean teeman käytettäväksi sovelluksessa",
"transcode": "ota transkoodaus käyttöön",
"transcodeBitrate_description": "valitsee transkoodattavan bittinopeuden. 0 tarkoittaa palvelimen valintaa",
"transcodeFormat": "transkoodattava formaatti",
"translationApiProvider_description": "palveluntarjoajan API käännöstä varten",
"translationApiKey": "käännöksen API-avain",
"translationTargetLanguage": "käännöksen kohdekieli",
"translationTargetLanguage_description": "kohdekieli käännöstä varten",
"trayEnabled": "näytä järjestelmäpalkki",
"volumeWheelStep_description": "äänenvoimakkuuden muutoksen suuruus rullattaessa hiiren rullalla äänenvoimakkuuden säätimen päällä",
"zoom_description": "asettaa sovelluksen zoomausprosentin",
"webAudio_description": "käytä web-ääntä. tämä mahdollistaa edistyneet ominaisuudet, kuten replaygainin. poista käytöstä, jos koet ongelmia",
"startMinimized": "käynnistä pienennettynä",
"useSystemTheme": "käytä järjestelmän teemaa",
"volumeWheelStep": "äänenvoimakkuusrullan askel",
"discordServeImage": "jaa {{discord}} kuvat palvelimelta",
"discordServeImage_description": "jaa kansikuvat {{discord}}n rich presenceä varten suoraan palvelimelta. saatavilla vain jellyfinille ja navidromelle"
},
"page": {
"itemDetail": {
"copiedPath": "polku on kopioitu onnistuneesti",
"copyPath": "kopioi reitti leikepöytälle",
"openFile": "näytä kappale tiedostonhallinnassa"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "siirrä kohteesta $t(entity.artist_one)",
"moreFromGeneric": "listää kohteesta {{item}}",
"released": "julkaistu"
},
"albumList": {
"artistAlbums": "artistin {{artist}} albumit",
"genreAlbums": "\"{{genre}}\"$t(entity.album_other)",
"title": "$t(entity.album_other)"
},
"appMenu": {
"goBack": "mene takaisin",
"openBrowserDevtools": "avaa selaimen kehitystyökalut",
"quit": "$t(common.quit)",
"selectServer": "valitse palvelin",
"settings": "$t(common.setting_other)",
"expandSidebar": "laajenna sivupalkki",
"goForward": "mene eteenpäin",
"manageServers": "hallitse palvelimia",
"collapseSidebar": "kutista sivupalkki",
"version": "versio {{version}}"
},
"contextMenu": {
"playSimilarSongs": "$t(player.playSimilarSongs)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"addToPlaylist": "$t(action.addToPlaylist)",
"createPlaylist": "$t(action.createPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"numberSelected": "{{count}} valittuna",
"play": "$t(player.play)",
"download": "lataa",
"moveToBottom": "$t(action.moveToBottom)",
"moveToTop": "$t(action.moveToTop)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"shareItem": "jaa kohde",
"showDetails": "lisätietoa",
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"moveToNext": "$t(action.moveToNext)",
"removeFromQueue": "$t(action.removeFromQueue)"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"albums": "$t(entity.album_other)",
"settings": "$t(common.setting_other)",
"shared": "$t(entity.playlist_other) jaettu",
"tracks": "$t(entity.track_other)",
"artists": "$t(entity.artist_other)",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"nowPlaying": "nyt soi",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)"
},
"setting": {
"generalTab": "yleinen",
"windowTab": "ikkuna",
"hotkeysTab": "pikanäppäimet",
"playbackTab": "toisto",
"advanced": "edistyneet"
},
"fullscreenPlayer": {
"upNext": "seuraavaksi",
"visualizer": "visualisaattori",
"noLyrics": "sanoja ei löytynyt",
"config": {
"showLyricMatch": "näytä sanojen yhteneväisyys",
"showLyricProvider": "näytä sanojen tarjoaja",
"lyricGap": "sanojen rako",
"synchronized": "synkronoitu",
"lyricSize": "sanojen koko",
"opacity": "läpinäkyvyys",
"unsynchronized": "synkronoimaton",
"useImageAspectRatio": "käytä kuvan kuvasuhdetta",
"dynamicBackground": "liikkuva tausta",
"dynamicImageBlur": "kuvan sumennuksen koko",
"dynamicIsImage": "käytä taustakuvaa",
"lyricOffset": "sanojen kompensointi (ms)",
"followCurrentLyric": "seuraa nykyisiä sanoja",
"lyricAlignment": "sanojen kohdistus"
},
"lyrics": "sanat",
"related": "liittyvät"
},
"genreList": {
"showAlbums": "näytä $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "näytä $t(entity.genre_one) $t(entity.track_other)",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
"commands": {
"searchFor": "hae {{query}}",
"serverCommands": "palvelimen komennot",
"goToPage": "mene sivulle"
},
"title": "komennot"
},
"home": {
"explore": "tutki kirjastotasi",
"recentlyPlayed": "hiljattain soitetut",
"title": "$t(common.home)",
"mostPlayed": "eniten soitetut",
"newlyAdded": "hiljattain lisätyt julkaisut"
},
"albumArtistDetail": {
"about": "{{artist}}{sta/stä",
"viewDiscography": "katsele diskografiaa",
"relatedArtists": "liittyvät $t(entity.artist_other)",
"appearsOn": "esiintyy",
"topSongs": "parhaat kappaleet",
"topSongsFrom": "parhaat kappaleet albumilta {{title}}",
"recentReleases": "hiljattaiset julkaisut",
"viewAll": "katsele kaikkia",
"viewAllTracks": "katsele kaikkia $t(entity.track_other)"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"manageServers": {
"title": "hallitse palvelimia",
"serverDetails": "palvelimen lisätiedot",
"url": "URL",
"username": "käyttäjänimi",
"editServerDetailsTooltip": "muokkaa palvelimen lisätietoja",
"removeServer": "etäpalvelin"
},
"playlist": {
"reorder": "uudelleenjärjestely mahdollista vain, kun järjestellään id:n mukaan"
},
"trackList": {
"artistTracks": "artistin {{artist}} kappaleet",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)"
}
},
"player": {
"addLast": "lisää viimeinen",
"addNext": "lisää seuraava",
"favorite": "suosikki",
"queue_moveToTop": "siirrä valittu alas",
"queue_remove": "poista valittu",
"repeat": "kertaus",
"previous": "edellinen",
"queue_clear": "tyhjennä jono",
"skip": "ohita",
"skip_forward": "ohita eteenpäin",
"stop": "pysäytä",
"skip_back": "ohita taaksepäin",
"unfavorite": "poista suosikeista",
"playbackFetchNoResults": "kappaleita ei löytynyt",
"queue_moveToBottom": "siittä valittu ylös",
"pause": "tauota",
"playbackSpeed": "toistonopeus",
"repeat_all": "kertaa kaikki",
"playbackFetchCancel": "tämä vie aikaa... sulje ilmoitus peruaksesi",
"mute": "mykistä",
"shuffle": "soita sekoitettuna",
"next": "seuraava",
"play": "toista",
"playbackFetchInProgress": "ladataan kappaleita…",
"viewQueue": "katsele jonoa",
"muted": "mykistetty",
"playRandom": "toista satunnainen",
"playSimilarSongs": "toista samanlaisia kappaleita",
"repeat_off": "kertaus pois päältä",
"shuffle_off": "sekoitus pois päältä",
"toggleFullscreenPlayer": "vaihda kokoruudun soittimeen"
},
"table": {
"config": {
"general": {
"gap": "$t(common.gap)",
"size": "$t(common.size)",
"autoFitColumns": "sovita sarakkeet",
"followCurrentSong": "seuraa nykyistä kappaletta",
"displayType": "näytön tyyppi",
"itemGap": "kohteiden väli (px)",
"itemSize": "kohteiden koko (px)",
"tableColumns": "taulukon sarakkeet"
},
"label": {
"channels": "$t(common.channel_other)",
"trackNumber": "raidan numero",
"album": "$t(entity.album_one)",
"actions": "$t(common.action_other)",
"codec": "$t(common.codec)",
"dateAdded": "lisäyspäivämäärä",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"discNumber": "levyn numero",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"lastPlayed": "viimeksi soitettu",
"note": "$t(common.note)",
"titleCombined": "$t(common.title) (yhdistetty)",
"rowIndex": "rivin indeksi",
"biography": "$t(common.biography)",
"bitrate": "$t(common.bitrate)",
"bpm": "$t(common.bpm)",
"genre": "$t(entity.genre_one)",
"playCount": "toistojen lukumäärä",
"rating": "$t(common.rating)",
"releaseDate": "julkaisupäivämäärä",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"year": "$t(common.year)"
},
"view": {
"table": "taulukko",
"card": "kortti",
"poster": "juliste"
}
},
"column": {
"releaseYear": "vuosi",
"bpm": "bpm",
"artist": "$t(entity.artist_one)",
"biography": "biografia",
"dateAdded": "lisäyspäivämäärä",
"album": "albumi",
"albumArtist": "albumin artisti",
"lastPlayed": "viimeksi toistettu",
"path": "polku",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "nimi",
"trackNumber": "raita",
"codec": "$t(common.codec)",
"comment": "kommentti",
"albumCount": "$t(entity.album_other)",
"bitrate": "bittinopeus",
"channels": "$t(common.channel_other)",
"discNumber": "levy",
"favorite": "suosikki",
"genre": "$t(entity.genre_one)",
"playCount": "toistoja",
"rating": "arvostelu",
"releaseDate": "julkaisupäivämäärä"
}
}
}
+14 -5
View File
@@ -53,7 +53,8 @@
"openIn": {
"lastfm": "Ouvrir dans Last.fm",
"musicbrainz": "Ouvrir dans MusicBrainz"
}
},
"moveToNext": "passer au suivant"
},
"common": {
"backward": "en arrière",
@@ -147,7 +148,9 @@
"trackGain": "gain de la piste",
"trackPeak": "crête de la piste",
"codec": "codec",
"translation": "traduction"
"translation": "traduction",
"additionalParticipants": "participants additionnels",
"tags": "tags"
},
"error": {
"remotePortWarning": "redémarrer le serveur pour appliquer le nouveau port",
@@ -171,7 +174,8 @@
"loginRateError": "trop de tentative de connexion, merci de réessayer dans quelques secondes",
"openError": "impossible d'ouvrir le fichier",
"networkError": "une erreur de réseau est survenue",
"badAlbum": "vous voyez cette page parce que cette chanson ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez une chanson qui n'est pas dans votre répertoire de musique. jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\"."
"badAlbum": "vous voyez cette page parce que cette chanson ne fait pas parti d'un album. vous rencontrez probablement cette erreur si vous avez une chanson qui n'est pas dans votre répertoire de musique. jellyfin gère les chansons uniquement si elles sont dans un sous-dossier, qui est lui-même dans un dossier \"Musique(s)\".",
"badValue": "option {{value}} invalide. Cette valeur n'existe plus"
},
"filter": {
"mostPlayed": "plus joués",
@@ -315,7 +319,8 @@
"playSimilarSongs": "$t(player.playSimilarSongs)",
"showDetails": "obtenir des informations",
"download": "télécharger",
"playShuffled": "$t(player.shuffle)"
"playShuffled": "$t(player.shuffle)",
"moveToNext": "$t(action.moveToNext)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
@@ -589,7 +594,11 @@
"transcodeNote": "prend effet après 1 (web) - 2 (mpv) chansons",
"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": "taille 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",
"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"
},
"form": {
"deletePlaylist": {
+254
View File
@@ -0,0 +1,254 @@
{
"action": {
"moveToNext": "ugrás a következőre",
"deletePlaylist": "$t(entity.playlist_one) törlése",
"removeFromFavorites": "eltávolítás innen: $t(entity.favorite_other)",
"setRating": "értékelés",
"viewPlaylists": "$t(entity.playlist_other) megtekintése",
"openIn": {
"lastfm": "Megnyitás Last.fm-ben",
"musicbrainz": "Megnyitás MusicBrainz-ben"
},
"clearQueue": "műsorlista kiürítése",
"createPlaylist": "$t(entity.playlist_one) létrehozása",
"deselectAll": "kijelölések törlése",
"editPlaylist": "$t(entity.playlist_one) szerkesztése",
"goToPage": "oldal meglátogatása",
"moveToBottom": "ugrás az utolsóhoz",
"moveToTop": "ugrás az elsőhöz",
"removeFromPlaylist": "eltávolítás innen: $t(entity.playlist_one)",
"removeFromQueue": "eltávolítás a műsorlistáról",
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) szerkesztője",
"addToFavorites": "$t(entity.favorite_other) kedvelése",
"addToPlaylist": "hozzáadás lejátszási listához: $t(entity.playlist_one)"
},
"common": {
"collapse": "összecsukás",
"currentSong": "jelenlegi: $t(entity.track_one)",
"no": "nem",
"close": "bezárás",
"confirm": "rendben",
"create": "létrehozás",
"codec": "kodek",
"delete": "törlés",
"description": "leírás",
"comingSoon": "hamarosan…",
"decrease": "csökkentés",
"enable": "engedélyes",
"disable": "letiltás",
"disc": "lemez",
"modified": "módosult",
"forceRestartRequired": "a módosítások alkalmazásához újra kell indulnunk... zárd be az értesítést az újraindításhoz",
"home": "főoldal",
"name": "név",
"action_one": "művelet",
"action_other": "műveletek",
"add": "hozzáadás",
"albumGain": "album erősség",
"albumPeak": "album csúcs",
"areYouSure": "biztos vagy benne?",
"ascending": "növekvő",
"backward": "visszafelé",
"biography": "biográfia",
"bitrate": "bitráta",
"cancel": "mégse",
"center": "közép",
"channel_one": "csatorna",
"channel_other": "csatornák",
"clear": "törlés",
"configure": "konfigurálás",
"descending": "csökkenő",
"dismiss": "figyelmen kívül hagyás",
"duration": "hossz",
"edit": "szerkesztés",
"expand": "megnyitás",
"favorite": "kedvenc",
"filter_one": "szűrő",
"filter_other": "szűrők",
"filters": "szűrők",
"forward": "előre",
"gap": "rés",
"increase": "megnövelés",
"left": "bal",
"limit": "korlát",
"manage": "kezelés",
"maximize": "maximalizálás",
"menu": "menü",
"minimize": "minimalizálás",
"mbid": "MusicBrainz azonosító",
"noResultsFromQuery": "nincsenek találatok a keresett kifejezésre",
"note": "jegyzet",
"ok": "rendben",
"owner": "tulajdonos",
"path": "elérési út",
"playerMustBePaused": "a lejátszónak szüneteltetve kell lennie",
"preview": "előnézet",
"previousSong": "előző $t(entity.track_one)",
"quit": "kilépés",
"random": "véletlenszerű",
"refresh": "frissítés",
"reset": "reszetelés",
"resetToDefault": "visszaállítás alapértelmezettekre",
"right": "jobb",
"save": "mentés",
"search": "keresés",
"title": "cím",
"trackNumber": "dalszám",
"unknown": "ismeretlen",
"version": "verzió",
"yes": "igen",
"none": "egyik sem",
"restartRequired": "újraindítás szükséges",
"setting": "beállítás",
"translation": "fordítás",
"rating": "értékelések",
"reload": "újratöltés",
"share": "megosztás",
"sortOrder": "sorrend",
"saveAndReplace": "mentés és felülírás",
"saveAs": "mentés másként",
"size": "méret",
"year": "év",
"trackGain": "dal erősség",
"trackPeak": "dal csúcs"
},
"entity": {
"albumArtist_one": "album szerzője",
"albumArtist_other": "album szerzői",
"albumArtistCount_one": "{{count}} album szerző",
"albumArtistCount_other": "{{count}} album szerzők",
"albumWithCount_one": "{{count}} album",
"albumWithCount_other": "{{count}} album",
"artist_one": "előadó",
"artist_other": "előadók",
"favorite_one": "kedvelés",
"favorite_other": "kedvelések",
"folder_one": "mappa",
"folder_other": "mappák",
"genreWithCount_one": "{{count}} műfaj",
"genreWithCount_other": "{{count}} műfaj",
"track_one": "dal",
"track_other": "dalok",
"song_one": "zene",
"song_other": "zenék",
"album_one": "album",
"album_other": "albumok",
"smartPlaylist": "intelligens $t(entity.playlist_one)",
"artistWithCount_one": "{{count}} előadó",
"artistWithCount_other": "{{count}} előadó",
"playlist_one": "lejátszási lista",
"playlist_other": "lejátszási listák",
"playlistWithCount_one": "{{count}} lejátszási lista",
"playlistWithCount_other": "{{count}} lejátszási lista",
"folderWithCount_one": "{{count}} mappa",
"folderWithCount_other": "{{count}} mappa",
"genre_one": "műfaj",
"genre_other": "műfajok",
"play_one": "{{count}} lejátszás",
"play_other": "{{count}} lejátszás",
"trackWithCount_one": "{{count}} dal",
"trackWithCount_other": "{{count}} dal"
},
"error": {
"apiRouteError": "a kérést nem sikerült célbajuttatni",
"audioDeviceFetchError": "hiba történt a hangeszközök lekérésekor",
"authenticationFailed": "sikertelen hitelesítés",
"credentialsRequired": "hitelesítési adatok szükségesek",
"localFontAccessDenied": "hozzáférés megtagadásra került a helyi betűtípusokhoz",
"networkError": "hálózati hibába ütköztünk",
"openError": "a fájl megnyitása sikertelen volt",
"playbackError": "hiba történt a média lejátszásakor",
"remoteEnableError": "hiba történt a távoli szerver műveletkor: $t(common.enable)",
"remotePortError": "hiba történt a távoli szerver PORT-jának beállításakor",
"remotePortWarning": "indítsd újra a szervert az új PORT használatához",
"genericError": "hiba történt",
"endpointNotImplementedError": "a(z) {{endpoint}} végpont nincs implementálva a következőhöz: {{serverType}}",
"badAlbum": "azért látod ezt az oldalt mert ez a zeneszám nem része egy albumnak. ez általában akkor történik amikor egy szám a zenekönyvtárad gyökerébe kerül. a Jellyfin csak mappákba rendezett számokat csoportosít",
"loginRateError": "túl sok bejelentkezési kísérlet, kérlek próbáld újra pár másodperc múlva",
"mpvRequired": "MPV szükséges",
"invalidServer": "érvénytelen szerver",
"remoteDisableError": "hiba történt a távoli szerver műveletkor: $t(common.disable)",
"sessionExpiredError": "a munkameneted lejárt",
"systemFontError": "hiba történt a rendszer betűtípusainak lekérésekor",
"serverRequired": "szerver szükséges",
"serverNotSelectedError": "nincs szerver kiválasztva"
},
"filter": {
"albumCount": "$t(entity.album_other) darab",
"bitrate": "bitráta",
"comment": "megjegyzés",
"dateAdded": "hozzáadva",
"duration": "hossz",
"fromYear": "évtől",
"isCompilation": "gyűjtemény",
"isRated": "értékelt",
"lastPlayed": "utoljára lejátszva",
"mostPlayed": "legtöbbször lejátszott",
"note": "megjegyzés",
"random": "véletlenszerű",
"rating": "értékelések",
"recentlyAdded": "nemrég hozzáadott",
"releaseDate": "megjelenési dátum",
"releaseYear": "megjelenés éve",
"songCount": "dal szám",
"title": "cím",
"disc": "lemez",
"criticRating": "kritikusok értékelése",
"communityRating": "közösségi értékelés",
"albumArtist": "$t(entity.albumArtist_one)",
"biography": "biográfia",
"album": "$t(entity.album_one)",
"favorited": "kedvelt",
"isRecentlyPlayed": "mostanában lejátszott",
"name": "név",
"owner": "$t(common.owner)",
"id": "azonosító",
"recentlyPlayed": "nemrég lejátszott",
"isFavorited": "kedvelt",
"search": "keresés",
"isPublic": "nyilvános",
"playCount": "lejátszások száma",
"recentlyUpdated": "nemrég módosult",
"path": "elérési út",
"toYear": "évhez",
"trackNumber": "dalszám"
},
"form": {
"addServer": {
"error_savePassword": "hiba történt a jelszó mentésekor",
"ignoreCors": "CORS figyelmen kívül hagyása $t(common.restartRequired)",
"ignoreSsl": "SSL figyelmen kívül hagyása $t(common.restartRequired)",
"input_password": "jelszó",
"input_url": "url",
"input_username": "felhasználónév",
"success": "szerver sikeresen hozzáadva",
"title": "szerver hozzáadása",
"input_name": "szervernév",
"input_savePassword": "jelszó mentése",
"input_legacyAuthentication": "klasszikus hitelesítés bekapcsolása"
},
"addToPlaylist": {
"input_skipDuplicates": "duplikátumok átugrása",
"input_playlists": "$t(entity.playlist_other)",
"success": "hozzáadtuk ezt: $t(entity.trackWithCount, {\"count\": {{message}} }) a következőhöz: $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "hozzáadás a következőhöz: $t(entity.playlist_one)"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "nyilvános",
"title": "$t(entity.playlist_one) létrehozása",
"success": "$t(entity.playlist_one) sikeresen létrehozva"
},
"deletePlaylist": {
"input_confirm": "a megerősítéshez írd be a(z) $t(entity.playlist_one) nevét",
"success": "$t(entity.playlist_one) sikeresen törölve",
"title": "$t(entity.playlist_one) törlése"
},
"editPlaylist": {
"success": "$t(entity.playlist_one) sikeresen módosítva",
"publicJellyfinNote": "A Jellyfin valamiért nem teszi közzé, hogy egy lejátszási lista nyilvános-e. Amennyiben azt szeretnéd, hogy nyilvános maradjon, válaszd ki az alábbi beviteli mezőt"
}
}
}
+747
View File
@@ -0,0 +1,747 @@
{
"action": {
"createPlaylist": "buat $t(entity.playlist_one)",
"toggleSmartPlaylistEditor": "ubah editor $t(entity.smartPlaylist)",
"goToPage": "pergi ke halaman",
"moveToTop": "pindah ke atas",
"addToPlaylist": "tambahkan ke $t(entity.playlist_one)",
"removeFromFavorites": "hapus dari $t(entity.favorite_other)",
"removeFromPlaylist": "hapus dari $t(entity.playlist_one)",
"deselectAll": "batalkan pilih semua",
"editPlaylist": "ubah $t(entity.playlist_one)",
"moveToNext": "pindah ke berikutnya",
"refresh": "$t(common.refresh)",
"removeFromQueue": "hapus dari antrean",
"setRating": "setel penilaian",
"viewPlaylists": "lihat $t(entity.playlist_other)",
"openIn": {
"lastfm": "Buka di Last.fm",
"musicbrainz": "Buka di MusicBrainz"
},
"addToFavorites": "tambahkan ke $t(entity.favorite_other)",
"clearQueue": "kosongkan antrian",
"deletePlaylist": "hapus $t(entity.playlist_one)",
"moveToBottom": "pindah ke bawah"
},
"common": {
"clear": "bersihkan",
"action_other": "aksi",
"codec": "Koded",
"channel_other": "Saluran",
"duration": "durasi",
"create": "buat",
"center": "tengah",
"areYouSure": "apakah Anda yakin?",
"add": "tambah",
"albumGain": "perolehan album",
"albumPeak": "Puncak album",
"cancel": "batal",
"close": "Tutup",
"configure": "konfigurasi",
"currentSong": "lagu saat ini $t(entity.track_one)",
"delete": "hapus",
"description": "deskripsi",
"edit": "ubah",
"biography": "biografi",
"confirm": "konfirmasi",
"descending": "menurun",
"disable": "nonaktifkan",
"disc": "disk",
"enable": "aktifkan",
"expand": "perbesar",
"favorite": "favorit",
"filter_other": "filter",
"filters": "filter",
"forceRestartRequired": "perlu restart untuk menerapkan perubahan... tutup pemberitahuan untuk memulai ulang",
"forward": "maju",
"gap": "jarak",
"home": "beranda",
"increase": "tingkatkan",
"left": "kiri",
"limit": "batasi",
"manage": "kelola",
"maximize": "maksimalkan",
"menu": "menu",
"minimize": "minimalisasi",
"modified": "dimodifikasi",
"mbid": "ID MusicBrainz",
"name": "nama",
"no": "tidak",
"none": "tidak ada",
"noResultsFromQuery": "permintaan tidak menghasilkan hasil",
"note": "catatan",
"ok": "oke",
"owner": "pemilik",
"playerMustBePaused": "pemain harus dijeda",
"preview": "Pratinjau",
"previousSong": "lagu sebelumnya $t(entity.track_one)",
"quit": "keluar",
"random": "acak",
"rating": "penilaian",
"refresh": "segarkan",
"reload": "Muat Ulang",
"reset": "reset",
"resetToDefault": "reset ke default",
"restartRequired": "restart diperlukan",
"right": "kanan",
"save": "simpan",
"saveAndReplace": "simpan dan ganti",
"saveAs": "simpan sebagai",
"search": "cari",
"setting": "pengaturan",
"share": "Bagikan",
"size": "ukuran",
"sortOrder": "urutkan",
"title": "judul",
"trackNumber": "pista",
"trackGain": "Gain pista",
"trackPeak": "puncak lagu",
"unknown": "tidak dikenal",
"version": "versi",
"year": "tahun",
"yes": "ya",
"path": "path(jalur)",
"ascending": "menaik",
"bpm": "bpm",
"bitrate": "kecepatan bit",
"collapse": "lipat",
"comingSoon": "segera hadir…",
"decrease": "kurangi",
"dismiss": "abaikan",
"translation": "terjemahan",
"backward": "mundur"
},
"entity": {
"album_other": "album",
"albumArtist_other": "artis album",
"albumArtistCount_other": "{{count}} artis album",
"albumWithCount_other": "{{count}} album",
"artist_other": "artis",
"artistWithCount_other": "{{count}} artis",
"favorite_other": "favorit",
"folder_other": "folder",
"folderWithCount_other": "{{count}} folder",
"genre_other": "genre",
"genreWithCount_other": "{{count}} genre",
"playlist_other": "daftar putar",
"play_other": "Putar {{count}}",
"playlistWithCount_other": "{{count}} daftar putar",
"smartPlaylist": "$t(entity.playlist_one) pintar",
"track_other": "pista",
"song_other": "lagu",
"trackWithCount_other": "{{count}} pista"
},
"error": {
"apiRouteError": "tidak dapat mengarahkan permintaan",
"audioDeviceFetchError": "terjadi kesalahan saat mencoba mengambil perangkat audio",
"authenticationFailed": "autentikasi gagal",
"badAlbum": "Anda melihat halaman ini karena lagu ini tidak termasuk dalam album. Masalah ini bisa terjadi jika Anda memiliki lagu di tingkat atas folder musik Anda. Jellyfin hanya mengelompokkan lagu jika mereka berada di dalam folder.",
"credentialsRequired": "kredensial diperlukan",
"endpointNotImplementedError": "endpoint {{endpoint}} tidak diimplementasikan untuk {{serverType}}",
"genericError": "terjadi kesalahan",
"invalidServer": "server tidak valid",
"localFontAccessDenied": "akses ke font lokal ditolak",
"loginRateError": "terlalu banyak percobaan login, coba beberapa detik lagi",
"mpvRequired": "MPV diperlukan",
"networkError": "terjadi kesalahan jaringan",
"openError": "Tidak dapat membuka file",
"playbackError": "terjadi kesalahan saat mencoba memutar media",
"remoteDisableError": "terjadi kesalahan saat mencoba $t(common.disable) server jarak jauh",
"remoteEnableError": "terjadi kesalahan saat mencoba $t(common.enable) server jarak jauh",
"remotePortError": "terjadi kesalahan saat mencoba mengatur port server jarak jauh",
"remotePortWarning": "restart server untuk menerapkan port baru",
"serverNotSelectedError": "tidak ada server yang dipilih",
"serverRequired": "server diperlukan",
"sessionExpiredError": "sesi Anda telah kedaluwarsa",
"systemFontError": "terjadi kesalahan saat mencoba mendapatkan font sistem"
},
"filter": {
"album": "$t(entity.album_one)",
"albumArtist": "$t(entity.albumArtist_one)",
"albumCount": "Hitung $t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "biografi",
"bitrate": "bitrate",
"bpm": "lpm",
"channels": "$t(common.channel_other)",
"comment": "komentar",
"communityRating": "penilaian komunitas",
"criticRating": "penilaian kritik",
"dateAdded": "tanggal ditambahkan",
"disc": "disk",
"duration": "durasi",
"favorited": "favorit",
"genre": "$t(entity.genre_one)",
"id": "id",
"isCompilation": "apakah ini kompilasi",
"isFavorited": "apakah ini favorit",
"isPublic": "apakah ini publik",
"isRated": "apakah ini terklasifikasi",
"isRecentlyPlayed": "baru saja diputar",
"lastPlayed": "terakhir diputar",
"mostPlayed": "paling sering diputar",
"name": "nama",
"note": "catatan",
"owner": "$t(common.owner)",
"playCount": "jumlah putar",
"random": "acak",
"rating": "penilaian",
"recentlyAdded": "baru saja ditambahkan",
"recentlyPlayed": "baru saja diputar",
"recentlyUpdated": "baru saja diperbarui",
"releaseDate": "tanggal rilis",
"releaseYear": "tahun rilis",
"search": "cari",
"songCount": "jumlah lagu",
"toYear": "hingga tahun",
"trackNumber": "nomor pista",
"fromYear": "dari tahun",
"title": "judul",
"path": "path(jalur)"
},
"form": {
"addServer": {
"error_savePassword": "terjadi kesalahan saat mencoba menyimpan kata sandi",
"ignoreCors": "abaikan cors ($t(common.restartRequired))",
"ignoreSsl": "abaikan ssl ($t(common.restartRequired))",
"input_legacyAuthentication": "izinkan autentikasi warisan",
"input_name": "nama server",
"input_password": "kata sandi",
"input_savePassword": "simpan kata sandi",
"input_url": "url",
"input_username": "nama pengguna",
"success": "server berhasil ditambahkan",
"title": "tambah server"
},
"addToPlaylist": {
"input_playlists": "$t(entity.playlist_other)",
"input_skipDuplicates": "lewati duplikat",
"success": "ditambahkan $t(entity.trackWithCount, {\"count\": {{message}} }) ke $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "tambahkan ke $t(entity.playlist_one)"
},
"createPlaylist": {
"input_description": "$t(common.description)",
"input_name": "$t(common.name)",
"input_owner": "$t(common.owner)",
"input_public": "publik",
"success": "$t(entity.playlist_one) berhasil dibuat",
"title": "buat $t(entity.playlist_one)"
},
"deletePlaylist": {
"input_confirm": "ketik nama $t(entity.playlist_one) untuk mengonfirmasi",
"success": "$t(entity.playlist_one) berhasil dihapus",
"title": "hapus $t(entity.playlist_one)"
},
"editPlaylist": {
"publicJellyfinNote": "Jellyfin entah bagaimana tidak menampilkan apakah playlist ini publik atau tidak. Jika Anda ingin playlist ini tetap publik, harap pilih entri berikut",
"success": "$t(entity.playlist_one) berhasil diperbarui",
"title": "ubah $t(entity.playlist_one)"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "cari lirik"
},
"queryEditor": {
"input_optionMatchAll": "cocokkan semua",
"input_optionMatchAny": "cocokkan salah satu"
},
"shareItem": {
"allowDownloading": "Izinkan unduhan",
"description": "Deskripsi",
"setExpiration": "Atur masa berlaku",
"success": "Tautan berbagi berhasil disalin ke papan klip (atau klik di sini untuk membuka)",
"expireInvalid": "Masa berlaku harus di masa depan",
"createFailed": "Tidak dapat membuat sumber daya berbagi (Apakah berbagi diaktifkan?)"
},
"updateServer": {
"success": "Server berhasil diperbarui",
"title": "perbarui server"
}
},
"page": {
"albumArtistDetail": {
"about": "Tentang {{artist}}",
"recentReleases": "Rilis terbaru",
"viewDiscography": "Lihat diskografi",
"relatedArtists": "$t(entity.artist_other) serupa",
"topSongs": "Lagu terbaik",
"topSongsFrom": "Lagu terbaik dari {{title}}",
"viewAll": "Lihat semua",
"viewAllTracks": "Lihat semua $t(entity.track_other)",
"appearsOn": "Tampil di"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "lebih banyak dari $t(entity.artist_one) ini",
"moreFromGeneric": "lebih banyak dari {{item}}",
"released": "dirilis"
},
"albumList": {
"artistAlbums": "album dari {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"title": "$t(entity.album_other)"
},
"appMenu": {
"collapseSidebar": "perkecil sidebar",
"expandSidebar": "perluas sidebar",
"goBack": "kembali",
"goForward": "maju",
"manageServers": "kelola server",
"openBrowserDevtools": "buka alat pengembang browser",
"quit": "$t(common.quit)",
"selectServer": "pilih server",
"settings": "$t(common.setting_other)",
"version": "versi {{version}}"
},
"manageServers": {
"title": "kelola server",
"serverDetails": "detail server",
"url": "URL",
"username": "nama pengguna",
"editServerDetailsTooltip": "edit detail server",
"removeServer": "hapus server"
},
"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": "unduh",
"moveToNext": "$t(action.moveToNext)",
"moveToBottom": "$t(action.moveToBottom)",
"numberSelected": "{{count}} terpilih",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "Bagikan item",
"showDetails": "Lihat detail",
"moveToTop": "$t(action.moveToTop)",
"play": "$t(player.play)"
},
"fullscreenPlayer": {
"config": {
"dynamicBackground": "latar belakang dinamis",
"dynamicImageBlur": "ukuran blur gambar",
"dynamicIsImage": "aktifkan gambar latar belakang",
"followCurrentLyric": "ikuti lirik saat ini",
"lyricAlignment": "penyelarasan lirik",
"lyricSize": "ukuran lirik",
"opacity": "opasitas",
"showLyricMatch": "tampilkan kecocokan lirik",
"showLyricProvider": "tampilkan penyedia lirik",
"synchronized": "sinkronisasi",
"unsynchronized": "tidak sinkronisasi",
"useImageAspectRatio": "gunakan rasio aspek gambar",
"lyricOffset": "offset lirik (ms)",
"lyricGap": "jarak lirik"
},
"lyrics": "lirik",
"related": "terkait",
"upNext": "berikutnya",
"noLyrics": "tanpa lirik",
"visualizer": "visualisasi"
},
"genreList": {
"showAlbums": "Tampilkan $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "Tampilkan $t(entity.genre_one) $t(entity.track_other)",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
"commands": {
"goToPage": "pergi ke halaman",
"searchFor": "cari {{query}}",
"serverCommands": "perintah server"
},
"title": "perintah"
},
"home": {
"explore": "jelajahi dari pustaka Anda",
"mostPlayed": "paling banyak diputar",
"newlyAdded": "rilis baru ditambahkan",
"recentlyPlayed": "baru saja diputar",
"title": "$t(common.home)"
},
"itemDetail": {
"copyPath": "Salin jalur ke papan klip",
"copiedPath": "Jalur berhasil disalin",
"openFile": "Tampilkan lagu di pengelola file"
},
"playlist": {
"reorder": "penataan ulang hanya aktif saat mengurutkan berdasarkan ID"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"setting": {
"advanced": "Lanjutan",
"generalTab": "umum",
"hotkeysTab": "tombol pintasan",
"playbackTab": "pemutaran",
"windowTab": "jendela"
},
"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)",
"nowPlaying": "sedang diputar",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"shared": "berbagi $t(entity.playlist_other)",
"tracks": "$t(entity.track_other)"
},
"trackList": {
"artistTracks": "lagu oleh {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)"
}
},
"player": {
"addLast": "tambahkan terakhir",
"favorite": "favorit",
"mute": "bisukan",
"muted": "terbisukan",
"next": "berikutnya",
"play": "putar",
"playbackFetchCancel": "ini memerlukan waktu... tutup pemberitahuan untuk membatalkan",
"playbackFetchInProgress": "memuat lagu…",
"playbackFetchNoResults": "tidak ada lagu ditemukan",
"playbackSpeed": "kecepatan pemutaran",
"playRandom": "putar acak",
"playSimilarSongs": "putar lagu serupa",
"previous": "sebelumnya",
"queue_clear": "bersihkan antrean",
"queue_moveToBottom": "pindahkan yang terpilih ke bawah",
"queue_moveToTop": "pindahkan yang terpilih ke atas",
"queue_remove": "hapus yang terpilih",
"repeat": "ulang",
"repeat_all": "ulang semua",
"repeat_off": "ulang dimatikan",
"shuffle": "putar acak",
"shuffle_off": "acak dimatikan",
"skip": "lewati",
"skip_back": "mundur",
"skip_forward": "lewati maju",
"stop": "berhenti",
"toggleFullscreenPlayer": "aktifkan pemutar layar penuh",
"unfavorite": "bukan favorit",
"pause": "jeda",
"viewQueue": "lihat antrean",
"addNext": "tambahkan berikutnya"
},
"setting": {
"accentColor": "warna sorotan",
"accentColor_description": "menetapkan warna sorotan aplikasi",
"albumBackground": "gambar latar belakang album",
"albumBackground_description": "Tambahkan gambar latar belakang ke halaman album yang berisi sampul album",
"albumBackgroundBlur": "Ukuran blur gambar latar belakang album",
"albumBackgroundBlur_description": "Atur tingkat blur gambar latar belakang album",
"applicationHotkeys": "tombol pintasan aplikasi",
"applicationHotkeys_description": "menetapkan tombol pintasan aplikasi. centang untuk menjadikannya tombol pintasan global (desktop saja)",
"artistConfiguration": "Pengaturan halaman artis album",
"artistConfiguration_description": "Atur elemen apa yang ditampilkan dan urutannya di halaman artis album",
"audioDevice": "perangkat audio",
"audioDevice_description": "pilih perangkat audio yang digunakan untuk pemutaran (hanya pemutar web)",
"audioExclusiveMode": "mode audio eksklusif",
"audioExclusiveMode_description": "aktifkan mode audio eksklusif. Dalam mode ini, sistem biasanya diblokir, dan hanya mpv yang akan diizinkan untuk output audio",
"audioPlayer": "pemutar audio",
"audioPlayer_description": "pilih pemutar audio yang digunakan untuk pemutaran",
"buttonSize": "ukuran tombol bilah pemutaran",
"buttonSize_description": "ukuran tombol pada bilah pemutaran",
"webAudio_description": "Menggunakan audio web. Ini mengaktifkan fitur lanjutan seperti Replaygain. Nonaktifkan opsi ini jika Anda mengalami masalah",
"windowBarStyle": "gaya bilah jendela",
"windowBarStyle_description": "pilih gaya bilah jendela",
"zoom": "persentase zoom",
"zoom_description": "tentukan persentase zoom aplikasi",
"clearCache_description": "'Pembersihan keras' Feishin. Untuk membersihkan cache Feishin, kosongkan cache browser (gambar yang disimpan dan elemen lainnya). Kredensial dan pengaturan server tetap terjaga",
"clearQueryCache": "Bersihkan cache Feishin",
"clearQueryCache_description": "'Pembersihan lunak' Feishin. Ini akan menyegarkan daftar putar, metadata lagu, dan mengatur ulang lirik yang disimpan. Pengaturan, kredensial server, dan gambar cache tetap terjaga",
"clearCacheSuccess": "Cache berhasil dibersihkan",
"contextMenu": "Pengaturan menu konteks (klik kanan)",
"contextMenu_description": "Memungkinkan Anda menyembunyikan elemen yang ditampilkan dalam menu saat Anda klik kanan pada elemen. Elemen yang tidak dipilih akan disembunyikan",
"crossfadeDuration": "durasi crossfade",
"crossfadeDuration_description": "atur durasi efek crossfade",
"crossfadeStyle": "gaya crossfade",
"crossfadeStyle_description": "pilih gaya crossfade yang digunakan oleh pemutar audio",
"customCssEnable": "Aktifkan CSS kustom",
"customCssEnable_description": "Memungkinkan penulisan CSS kustom.",
"customCssNotice": "Pemberitahuan: meskipun ada sanitasi (menolak url() dan content:), menggunakan CSS kustom masih dapat berisiko mengubah antarmuka.",
"customCss": "CSS kustom",
"customCss_description": "CSS kustom konten. Catatan: content dan url eksternal adalah properti yang ditolak. Pratinjau konten Anda ditampilkan di bawah. Entri tambahan yang tidak Anda tentukan hadir karena sanitasi.",
"customFontPath": "jalur font kustom",
"customFontPath_description": "tentukan jalur font kustom yang akan digunakan aplikasi",
"disableAutomaticUpdates": "nonaktifkan pembaruan otomatis",
"discordApplicationId": "ID aplikasi {{discord}}",
"discordApplicationId_description": "ID aplikasi untuk status aktivitas {{discord}} (defaultnya adalah {{defaultId}})",
"discordIdleStatus": "tampilkan status tidak aktif dalam status aktivitas",
"discordIdleStatus_description": "ketika diaktifkan, memperbarui status saat pemutar tidak aktif",
"discordListening": "Tampilkan status sebagai mendengarkan",
"discordListening_description": "tampilkan status sebagai mendengarkan alih-alih bermain",
"discordRichPresence": "status aktivitas {{discord}}",
"discordRichPresence_description": "aktifkan status pemutaran di status aktivitas {{discord}}. Gambar tombol adalah: {{icon}}, {{playing}}, dan {{paused}} ",
"discordUpdateInterval": "interval pembaruan status aktivitas {{discord}}",
"discordUpdateInterval_description": "waktu dalam detik antara setiap pembaruan (minimal 15 detik)",
"doubleClickBehavior": "masukkan semua lagu yang dicari saat mengklik dua kali",
"doubleClickBehavior_description": "jika true, semua lagu yang cocok dalam pencarian lagu akan dimasukkan ke dalam antrean. Jika tidak, hanya lagu yang dipilih yang akan dimasukkan ke dalam antrean",
"enableRemote": "aktifkan kontrol jarak jauh server",
"enableRemote_description": "aktifkan kontrol jarak jauh server untuk memungkinkan perangkat lain mengontrol aplikasi",
"externalLinks": "Tampilkan tautan eksternal",
"externalLinks_description": "Izinkan untuk menampilkan tautan eksternal (Last.fm, MusicBrainz) di halaman artis/album",
"exitToTray": "keluar ke baki",
"exitToTray_description": "keluar dari aplikasi ke baki sistem",
"floatingQueueArea": "tampilkan area antrean mengambang",
"floatingQueueArea_description": "menampilkan ikon mengambang di sisi kanan layar untuk melihat antrean pemutaran",
"followLyric": "ikuti lirik saat ini",
"followLyric_description": "gulir lirik ke posisi pemutaran saat ini",
"font": "font",
"font_description": "tentukan font yang digunakan aplikasi",
"fontType": "jenis font",
"fontType_description": "font bawaan memilih salah satu font yang disediakan oleh Feishin. font sistem memungkinkan Anda memilih font apa pun yang disediakan oleh sistem operasi Anda. kustom memungkinkan Anda memberikan font Anda sendiri",
"fontType_optionBuiltIn": "font bawaan",
"fontType_optionCustom": "font kustom",
"fontType_optionSystem": "font sistem",
"gaplessAudio": "audio tanpa jeda",
"gaplessAudio_description": "tentukan pengaturan audio tanpa jeda untuk mpv",
"gaplessAudio_optionWeak": "lemah (disarankan)",
"genreBehavior": "Perilaku default halaman genre",
"genreBehavior_description": "Menentukan apakah klik pada genre akan membuka daftar lagu atau album secara default",
"globalMediaHotkeys": "tombol pintasan media global",
"globalMediaHotkeys_description": "aktifkan atau nonaktifkan penggunaan tombol pintasan sistem media untuk mengontrol pemutaran",
"homeConfiguration": "Pengaturan halaman beranda",
"homeConfiguration_description": "Mengatur elemen mana yang ditampilkan dan urutannya di halaman beranda",
"homeFeature": "Karusel fitur beranda",
"homeFeature_description": "Mengontrol apakah karusel besar fitur ditampilkan di halaman beranda",
"hotkey_browserBack": "mundur",
"hotkey_browserForward": "maju",
"hotkey_favoriteCurrentSong": "$t(common.currentSong) favorit",
"hotkey_favoritePreviousSong": "$t(common.previousSong) favorit",
"hotkey_globalSearch": "pencarian global",
"hotkey_localSearch": "pencarian di halaman",
"hotkey_playbackNext": "lagu berikutnya",
"hotkey_playbackPause": "jeda",
"hotkey_playbackPlay": "putar",
"hotkey_playbackPlayPause": "putar / jeda",
"hotkey_playbackPrevious": "lagu sebelumnya",
"hotkey_playbackStop": "berhenti",
"hotkey_rate0": "Bersihkan penilaian",
"hotkey_rate1": "beri penilaian 1 bintang",
"hotkey_rate2": "beri penilaian 2 bintang",
"hotkey_rate3": "beri penilaian 3 bintang",
"hotkey_rate4": "beri penilaian 4 bintang",
"hotkey_rate5": "beri penilaian 5 bintang",
"hotkey_skipBackward": "mundur",
"hotkey_skipForward": "lompat ke depan",
"hotkey_toggleCurrentSongFavorite": "ubah $t(common.currentSong) menjadi favorit",
"hotkey_toggleFullScreenPlayer": "ubah pemutar menjadi layar penuh",
"hotkey_togglePreviousSongFavorite": "ubah $t(common.previousSong) menjadi favorit",
"hotkey_toggleQueue": "ubah antrean",
"hotkey_toggleRepeat": "toggle ulangi",
"hotkey_toggleShuffle": "toggle acak",
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) tidak favorit",
"hotkey_unfavoritePreviousSong": "$t(common.previousSong) tidak favorit",
"hotkey_volumeDown": "turunkan volume",
"hotkey_volumeMute": "senyapkan volume",
"hotkey_volumeUp": "naikkan volume",
"hotkey_zoomIn": "perbesar",
"hotkey_zoomOut": "perkecil",
"imageAspectRatio": "Gunakan rasio aspek sampul asli",
"imageAspectRatio_description": "Jika diaktifkan, sampul akan ditampilkan dengan rasio aspek aslinya. Untuk seni yang tidak 1:1, ruang yang tersisa akan kosong",
"language_description": "menetapkan bahasa untuk aplikasi ($t(common.restartRequired))",
"lastfmApiKey": "Kunci API untuk {{lastfm}}",
"lastfmApiKey_description": "kunci API untuk {{lastfm}}. Diperlukan untuk sampul",
"lyricFetch": "cari lirik di Internet",
"lyricFetch_description": "mencari lirik dari berbagai sumber di Internet",
"lyricFetchProvider": "penyedia untuk mencari lirik",
"lyricFetchProvider_description": "pilih penyedia untuk mencari lirik. urutan penyedia adalah urutan pencarian",
"lyricOffset": "geser lirik (ms)",
"lyricOffset_description": "geser lirik sebanyak jumlah milidetik yang ditentukan",
"minimizeToTray": "minimalkan ke baki",
"minimizeToTray_description": "minimalkan aplikasi ke baki sistem",
"minimumScrobblePercentage": "persentase durasi scrobble minimum",
"minimumScrobblePercentage_description": "persentase minimum lagu yang harus diputar sebelum melakukan scrobble",
"minimumScrobbleSeconds": "scrobble minimum (detik)",
"minimumScrobbleSeconds_description": "durasi minimum dalam detik dari lagu yang harus diputar sebelum melakukan scrobble",
"mpvExecutablePath_description": "tentukan jalur executable mpv. jika dibiarkan kosong, jalur default akan digunakan",
"mpvExtraParameters_help": "Satu per baris",
"passwordStore": "kata sandi/penyimpanan rahasia",
"passwordStore_description": "metode penyimpanan kata sandi/kunci rahasia yang akan digunakan. ubah opsi ini jika Anda mengalami masalah dalam menyimpan kata sandi.",
"playbackStyle": "gaya pemutaran",
"playbackStyle_description": "pilih gaya pemutaran yang akan digunakan oleh pemutar audio",
"playbackStyle_optionCrossFade": "crossfade",
"playbackStyle_optionNormal": "normal",
"playButtonBehavior": "perilaku tombol putar",
"playButtonBehavior_description": "tentukan perilaku default tombol putar saat lagu ditambahkan ke antrean",
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerAlbumArtResolution": "resolusi sampul album pemutar",
"playerAlbumArtResolution_description": "resolusi untuk pratinjau sampul album pemutar besar. semakin besar akan membuatnya lebih tajam, tetapi dapat memperlambat pemuatan. Nilai default adalah 0, yang berarti otomatis",
"playerbarOpenDrawer": "Buka pemutar ke layar penuh",
"playerbarOpenDrawer_description": "Izinkan mengklik bilah pemutar untuk membuka pemutar di layar penuh",
"remotePassword": "kata sandi kontrol jarak jauh server",
"remotePassword_description": "tentukan kata sandi untuk kontrol jarak jauh server. Kredensial ini dikirimkan dengan tidak aman secara default, jadi Anda harus menggunakan kata sandi unik untuk menghindari masalah",
"remotePort": "port kontrol jarak jauh server",
"remotePort_description": "tentukan port untuk kontrol jarak jauh server",
"remoteUsername": "nama pengguna kontrol jarak jauh server",
"remoteUsername_description": "tentukan nama pengguna untuk kontrol jarak jauh server. jika nama pengguna dan kata sandi kosong, otentikasi akan dinonaktifkan",
"replayGainClipping": "potong {{ReplayGain}}",
"replayGainClipping_description": "mencegah pemotongan yang disebabkan oleh {{ReplayGain}} dengan menurunkan gain secara otomatis",
"replayGainFallback": "alternatif {{ReplayGain}}",
"replayGainFallback_description": "gain dalam dB yang akan diterapkan jika file tidak memiliki tag {{ReplayGain}}",
"replayGainMode": "mode {{ReplayGain}}",
"replayGainMode_description": "menyesuaikan volume gain sesuai dengan nilai {{ReplayGain}} yang disimpan dalam metadata file",
"replayGainMode_optionAlbum": "$t(entity.album_one)",
"replayGainMode_optionNone": "$t(common.none)",
"replayGainMode_optionTrack": "$t(entity.track_one)",
"replayGainPreamp": "preamplifier (dB) {{ReplayGain}}",
"replayGainPreamp_description": "menyesuaikan gain preamplifier yang diterapkan ke nilai {{ReplayGain}}",
"sampleRate_description": "pilih rasio sampel output yang akan digunakan jika frekuensi sampel yang dipilih berbeda dari media yang sedang diputar. nilai di bawah 8000 akan menggunakan frekuensi default",
"savePlayQueue_description": "menyimpan antrean pemutaran saat aplikasi ditutup dan mengembalikannya saat dibuka",
"scrobble": "scrobble",
"scrobble_description": "melakukan scrobble pemutaran di server media Anda",
"showSkipButton": "tampilkan tombol lompat",
"showSkipButton_description": "menampilkan atau menyembunyikan tombol lompat di bilah pemutar",
"showSkipButtons": "tampilkan tombol lompat",
"showSkipButtons_description": "menampilkan atau menyembunyikan tombol lompat di bilah pemutar",
"sidebarCollapsedNavigation": "navigasi sidebar (terlipat)",
"sidebarCollapsedNavigation_description": "tampilkan atau sembunyikan navigasi di sidebar yang terlipat",
"sidebarConfiguration": "pengaturan sidebar",
"sidebarConfiguration_description": "pilih elemen dan urutan tampilannya di sidebar",
"sidebarPlaylistList": "daftar putar sidebar",
"sidebarPlaylistList_description": "tampilkan atau sembunyikan daftar putar di sidebar",
"sidePlayQueueStyle": "gaya antrean pemutaran samping",
"sidePlayQueueStyle_description": "menetapkan gaya antrean pemutaran samping",
"sidePlayQueueStyle_optionAttached": "terpasang",
"sidePlayQueueStyle_optionDetached": "terpisah",
"skipDuration": "durasi lompat",
"skipDuration_description": "tentukan durasi untuk lompat saat menggunakan tombol lompat di bilah pemutar",
"skipPlaylistPage": "lompat halaman daftar putar",
"skipPlaylistPage_description": "saat menavigasi ke daftar putar, pergi ke halaman daftar lagu dari daftar putar alih-alih halaman default",
"startMinimized": "mulai dengan minimalkan",
"startMinimized_description": "mulai aplikasi di baki sistem",
"theme": "tema",
"theme_description": "tentukan tema yang digunakan oleh aplikasi",
"themeDark": "tema (gelap)",
"themeDark_description": "tentukan tema gelap yang digunakan oleh aplikasi",
"themeLight": "tema (terang)",
"themeLight_description": "tentukan tema terang yang digunakan oleh aplikasi",
"transcodeNote": "Akan ditampilkan setelah 1 (web) - 2 (mpv) lagu",
"transcode": "aktifkan transkode",
"transcode_description": "mengaktifkan transkode ke berbagai format",
"transcodeBitrate": "bitrate untuk transkode",
"transcodeBitrate_description": "pilih bitrate untuk ditranskode. 0 berarti biarkan server yang memilih",
"transcodeFormat": "format untuk ditranskode",
"transcodeFormat_description": "pilih format untuk ditranskode. biarkan kosong agar server yang memutuskan",
"translationApiProvider": "Penyedia API penerjemahan",
"translationApiProvider_description": "Penyedia API untuk penerjemahan",
"translationApiKey": "kunci API penerjemahan",
"translationApiKey_description": "Kunci API untuk penerjemahan (hanya untuk endpoint layanan global)",
"translationTargetLanguage": "bahasa tujuan penerjemahan",
"translationTargetLanguage_description": "bahasa tujuan untuk penerjemahan",
"trayEnabled": "Tampilkan di area pemberitahuan",
"trayEnabled_description": "tampilkan/sembunyikan ikon/menu di area pemberitahuan. Jika dinonaktifkan, juga menonaktifkan meminimalkan/keluar ke baki",
"useSystemTheme": "gunakan tema sistem",
"useSystemTheme_description": "ikuti preferensi terang atau gelap yang ditetapkan oleh sistem",
"volumeWheelStep": "langkah roda volume",
"volumeWheelStep_description": "jumlah volume yang berubah saat menggulirkan roda mouse pada penggeser volume",
"volumeWidth": "Lebar penggeser volume",
"volumeWidth_description": "Lebar penggeser volume",
"webAudio": "gunakan audio web",
"clearCache": "Bersihkan cache browser",
"disableLibraryUpdateOnStartup": "nonaktifkan pemeriksaan versi baru saat startup",
"language": "bahasa",
"mpvExecutablePath": "jalur executable mpv",
"mpvExtraParameters": "parameter tambahan mpv",
"playButtonBehavior_optionPlay": "$t(player.play)",
"sampleRate": "rasio sampel",
"savePlayQueue": "simpan antrean pemutaran"
},
"table": {
"column": {
"album": "album",
"albumArtist": "artis album",
"albumCount": "$t(entity.album_other)",
"artist": "$t(entity.artist_one)",
"biography": "biografi",
"bitrate": "bitrate",
"bpm": "lpm",
"channels": "$t(common.channel_other)",
"codec": "$t(common.codec)",
"comment": "komentar",
"dateAdded": "tanggal ditambahkan",
"discNumber": "nomor disk",
"favorite": "favorit",
"genre": "$t(entity.genre_one)",
"lastPlayed": "terakhir diputar",
"path": "jalur",
"playCount": "putaran",
"rating": "penilaian",
"releaseDate": "tanggal rilis",
"releaseYear": "tahun",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "judul",
"trackNumber": "pista"
},
"config": {
"general": {
"autoFitColumns": "sesuaikan kolom otomatis",
"followCurrentSong": "ikuti lagu saat ini",
"displayType": "tipe tampilan",
"gap": "$t(common.gap)",
"itemGap": "jarak antar elemen (px)",
"itemSize": "ukuran elemen (px)",
"size": "$t(common.size)",
"tableColumns": "kolom tabel"
},
"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)",
"dateAdded": "tanggal ditambahkan",
"discNumber": "nomor disk",
"duration": "$t(common.duration)",
"favorite": "$t(common.favorite)",
"genre": "$t(entity.genre_one)",
"lastPlayed": "terakhir diputar",
"note": "$t(common.note)",
"owner": "$t(common.owner)",
"path": "$t(common.path)",
"playCount": "jumlah putaran",
"rating": "$t(common.rating)",
"releaseDate": "tanggal rilis",
"rowIndex": "indeks baris",
"size": "$t(common.size)",
"songCount": "$t(entity.track_other)",
"title": "$t(common.title)",
"titleCombined": "$t(common.title) (digabungkan)",
"trackNumber": "nomor pista",
"year": "$t(common.year)"
},
"view": {
"card": "kartu",
"poster": "poster",
"table": "tabel"
}
}
}
}
+444 -1
View File
@@ -1 +1,444 @@
{}
{
"action": {
"openIn": {
"lastfm": "Åpne i Last.fm",
"musicbrainz": "Åpne i MusicBrainz"
},
"moveToBottom": "flytt til bunnen",
"deletePlaylist": "slett $t(entity.playlist_one)",
"deselectAll": "avmarker alle",
"editPlaylist": "rediger $t(entity.playlist_one)",
"addToFavorites": "legg til $t(entity.favorite_other)",
"addToPlaylist": "legg til $t(entity.playlist_one)",
"clearQueue": "tøm kø",
"createPlaylist": "opprett $t(entity.playlist_one)",
"goToPage": "gå til side",
"moveToTop": "flytt til toppen",
"refresh": "$t(common.refresh)",
"removeFromFavorites": "fjern fra $t(entity.favorite_other)",
"moveToNext": "flytt til neste",
"setRating": "angi vurdering",
"removeFromQueue": "fjern fra kø",
"removeFromPlaylist": "fjern fra $t(entity.playlist_one)",
"viewPlaylists": "vise $t(entity.playlist_other)",
"toggleSmartPlaylistEditor": "bytt $t(entity.smartPlaylist) editor"
},
"common": {
"bpm": "bpm",
"cancel": "avbryt",
"center": "midtstill",
"clear": "tøm",
"collapse": "slå sammen",
"configure": "konfigurer",
"confirm": "bekreft",
"currentSong": "gjeldende $t(entity.track_one)",
"version": "versjon",
"areYouSure": "er du sikker?",
"ascending": "stigende",
"backward": "bakover",
"biography": "biografi",
"bitrate": "bithastighet",
"close": "lukk",
"codec": "kodek",
"comingSoon": "kommer snart…",
"create": "opprett",
"decrease": "minsk",
"disable": "deaktiver",
"disc": "skive",
"duration": "lengde",
"enable": "aktiver",
"expand": "utvid",
"favorite": "favoritt",
"filters": "filter",
"forceRestartRequired": "ta omstart for å la endringene trå i kraft... lukk meldingen for å ta omstart",
"forward": "fremover",
"gap": "avstand",
"home": "hjem",
"increase": "øke",
"left": "venstre",
"limit": "grense",
"menu": "meny",
"minimize": "minimer",
"modified": "modifisert",
"mbid": "MusicBrainz ID",
"name": "navn",
"no": "nei",
"none": "ingen",
"noResultsFromQuery": "spørringen ga ikke noe resultat",
"note": "merke",
"owner": "eier",
"playerMustBePaused": "spilleren må settes på pause",
"path": "sti",
"previousSong": "forrige $t(entity.track_one)",
"refresh": "frisk opp",
"rating": "vurdering",
"random": "vilkårlig",
"reset": "tilbakestill",
"restartRequired": "omstart nødvendig",
"save": "lagre",
"saveAs": "lagre som",
"saveAndReplace": "lagre og overskriv",
"search": "søk",
"trackGain": "forsterkningsgrad spor",
"trackPeak": "maksnivå spor",
"translation": "oversettelse",
"unknown": "ukjent",
"preview": "forhåndsvisning",
"share": "del",
"quit": "avslutt",
"size": "størrelse",
"setting": "innstilling",
"trackNumber": "spor",
"title": "tittel",
"channel_one": "kanal",
"channel_other": "kanaler",
"filter_one": "filter",
"filter_other": "filter",
"add": "legg til",
"edit": "rediger",
"resetToDefault": "nullstill",
"ok": "ok",
"reload": "last inn på nytt",
"action_one": "handling",
"action_other": "handlinger",
"year": "år",
"yes": "ja",
"descending": "synkende",
"dismiss": "avkreft",
"delete": "slett",
"description": "beskrivelse",
"manage": "håndtere",
"maximize": "maksimer",
"right": "høyre",
"sortOrder": "rekkefølge"
},
"entity": {
"smartPlaylist": "smart $t(entity.playlist_one)",
"album_one": "album",
"album_other": "album",
"albumArtist_one": "albumartist",
"albumArtist_other": "albumartister",
"albumArtistCount_one": "{{count}} albumartist",
"albumArtistCount_other": "{{count}} albumartister",
"albumWithCount_one": "{{count}} album",
"albumWithCount_other": "{{count}} album",
"favorite_one": "favoritt",
"favorite_other": "favoritter",
"folder_one": "mappe",
"folder_other": "mapper",
"play_one": "{{count}} avspilling",
"play_other": "{{count}} avspillinger",
"playlistWithCount_one": "{{count}} spilleliste",
"playlistWithCount_other": "{{count}} spillelister",
"artistWithCount_one": "{{count}} artist",
"artistWithCount_other": "{{count}} artister",
"genre_one": "sjanger",
"genre_other": "sjangere",
"track_one": "spor",
"track_other": "spor",
"genreWithCount_one": "{{count}} sjanger",
"genreWithCount_other": "{{count}} sjangere",
"playlist_one": "spilleliste",
"playlist_other": "spillelister",
"folderWithCount_one": "{{count}} mappe",
"folderWithCount_other": "{{count}} mapper",
"trackWithCount_one": "{{count}} spor",
"trackWithCount_other": "{{count}} spor",
"artist_one": "artist",
"artist_other": "artister",
"song_one": "sang",
"song_other": "sanger"
},
"error": {
"apiRouteError": "kan ikke behandle forespørselen",
"mpvRequired": "MPV er påkrevd",
"authenticationFailed": "autentisering feilet",
"badAlbum": "du ser denne siden fordi sangen ikke er med i et album. Mest sannsynlig opplever du dette problemet fordi du har en sang helt øverst i musikkmappen. jellyfin gruperer kun spor som ligger i en mappe.",
"endpointNotImplementedError": "endepunkt {{endpoint}} er ikke implementert for {{serverType}}",
"credentialsRequired": "innloggingsdetaljer er påkrevd",
"genericError": "en feil har oppstått",
"invalidServer": "ugyldig server",
"playbackError": "et problem oppstod ved avspilling av media",
"localFontAccessDenied": "ingen tilgang til lokale skrifttyper",
"loginRateError": "for mange innloggingsforsøk, vennligst prøv igjen om noen få sekunder",
"audioDeviceFetchError": "en feil oppstod ved innhenting av lydenheter",
"networkError": "at nettverksproblem har oppstått",
"openError": "kunne ikke åpne fil",
"serverNotSelectedError": "ingen server er valgt",
"remotePortError": "et problem oppstod med å sette serverport",
"systemFontError": "et problem oppstod med innlasting av systemskrifttyper",
"serverRequired": "server er påkrevd",
"sessionExpiredError": "sesjonen din har utløpt",
"remotePortWarning": "ta omstart av serveren for å aktivere ny port",
"remoteDisableError": "en problem oppstod ved å $t(common.disable) serveren",
"remoteEnableError": "et problem oppstod ved å $t(common.enable) serveren"
},
"filter": {
"bpm": "bpm",
"criticRating": "kritikervurdering",
"id": "id",
"name": "navn",
"bitrate": "bithastighet",
"albumArtist": "$t(entity.albumArtist_one)",
"artist": "$t(entity.artist_one)",
"biography": "biografi",
"album": "$t(entity.album_one)",
"duration": "lengde",
"favorited": "merket som favoritt",
"comment": "kommentar",
"communityRating": "fellesskapsvurdering",
"dateAdded": "lagt til dato",
"disc": "skive",
"isPublic": "er offentlig",
"isRecentlyPlayed": "er avspilt nylig",
"mostPlayed": "mest avspilt",
"owner": "$t(common.owner)",
"path": "sti",
"lastPlayed": "sist avspilt",
"rating": "vurdering",
"recentlyPlayed": "nylig avspilt",
"playCount": "antall avspillinger",
"recentlyUpdated": "nylig oppdatert",
"random": "vilkårlig",
"search": "søk",
"songCount": "antall sanger",
"title": "tittel",
"toYear": "til år",
"releaseDate": "utgivelsesdato",
"releaseYear": "utgivelsesår",
"note": "notat",
"isRated": "er vurdert",
"fromYear": "fra år",
"isCompilation": "er samling",
"isFavorited": "er merket som favoritt",
"recentlyAdded": "nylig lagt til",
"channels": "$t(common.channel_other)",
"genre": "$t(entity.genre_one)",
"trackNumber": "spor"
},
"form": {
"createPlaylist": {
"input_description": "$t(common.description)",
"input_owner": "$t(common.owner)",
"input_public": "offentlig",
"title": "opprett $t(entity.playlist_one)",
"input_name": "$t(common.name)",
"success": "$t(entity.playlist_one) opprettet"
},
"lyricSearch": {
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)",
"title": "sangtekstsøk"
},
"addServer": {
"ignoreCors": "ignorer cors ($t(common.restartRequired))",
"ignoreSsl": "ignorer ssl ($t(common.restartRequired))",
"error_savePassword": "en problem oppstod ved lagring av passord",
"input_savePassword": "lagre passord",
"input_url": "lenke",
"input_username": "brukernavn",
"success": "serveren er lagt til",
"input_legacyAuthentication": "aktiver tradisjonell autentisering",
"input_name": "servernavn",
"title": "legg til server",
"input_password": "passord"
},
"addToPlaylist": {
"success": "la $t(entity.trackWithCount, {\"count\": {{message}} }) til $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "legg til i $t(entity.playlist_one)",
"input_skipDuplicates": "hopp over duplikater",
"input_playlists": "$t(entity.playlist_other)"
},
"deletePlaylist": {
"title": "slett $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) er slettet",
"input_confirm": "skrive inn navnet på $t(entity.playlist_one) for å bekrefte"
},
"editPlaylist": {
"title": "rediger $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) er oppdatert"
},
"shareItem": {
"allowDownloading": "tillat nedlasting",
"description": "beskrivelse",
"createFailed": "opprettelse av delt ressurs feilet (er deling aktivert?)",
"setExpiration": "angi utløpstid",
"success": "del lenke som er kopiert til utklippstavlen (eller klikk her for å åpne)",
"expireInvalid": "utløpstid må være et fremtidig tidspunkt"
},
"updateServer": {
"success": "vellykket oppdatering av serveren",
"title": "oppdater server"
}
},
"page": {
"appMenu": {
"collapseSidebar": "slå sammen sidefelt",
"quit": "$t(common.quit)",
"selectServer": "velg server",
"version": "versjon {{version}}",
"manageServers": "administrere servere",
"goBack": "gå tilbake",
"openBrowserDevtools": "åpne utviklingsverktøy i nettleser",
"settings": "$t(common.setting_other)",
"expandSidebar": "utvid sidefelt",
"goForward": "gå fremover"
},
"contextMenu": {
"addToPlaylist": "$t(action.addToPlaylist)",
"showDetails": "hent info",
"moveToTop": "$t(action.moveToTop)",
"moveToBottom": "$t(action.moveToBottom)",
"numberSelected": "{{count}} valgt",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"createPlaylist": "$t(action.createPlaylist)",
"play": "$t(player.play)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"download": "last ned",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"removeFromQueue": "$t(action.removeFromQueue)",
"setRating": "$t(action.setRating)",
"addToFavorites": "$t(action.addToFavorites)",
"moveToNext": "$t(action.moveToNext)",
"playShuffled": "$t(player.shuffle)",
"shareItem": "del element",
"addFavorite": "$t(action.addToFavorites)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)"
},
"albumArtistDetail": {
"topSongs": "beste sanger",
"viewDiscography": "se diskografi",
"recentReleases": "nylige utgivelser",
"topSongsFrom": "beste sanger fra {{title}}",
"viewAllTracks": "se alle $t(entity.track_other)",
"viewAll": "se alle",
"about": "Om {{artist}}",
"appearsOn": "opptrer på",
"relatedArtists": "relatert $t(entity.artist_other)"
},
"albumList": {
"artistAlbums": "album av {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"title": "$t(entity.album_other)"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
},
"albumDetail": {
"moreFromArtist": "mer fra denne $t(entity.artist_one)",
"moreFromGeneric": "mer fra {{item}}",
"released": "utgitt"
},
"fullscreenPlayer": {
"config": {
"dynamicIsImage": "aktiver bakgrunnsbilde",
"lyricGap": "sangtekstavstand",
"dynamicImageBlur": "bilduskarphetstørrelse",
"lyricAlignment": "sangtekstjustering",
"lyricOffset": "sangtekstjustering (ms)",
"lyricSize": "sangtekststørrelse",
"opacity": "absorpsjon",
"showLyricMatch": "vis sangteksttreff",
"showLyricProvider": "vis sangteksttilbyder",
"synchronized": "synkronisert",
"unsynchronized": "usynkronisert",
"dynamicBackground": "dynamisk bakgrunn",
"useImageAspectRatio": "bruk sideforhold til bildet",
"followCurrentLyric": "følg sangtekst"
},
"noLyrics": "fant ikke sangtekst",
"lyrics": "sangtekst",
"upNext": "kommende",
"visualizer": "fremviser",
"related": "relatert"
},
"genreList": {
"title": "$t(entity.genre_other)",
"showAlbums": "vis $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "vis $t(entity.genre_one) $t(entity.track_other)"
},
"globalSearch": {
"title": "kommandoer",
"commands": {
"goToPage": "gå til side",
"searchFor": "søk etter {{query}}",
"serverCommands": "serverkommandoer"
}
},
"home": {
"recentlyPlayed": "nylig avspilt",
"explore": "utforsk biblioteket ditt",
"mostPlayed": "mest spilt",
"newlyAdded": "utgivelser nylig lagt til",
"title": "$t(common.home)"
},
"manageServers": {
"title": "administrere servere",
"url": "lenke",
"username": "brukernavn",
"editServerDetailsTooltip": "rediger serverdetaljer",
"removeServer": "fjern server",
"serverDetails": "serverdetaljer"
},
"itemDetail": {
"openFile": "vis spor i filbhehandleren",
"copiedPath": "vellykket kopiering av stien",
"copyPath": "kopier stien til utklippstavlen"
},
"trackList": {
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
"title": "$t(entity.track_other)",
"artistTracks": "spor fra {{artist}}"
},
"sidebar": {
"albumArtists": "$t(entity.albumArtist_other)",
"tracks": "$t(entity.track_other)",
"nowPlaying": "spilles nå",
"folders": "$t(entity.folder_other)",
"genres": "$t(entity.genre_other)",
"home": "$t(common.home)",
"albums": "$t(entity.album_other)",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)",
"shared": "delt $t(entity.playlist_other)",
"artists": "$t(entity.artist_other)"
},
"setting": {
"generalTab": "generelt",
"advanced": "avansert",
"hotkeysTab": "hurtigtaster",
"playbackTab": "avspilling",
"windowTab": "vindu"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
}
},
"player": {
"addLast": "legg til sist",
"queue_remove": "fjern valgte",
"queue_moveToBottom": "flytt valgte til toppen",
"addNext": "legg til som neste",
"favorite": "favoritt",
"mute": "skru av lyden",
"muted": "lyden er skrudd av",
"next": "neste",
"repeat_all": "gjenta alle",
"playbackFetchCancel": "dette kommer til å ta en stund... lukk denne meldingen for å avbryte",
"playRandom": "spill vilkårlig",
"queue_clear": "tøm kø",
"repeat_off": "gjentakelse er deaktivert",
"playbackFetchInProgress": "laster sanger…",
"repeat": "gjenta",
"play": "spill",
"previous": "forrige",
"queue_moveToTop": "flytt valgte til bunnen",
"playbackFetchNoResults": "ingen sanger funnet",
"playbackSpeed": "avspillingshastighet",
"playSimilarSongs": "spill lignende sanger"
}
}
+98 -19
View File
@@ -20,7 +20,8 @@
"openIn": {
"lastfm": "Otwórz w Last.fm",
"musicbrainz": "Otwórz w MusicBrainz"
}
},
"moveToNext": "przesuń na następne"
},
"common": {
"increase": "zwiększ",
@@ -113,7 +114,8 @@
"trackPeak": "peak utworu",
"codec": "kodek",
"preview": "podgląd",
"close": "zamknij"
"close": "zamknij",
"translation": "tłumaczenie"
},
"entity": {
"genre_one": "gatunek",
@@ -161,7 +163,13 @@
"genreWithCount_many": "{{count}} gatunków",
"trackWithCount_one": "{{count}} utwór",
"trackWithCount_few": "{{count}} utwory",
"trackWithCount_many": "{{count}} utworów"
"trackWithCount_many": "{{count}} utworów",
"play_one": "{{count}} odtworzenie",
"play_few": "{{count}} odtworzenia",
"play_many": "{{count}} odtworzeń",
"song_one": "piosenka",
"song_few": "piosenki",
"song_many": "piosenek"
},
"error": {
"remotePortWarning": "uruchom ponownie serwer aby używać nowego portu",
@@ -259,7 +267,7 @@
"error_savePassword": "wystąpił błąd podczas próby zapisania hasła"
},
"addToPlaylist": {
"success": "dodano {{message}} $t(entity.track_other) do {{numOfPlaylists}} $t(entity.playlist_other)",
"success": "dodano $t(entity.trackWithCount, {\"count\": {{message}} }) do $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"title": "dodano do $t(entity.playlist_one)",
"input_skipDuplicates": "pomiń duplikaty",
"input_playlists": "$t(entity.playlist_other)"
@@ -278,7 +286,9 @@
"title": "wyszukiwanie tekstów"
},
"editPlaylist": {
"title": "edytuj $t(entity.playlist_one)"
"title": "edytuj $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) zaktualizowana pomyślnie",
"publicJellyfinNote": "Z jakiegoś powodu Jellyfin nie udostępnia informacji na temat publiczności playlisty. Jeżeli chcesz, aby ta pozostała publiczna, mniej wybraną poniższą opcję"
},
"shareItem": {
"allowDownloading": "zezwól na pobieranie",
@@ -304,11 +314,14 @@
"useImageAspectRatio": "użyj współczynnika proporcji obrazu",
"lyricGap": "odstępy tekstu",
"dynamicImageBlur": "rozmiar rozmycia obrazu",
"dynamicIsImage": "włącz obraz w tle"
"dynamicIsImage": "włącz obraz w tle",
"lyricOffset": "opóźnienie tekstów (ms)"
},
"upNext": "następny",
"lyrics": "tekst",
"related": "powiązane"
"related": "powiązane",
"visualizer": "wizualizer",
"noLyrics": "nie znaleziono tekstu"
},
"appMenu": {
"selectServer": "wybierz serwer",
@@ -340,11 +353,16 @@
"numberSelected": "zaznaczono {{count}}",
"removeFromQueue": "$t(action.removeFromQueue)",
"shareItem": "udostępnij pozycję",
"showDetails": "zobacz informacje"
"showDetails": "zobacz informacje",
"download": "pobierz",
"playShuffled": "$t(player.shuffle)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"moveToNext": "$t(action.moveToNext)"
},
"albumDetail": {
"moreFromArtist": "więcej od $t(entity.artist_one)",
"moreFromGeneric": "więcej od {{item}}"
"moreFromGeneric": "więcej od {{item}}",
"released": "wydany"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
@@ -384,7 +402,8 @@
"playbackTab": "odtworzenia",
"generalTab": "ogólne",
"hotkeysTab": "skróty klawiszowe",
"windowTab": "okno"
"windowTab": "okno",
"advanced": "zaawansowane"
},
"trackList": {
"title": "$t(entity.track_other)",
@@ -417,6 +436,17 @@
"copyPath": "kopiuj ścieżkę do schowka",
"copiedPath": "ścieżka została skopiowana pomyślnie",
"openFile": "pokaż utwór w menedżerze plików"
},
"manageServers": {
"title": "zarządzaj serwerami",
"url": "URL",
"username": "nazwa użytkownika",
"removeServer": "usuń serwer",
"serverDetails": "szczegóły serwera",
"editServerDetailsTooltip": "edytuj szczegóły serwera"
},
"playlist": {
"reorder": "zmiana kolejności jest możliwa tylko podczas sortowania według id"
}
},
"player": {
@@ -431,7 +461,7 @@
"skip_back": "przeskocz do tyłu",
"favorite": "ulubione",
"next": "następny",
"shuffle": "losowa kolejność",
"shuffle": "odtwarzaj losowo",
"playbackFetchNoResults": "nie znaleziono utworów",
"playbackFetchInProgress": "wczytywanie utworów…",
"addNext": "dodaj następny",
@@ -448,7 +478,9 @@
"shuffle_off": "losowa kolejność wyłączona",
"addLast": "dodaj na końcu",
"mute": "wycisz",
"skip_forward": "przeskocz do przodu"
"skip_forward": "przeskocz do przodu",
"viewQueue": "zobacz kolejkę",
"playSimilarSongs": "odtwarzaj podobne"
},
"setting": {
"crossfadeStyle_description": "wybierz styl przenikania, który ma być używany do odtwarzania dźwięku",
@@ -471,7 +503,7 @@
"hotkey_rate1": "oceń na 1 gwiazdkę",
"hotkey_skipForward": "przeskocz do przodu",
"disableLibraryUpdateOnStartup": "wyłącz wyszukiwanie aktualizacji podczas uruchamiania aplikacji",
"discordApplicationId_description": "id dla aplikacji {{discord}} obszernie obecne (domyślnie {{defaultId}})",
"discordApplicationId_description": "id dla aplikacji {{discord}} rich presence (domyślnie {{defaultId}})",
"gaplessAudio": "dźwięk bez przerw",
"hotkey_playbackPlay": "odtwarzaj",
"hotkey_togglePreviousSongFavorite": "dodaj $t(common.previousSong) do ulubionych",
@@ -501,7 +533,7 @@
"crossfadeDuration_description": "ustaw czas trwania efektu przenikania",
"language": "język",
"hotkey_toggleShuffle": "przełącz kolejność losową",
"discordRichPresence_description": "włącz status odtwarzania w {{discord}} rich presence. Dzięki temu będą wyświetlane informacje takie jak: {{icon}}, {{playing}} i {{paused}}. ",
"discordRichPresence_description": "włącz status odtwarzania w {{discord}} (rich presence). Dzięki temu będą wyświetlane informacje takie jak: {{icon}}, {{playing}} i {{paused}}. ",
"audioDevice": "urządzenia dźwiękowe",
"hotkey_rate2": "oceń na 2 gwiazdki",
"exitToTray": "zamknij do zasobnika",
@@ -522,7 +554,7 @@
"customFontPath": "niestandardowa ścieżka czcionki",
"followLyric": "podążaj za tekstem",
"crossfadeDuration": "czas trwania przenikania",
"discordIdleStatus": "pokaż obszerne informacje w stanie bezczynności",
"discordIdleStatus": "pokaż status w stanie bezczynności",
"audioPlayer": "odtwarzacz dźwięku",
"hotkey_zoomOut": "oddal",
"hotkey_unfavoriteCurrentSong": "usuń $t(common.currentSong) z ulubionych",
@@ -537,7 +569,7 @@
"customFontPath_description": "ustaw ścieżkę dla niestandardowych czcionek dla aplikacji",
"gaplessAudio_optionWeak": "słabe (rekomendowane)",
"hotkey_playbackStop": "zatrzymaj",
"discordRichPresence": "{{discord}} obszernie obecny",
"discordRichPresence": "Status {{discord}} (rich presence)",
"font_description": "ustaw czcionkę dla aplikacji",
"playButtonBehavior_optionPlay": "$t(player.play)",
"minimumScrobblePercentage": "minimalny czas trwania scrobble (procentowy)",
@@ -630,7 +662,52 @@
"genreBehavior": "domyślne zachowanie strony gatunek",
"externalLinks_description": "umożliwia wyświetlanie linków zewnętrznych (Last.fm, MusicBrainz) na stronach artystów/albumów",
"homeConfiguration": "konfiguracja strony głównej",
"homeConfiguration_description": "konfiguracja elementów wyświetlanych na stronie głównej i ich kolejności"
"homeConfiguration_description": "konfiguracja elementów wyświetlanych na stronie głównej i ich kolejności",
"albumBackground_description": "dodaje obraz tła dla stron albumu zawierających grafikę albumu",
"albumBackgroundBlur": "rozmiar rozmycia obrazu tła albumu",
"albumBackgroundBlur_description": "dostosowywuje ilość rozmycia nakladanego na obraz tła albumu",
"albumBackground": "obraz tła albumu",
"artistConfiguration_description": "skonfiguruj jakie elementy są pokazywane, i w jakiej kolejności, na stronie albumu wykonawcy",
"discordListening_description": "pokazuje status jako słucha zamiast w grze",
"transcodeNote": "przynosi efekt po 1 (web) - 2 (mpv) piosenkach",
"transcode_description": "włącza transkodowanie na inne formaty",
"transcodeBitrate": "bitrate do transkodowania",
"transcode": "włącz transkodowanie",
"translationApiProvider": "usługodawca do api tłumaczeń",
"translationApiProvider_description": "wybór usługodawcy do api tłumaczeń",
"translationApiKey": "klucz api do tłumaczeń",
"transcodeFormat_description": "wybiera format do transkodowania. zostaw pusty aby serwer wybrał format",
"translationApiKey_description": "klucz api do tłumaczenia (Obsługuje tylko globalny endpoint)",
"homeFeature": "karuzela polecanych na stronie głównej",
"customCssEnable": "włącz niestandardowy css",
"customCssEnable_description": "pozwalaj na pisanie niestandardowego css.",
"customCssNotice": "Ostrzeżenie: chociaż istnieje pewne filtrowanie (uniemożliwia używanie url() i content:), używanie niestandardowego CSS-a może stwarzać ryzyko przez zmiany w interfejsie.",
"customCss_description": "zawartość niestandardowego css. Uwaga: content i zdalne url są niedozwolonymi właściwościami. Podgląd twojej zawartości jest pokazana poniżej. Dodatkowe pola których nie ustawiłeś, są obecne z powodu sanityzacji.",
"customCss": "niestandardowy css",
"doubleClickBehavior": "zakolejkuj wszystkie wyszukane utwory gdy podwójnie kliknięto",
"trayEnabled_description": "pokaż/ukryj ikonę/menu w zasobniku. jeżeli wyłączone, wyłącza też minimalizowanie.wyjście do zasobnika",
"webAudio_description": "używaj web audio. włącza to zaawansowane funkcje takie jak replaygain. wyłącz jeżeli nie działa poprawnie",
"artistConfiguration": "konfiguracja strony albumu wykonawcy",
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
"playerbarOpenDrawer_description": "pozwala przełączyć na odtwarzacz pełnoekranowy po kliknięciu paska odtwarzania",
"playerbarOpenDrawer": "przełącznik pełnego ekranu na pasku odtwarzania",
"imageAspectRatio": "używaj natywnych proporcji okładki",
"volumeWidth": "szerokość paska głośności",
"discordListening": "pokazuj status jako słucha",
"imageAspectRatio_description": "jeżeli włączone, okładka będzie pokazywana z użyciem jej natywnych proporcji. dla okładek które nie mają proporcji 1:1, pozostałe miejsce będzie puste",
"volumeWidth_description": "szerokość paska głośności",
"contextMenu_description": "pozwala ci na ukrycie elementów które są pokazywane w menu po kliknięciu prawym przyciskiem myszy na element. elementy które zostały odznaczone będą ukryte",
"contextMenu": "konfiguracja menu kontekstowego (pod prawym przyciskiem myszy)",
"transcodeBitrate_description": "wybiera bitrate do transkodowania. 0 pozwala wybrać to serwerowi",
"transcodeFormat": "format do transkodowania",
"translationTargetLanguage_description": "język do którego będzie tłumaczona treść",
"trayEnabled": "pokazuj w zasobniku",
"webAudio": "używaj web audio",
"homeFeature_description": "ustawienie powoduje to czy wyświetlana jest karuzela z polecanymi utworami na stronie głównej",
"doubleClickBehavior_description": "jeżeli włączone, wszystkie pasujące utwory w wyszukiwaniu zostaną zakolejkowane. w przeciwnym wypadku, tylko kliknięty będzie zakolejkowany",
"lastfmApiKey": "klucz API {{lastfm}}",
"lastfmApiKey_description": "klucz API dla {{lastfm}}. wymagany dla okładek",
"translationTargetLanguage": "docelowy język tłumaczenia"
},
"table": {
"config": {
@@ -646,7 +723,8 @@
"autoFitColumns": "automatyczne dopasowanie kolumn",
"size": "$t(common.size)",
"itemSize": "rozmiar elementu (px)",
"itemGap": "odstęp między elementami (px)"
"itemGap": "odstęp między elementami (px)",
"followCurrentSong": "śledź aktualną piosenkę"
},
"label": {
"releaseDate": "data premiery",
@@ -675,7 +753,8 @@
"favorite": "$t(common.favorite)",
"year": "$t(common.year)",
"albumArtist": "$t(entity.albumArtist_one)",
"codec": "$t(common.codec)"
"codec": "$t(common.codec)",
"songCount": "$t(entity.track_other)"
}
},
"column": {
+243 -25
View File
@@ -1,6 +1,6 @@
{
"common": {
"backward": "voltar",
"backward": "para trás",
"areYouSure": "tem certeza?",
"add": "adicionar",
"ascending": "ascendente",
@@ -27,7 +27,7 @@
"title": "titulo",
"create": "criar",
"confirm": "confirmar",
"home": "inicio",
"home": "início",
"comingSoon": "em breve…",
"channel_one": "canal",
"channel_many": "canais",
@@ -56,9 +56,9 @@
"path": "caminho",
"no": "não",
"owner": "dono",
"forward": "avançar",
"forward": "para frente",
"forceRestartRequired": "reinicie para aplicar as alterações… feche a notificação para reiniciar",
"setting": "contexto",
"setting": "configuração",
"version": "versão",
"filter_one": "filtro",
"filter_many": "filtros",
@@ -74,7 +74,7 @@
"restartRequired": "é necessário reiniciar",
"previousSong": "anterior $t(entity.track_one)",
"noResultsFromQuery": "a consulta não retornou resultados",
"quit": "abandonar",
"quit": "sair",
"search": "procurar",
"saveAs": "salvar como",
"yes": "sim",
@@ -87,7 +87,11 @@
"preview": "pré-visualizar",
"share": "compartilhar",
"close": "fechar",
"translation": "tradução"
"translation": "tradução",
"albumGain": "ganho do álbum",
"trackPeak": "pico da faixa",
"albumPeak": "pico do álbum",
"trackGain": "ganho da faixa"
},
"action": {
"goToPage": "vá para página",
@@ -116,7 +120,8 @@
"form": {
"deletePlaylist": {
"title": "deletar $t(entity.playlist_one)",
"input_confirm": "escreva o nome da $t(entity.playlist_one) para confirmar"
"input_confirm": "escreva o nome da $t(entity.playlist_one) para confirmar",
"success": "$t(entity.playlist_one) deletada com sucesso"
},
"addServer": {
"title": "adicionar servidor",
@@ -128,19 +133,25 @@
"input_url": "url",
"success": "servidor adicionado com sucesso",
"input_name": "nome do servidor",
"input_username": "nome de usuário"
"input_username": "nome de usuário",
"ignoreCors": "ignorar CORS ($t(common.restartRequired))"
},
"createPlaylist": {
"title": "criar $t(entity.playlist_one)",
"input_public": "público",
"input_description": "$t(common.description)",
"success": "$t(entity.playlist_one) criada com sucesso"
"success": "$t(entity.playlist_one) criada com sucesso",
"input_owner": "$t(common.owner)",
"input_name": "$t(common.name)"
},
"updateServer": {
"title": "atualizar servidor"
"title": "atualizar servidor",
"success": "servidor atualizado com sucesso"
},
"editPlaylist": {
"title": "editar $t(entity.playlist_one)"
"title": "editar $t(entity.playlist_one)",
"publicJellyfinNote": "O Jellyfin por algum motivo não expõe se uma playlist é pública ou não. Se você deseja que ela permaneça pública, por favor selecione a seguinte entrada",
"success": "$t(entity.playlist_one) atualizada com sucesso"
},
"addToPlaylist": {
"title": "adicionar à $t(entity.playlist_one)",
@@ -149,14 +160,52 @@
"success": "adicionado $t(entity.trackWithCount, {\"count\": {{message}} }) para $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })"
},
"lyricSearch": {
"title": "pesquisa de letras"
"title": "pesquisa de letras",
"input_artist": "$t(entity.artist_one)",
"input_name": "$t(common.name)"
},
"shareItem": {
"createFailed": "falha ao criar compartilhamento (o compartilhamento está ativado?)",
"setExpiration": "definir expiração",
"success": "link de compartilhamento copiado para a área de transferência (ou clique aqui para abrir)",
"allowDownloading": "permitir downloads",
"description": "descrição",
"expireInvalid": "a expiração deve ser uma data futura"
},
"queryEditor": {
"input_optionMatchAny": "corresponder qualquer um",
"input_optionMatchAll": "corresponder todos"
}
},
"setting": {
"discordIdleStatus_description": "quando ativado, atualiza o status enquanto o player está ocioso",
"discordUpdateInterval_description": "o tempo em segundos entre cada atualização (mínimo 15 segundos)",
"playButtonBehavior_description": "define o comportamento padrão do botão play ao adicionar músicas à fila",
"discordApplicationId": "{{discord}} ID do aplicativo"
"discordApplicationId": "{{discord}} ID do aplicativo",
"audioPlayer": "player de áudio",
"applicationHotkeys": "teclas de atalho da aplicação",
"applicationHotkeys_description": "configure as teclas de atalho da aplicação. clique na caixa de seleção para definir como tecla de atalho global (somente desktop)",
"contextMenu": "configuração do menu de contexto (clique do botão direito do mouse)",
"clearQueryCache": "limpar cache do feishin",
"clearCache": "limpar cache do navegador",
"clearQueryCache_description": "uma 'limpeza leve' do feishin. isso irá renovar playlists, metadados de faixas, e resetar letras salvas. as configurações, as credenciais de servidor e o cache de imagens serão mantidos",
"audioPlayer_description": "selecione o player de áudio usado para reprodução",
"audioExclusiveMode": "modo de áudio exclusivo",
"buttonSize": "tamanho do botão da barra de reprodução",
"albumBackground_description": "adiciona uma imagem de fundo contendo a arte do álbum para a página de álbum",
"clearCache_description": "uma 'limpeza geral' do feishin. em adição a limpar o cache do feishin, limpa o cache do navegador (imagens salvas e outros recursos). as credenciais de servidor e as configurações serão mantidas",
"clearCacheSuccess": "cache limpo com sucesso",
"audioDevice": "dispositivo de áudio",
"audioDevice_description": "selecione o dispositivo de áudio usado para reprodução (somente player web)",
"audioExclusiveMode_description": "ativar modo de saída exclusiva. Neste modo, o sistema é geralmente bloqueado, e apenas mpv terá saída de áudio",
"accentColor": "cor de realce",
"accentColor_description": "define a cor de realce para a aplicação",
"artistConfiguration": "configuração da página de artista de álbum",
"artistConfiguration_description": "configure quais itens serão mostrados, e em qual ordem, na página de artista de álbum",
"buttonSize_description": "o tamanho dos botões da barra de reprodução",
"albumBackgroundBlur": "tamanho de desfoque da imagem de fundo do álbum",
"albumBackgroundBlur_description": "ajusta a quantidade de desfoque aplicada para a imagem de fundo do álbum",
"albumBackground": "imagem de fundo do álbum"
},
"table": {
"config": {
@@ -184,22 +233,142 @@
"title": "$t(entity.albumArtist_other)"
},
"genreList": {
"title": "$t(entity.genre_other)"
"title": "$t(entity.genre_other)",
"showTracks": "mostrar $t(entity.genre_one) $t(entity.track_other)",
"showAlbums": "mostrar $t(entity.genre_one) $t(entity.album_other)"
},
"trackList": {
"title": "$t(entity.track_other)"
"title": "$t(entity.track_other)",
"artistTracks": "faixas de {{artist}}",
"genreTracks": "\"{{genre}}\" $t(entity.track_other)"
},
"globalSearch": {
"title": "comandos"
"title": "comandos",
"commands": {
"serverCommands": "comandos do servidor",
"goToPage": "ir para a página",
"searchFor": "buscar por {{query}}"
}
},
"sidebar": {
"home": "$t(common.home)"
"home": "$t(common.home)",
"tracks": "$t(entity.track_other)",
"shared": "$t(entity.playlist_other) compartilhada",
"albums": "$t(entity.album_other)",
"genres": "$t(entity.genre_other)",
"folders": "$t(entity.folder_other)",
"albumArtists": "$t(entity.albumArtist_other)",
"artists": "$t(entity.artist_other)",
"nowPlaying": "tocando agora",
"playlists": "$t(entity.playlist_other)",
"search": "$t(common.search)",
"settings": "$t(common.setting_other)"
},
"playlistList": {
"title": "$t(entity.playlist_other)"
},
"albumList": {
"title": "$t(entity.album_other)"
"title": "$t(entity.album_other)",
"artistAlbums": "álbuns de {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)"
},
"appMenu": {
"openBrowserDevtools": "abrir ferramentas do desenvolvedor",
"quit": "$t(common.quit)",
"selectServer": "selecionar servidor",
"collapseSidebar": "recolher barra lateral",
"expandSidebar": "expandir barra lateral",
"goBack": "voltar",
"goForward": "avançar",
"version": "versão {{version}}",
"manageServers": "gerenciar servidores",
"settings": "$t(common.setting_other)"
},
"contextMenu": {
"moveToTop": "$t(action.moveToTop)",
"moveToBottom": "$t(action.moveToBottom)",
"removeFromFavorites": "$t(action.removeFromFavorites)",
"numberSelected": "{{count}} selecionado",
"addFavorite": "$t(action.addToFavorites)",
"addLast": "$t(player.addLast)",
"addNext": "$t(player.addNext)",
"addToFavorites": "$t(action.addToFavorites)",
"playSimilarSongs": "$t(player.playSimilarSongs)",
"removeFromQueue": "$t(action.removeFromQueue)",
"play": "$t(player.play)",
"playShuffled": "$t(player.shuffle)",
"createPlaylist": "$t(action.createPlaylist)",
"download": "baixar",
"shareItem": "compartilhar item",
"showDetails": "obter informações",
"addToPlaylist": "$t(action.addToPlaylist)",
"deletePlaylist": "$t(action.deletePlaylist)",
"deselectAll": "$t(action.deselectAll)",
"moveToNext": "$t(action.moveToNext)",
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
"setRating": "$t(action.setRating)"
},
"albumArtistDetail": {
"viewAllTracks": "ver todas as $t(entity.track_other)",
"appearsOn": "aparece em",
"recentReleases": "lançamentos recentes",
"viewDiscography": "ver discografia",
"relatedArtists": "$t(entity.artist_other) relacionados",
"viewAll": "ver tudo",
"topSongsFrom": "músicas mais tocadas de {{title}}",
"topSongs": "músicas mais tocadas",
"about": "Sobre {{artist}}"
},
"fullscreenPlayer": {
"config": {
"unsynchronized": "não sincronizado",
"dynamicIsImage": "habilitar imagem de fundo",
"dynamicImageBlur": "tamanho do desfoque da imagem",
"lyricAlignment": "alinhamento da letra",
"showLyricMatch": "exibir correspondência da letra",
"showLyricProvider": "exibir origem da letra",
"synchronized": "sincronizado",
"lyricOffset": "deslocamento da letra (ms)",
"followCurrentLyric": "acompanhar letra",
"useImageAspectRatio": "usar proporção da imagem",
"lyricGap": "espaçamento da letra",
"lyricSize": "tamanho da letra",
"dynamicBackground": "fundo dinâmico",
"opacity": "opacidade"
},
"related": "relacionado",
"visualizer": "visualizador",
"upNext": "a seguir",
"lyrics": "letra",
"noLyrics": "nenhuma letra encontrada"
},
"albumDetail": {
"moreFromArtist": "mais deste $t(entity.artist_one)",
"moreFromGeneric": "mais de {{item}}",
"released": "lançado"
},
"itemDetail": {
"copyPath": "copiar caminho para a área de transferência",
"copiedPath": "caminho copiado com sucesso",
"openFile": "mostrar faixa no gerenciador de arquivos"
},
"manageServers": {
"serverDetails": "detalhes do servidor",
"url": "URL",
"username": "nome de usuário",
"editServerDetailsTooltip": "editar detalhes do servidor",
"removeServer": "remover servidor",
"title": "gerenciar servidores"
},
"setting": {
"generalTab": "geral",
"hotkeysTab": "teclas de atalho",
"windowTab": "janela",
"advanced": "avançado",
"playbackTab": "reprodução"
},
"playlist": {
"reorder": "reordenar apenas disponível quando ordenado pelo id"
}
},
"filter": {
@@ -229,13 +398,55 @@
"recentlyAdded": "adicionado recentemente",
"releaseDate": "data de lançamento",
"recentlyPlayed": "tocado recentemente",
"criticRating": "Nota da crítica",
"criticRating": "avaliação da crítica",
"isFavorited": "é favoritado",
"releaseYear": "ano de lançamento"
"releaseYear": "ano de lançamento",
"rating": "avaliação",
"artist": "$t(entity.artist_one)",
"bpm": "bpm",
"channels": "$t(common.channel_other)",
"comment": "comentário",
"owner": "$t(common.owner)",
"path": "caminho",
"id": "id",
"bitrate": "bitrate",
"isRated": "possui avaliação",
"note": "nota",
"albumCount": "número de $t(entity.album_other)",
"genre": "$t(entity.genre_one)"
},
"player": {
"playbackFetchNoResults": "nenhuma música encontrada",
"playbackFetchInProgress": "carregando músicas…"
"playbackFetchInProgress": "carregando músicas…",
"skip_forward": "avançar",
"mute": "mudo",
"playSimilarSongs": "tocar músicas similares",
"skip": "pular",
"stop": "parar",
"addNext": "adicionar a seguir",
"muted": "mudo",
"queue_clear": "limpar fila",
"toggleFullscreenPlayer": "alternar player de tela cheia",
"addLast": "adicionar no final",
"next": "próximo",
"play": "tocar",
"playRandom": "tocar aleatório",
"shuffle_off": "aleatório desativado",
"queue_moveToBottom": "mover selecionados para o topo",
"queue_moveToTop": "mover selecionados para o fim",
"skip_back": "retroceder",
"unfavorite": "remover favorito",
"playbackSpeed": "velocidade de reprodução",
"previous": "anterior",
"favorite": "favorito",
"playbackFetchCancel": "isso está demorando um pouco... feche a notificação para cancelar",
"queue_remove": "remover selecionados",
"repeat": "repetir",
"repeat_all": "repetir tudo",
"repeat_off": "repetição desativada",
"shuffle": "tocar aleatório",
"pause": "pausar",
"viewQueue": "ver fila"
},
"entity": {
"albumArtist_one": "artista do álbum",
@@ -277,12 +488,19 @@
"genreWithCount_one": "{{count}} gênero",
"genreWithCount_many": "{{count}} gêneros",
"genreWithCount_other": "{{count}} gêneros",
"trackWithCount_one": "faixa",
"trackWithCount_many": "faixas",
"trackWithCount_other": "faixas",
"trackWithCount_one": "{{count}} faixa",
"trackWithCount_many": "{{count}} faixas",
"trackWithCount_other": "{{count}} faixas",
"track_one": "faixa",
"track_many": "faixas",
"track_other": "faixas"
"track_other": "faixas",
"smartPlaylist": "$t(entity.playlist_one) inteligente",
"song_one": "música",
"song_many": "músicas",
"song_other": "músicas",
"play_one": "{{count}} reprodução",
"play_many": "{{count}} reproduções",
"play_other": "{{count}} reproduções"
},
"error": {
"remotePortWarning": "reinicie o servidor para aplicar a nova porta",
+8 -4
View File
@@ -20,7 +20,8 @@
"openIn": {
"lastfm": "открыть на Last.fm",
"musicbrainz": "открыть на MusicBrainz"
}
},
"moveToNext": "следующий"
},
"common": {
"backward": "назад",
@@ -437,13 +438,15 @@
},
"albumDetail": {
"moreFromArtist": "больше от $t(entity.artist_one)",
"moreFromGeneric": "больше из {{item}}"
"moreFromGeneric": "больше из {{item}}",
"released": "выпущен"
},
"setting": {
"playbackTab": "воспроизведение",
"generalTab": "общее",
"hotkeysTab": "горячие клавиши",
"windowTab": "окно"
"windowTab": "окно",
"advanced": "расширенные"
},
"albumArtistList": {
"title": "$t(entity.albumArtist_other)"
@@ -540,7 +543,8 @@
"title": "поиск слов песни"
},
"editPlaylist": {
"title": "редактировать $t(entity.playlist_one)"
"title": "редактировать $t(entity.playlist_one)",
"success": "$t(entity.playlist_one) обновлён успешно"
},
"shareItem": {
"success": "ссылка скопирована в буфер обмена (нажмите здесь, чтобы открыть)",
+767
View File
@@ -0,0 +1,767 @@
{
"action": {
"addToFavorites": "$ t இல் சேர்க்கவும் (entity.foavorite_other)",
"clearQueue": "தெளிவான வரிசை",
"goToPage": "பக்கத்திற்குச் செல்லுங்கள்",
"moveToBottom": "கீழே செல்லுங்கள்",
"moveToTop": "மேலே செல்லுங்கள்",
"refresh": "$ t (காமன்.ரெஃப்ரெச்)",
"removeFromFavorites": "$ t இலிருந்து அகற்று (entity.foavorite_other)",
"removeFromPlaylist": "$ t இலிருந்து அகற்று (entity.playlist_one)",
"removeFromQueue": "வரிசையிலிருந்து அகற்று",
"setRating": "மதிப்பீட்டை அமைக்கவும்",
"toggleSmartPlaylistEditor": "மாற்று $ t (entity.smartplaylist) ஆசிரியர்",
"viewPlaylists": "$ t (entity.playlist_other) காண்க",
"addToPlaylist": "$ t இல் சேர்க்கவும் (entity.playlist_one)",
"createPlaylist": "$ t ஐ உருவாக்கவும் (entity.playlist_one)",
"deletePlaylist": "$ t (entity.playlist_one) ஐ நீக்கு",
"deselectAll": "அனைத்தையும் தேர்வு செய்யுங்கள்",
"editPlaylist": "திருத்து $ t (entity.playlist_one)",
"moveToNext": "அடுத்து செல்லுங்கள்",
"openIn": {
"lastfm": "Last.fm இல் திறந்திருக்கும்",
"musicbrainz": "மியூசிக் பிரைன்ச் திறந்திருக்கும்"
}
},
"common": {
"description": "விவரம்",
"minimize": "குறைக்கவும்",
"modified": "மாற்றியமைக்கப்பட்ட",
"noResultsFromQuery": "வினவல் எந்த முடிவுகளும் திரும்பவில்லை",
"note": "குறிப்பு",
"ok": "சரி",
"configure": "உள்ளமைக்கவும்",
"confirm": "உறுதிப்படுத்தவும்",
"create": "உருவாக்கு",
"currentSong": "தற்போதைய $ t (entity.track_one)",
"decrease": "குறைவு",
"action_one": "செயல்",
"action_other": "செயல்கள்",
"add": "கூட்டு",
"albumGain": "ஆல்பம் ஆதாயம்",
"albumPeak": "ஆல்பம் உச்சம்",
"areYouSure": "நீங்கள் உறுதியாக இருக்கிறீர்களா?",
"ascending": "ஏறுதல்",
"backward": "பின்னோக்கு",
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"cancel": "ரத்துசெய்",
"center": "நடுவண்",
"channel_one": "வாய்க்கால்",
"channel_other": "சேனல்கள்",
"clear": "தெளிவான",
"close": "மூடு",
"codec": "புரிப்பு",
"collapse": "சரிவு",
"comingSoon": "விரைவில் வருகிறது…",
"delete": "நீக்கு",
"descending": "இறங்கு",
"disable": "முடக்கு",
"disc": "வட்டு",
"dismiss": "தள்ளுபடி",
"duration": "காலம்",
"edit": "தொகு",
"enable": "இயக்கு",
"saveAs": "என சேமி",
"expand": "விரிவாக்கு",
"favorite": "பிடித்த",
"filter_one": "வடிப்பி",
"filter_other": "வடிப்பான்கள்",
"filters": "வடிப்பான்கள்",
"forceRestartRequired": "மாற்றங்களைப் பயன்படுத்த மறுதொடக்கம்… மறுதொடக்கம் செய்ய அறிவிப்பை மூடு",
"forward": "முன்னோக்கி",
"gap": "இடைவெளி",
"home": "வீடு",
"increase": "அதிகரிப்பு",
"left": "இடது",
"limit": "வரம்பு",
"manage": "நிர்வகிக்கவும்",
"maximize": "அதிகரிக்கவும்",
"menu": "பட்டியல்",
"mbid": "மியூசிக் பிரேன்ச் ஐடி",
"name": "பெயர்",
"no": "இல்லை",
"none": "எதுவுமில்லை",
"owner": "உரிமையாளர்",
"path": "பாதை",
"playerMustBePaused": "வீரர் இடைநிறுத்தப்பட வேண்டும்",
"preview": "முன்னோட்டம்",
"previousSong": "முந்தைய $ t (entity.track_one)",
"quit": "வெளியேறு",
"random": "சீரற்ற",
"rating": "செயல்வரம்பு",
"refresh": "புதுப்பிப்பு",
"reload": "ஏற்றவும்",
"reset": "மீட்டமை",
"resetToDefault": "இயல்புநிலைக்கு மீட்டமைக்கவும்",
"restartRequired": "மறுதொடக்கம் தேவை",
"right": "வலது",
"save": "சேமி",
"saveAndReplace": "சேமித்து மாற்றவும்",
"search": "தேடல்",
"setting": "அமைத்தல்",
"share": "பங்கு",
"size": "அளவு",
"sortOrder": "ஒழுங்கு",
"unknown": "தெரியவில்லை",
"version": "பதிப்பு",
"year": "ஆண்டு",
"yes": "ஆம்",
"title": "தலைப்பு",
"trackNumber": "மின்தடம்",
"trackGain": "தடமறிதல்",
"trackPeak": "ட்ராக் பீக்",
"translation": "மொழிபெயர்ப்பு"
},
"entity": {
"folderWithCount_one": "{{count}} கோப்புறை",
"folderWithCount_other": "{{count}} கோப்புறைகள்",
"genre_one": "வகை",
"genre_other": "வகைகள்",
"genreWithCount_one": "{{count}} வகை",
"genreWithCount_other": "{{count}} வகைகள்",
"album_one": "ஆல்பம்",
"album_other": "ஆல்பம்",
"albumArtist_one": "ஆல்பம் கலைஞர்",
"albumArtist_other": "ஆல்பம் கலைஞர்கள்",
"albumArtistCount_one": "{{count}} ஆல்பம் கலைஞர்",
"albumArtistCount_other": "{{count}} ஆல்பம் கலைஞர்கள்",
"albumWithCount_one": "{{count}} ஆல்பம்",
"albumWithCount_other": "{{count}} ஆல்பங்கள்",
"artist_one": "கலைஞர்",
"artist_other": "கலைஞர்கள்",
"artistWithCount_one": "{{count}} கலைஞர்",
"artistWithCount_other": "{{count}} கலைஞர்கள்",
"favorite_one": "பிடித்த",
"favorite_other": "பிடித்தவை",
"folder_one": "கோப்புறை",
"folder_other": "கோப்புறைகள்",
"playlist_one": "பிளேலிச்ட்",
"playlist_other": "பிளேலிச்ட்கள்",
"play_one": "{{count}} play",
"play_other": "{{count}} நாடகங்கள்",
"playlistWithCount_one": "{{count}} பிளேலிச்ட்",
"playlistWithCount_other": "{{count}} பிளேலிச்ட்கள்",
"smartPlaylist": "அறிவுள்ள $ t (entity.playlist_one)",
"track_one": "மின்தடம்",
"track_other": "தடங்கள்",
"song_one": "பாடல்",
"song_other": "பாடல்கள்",
"trackWithCount_one": "{{count}} டிராக்",
"trackWithCount_other": "{{count}} தடங்கள்"
},
"error": {
"mpvRequired": "MPV தேவை",
"remotePortError": "தொலை சேவையக துறைமுகத்தை அமைக்க முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"remotePortWarning": "புதிய துறைமுகத்தைப் பயன்படுத்த சேவையகத்தை மறுதொடக்கம் செய்யுங்கள்",
"serverNotSelectedError": "சேவையகம் எதுவும் தேர்ந்தெடுக்கப்படவில்லை",
"serverRequired": "சேவையகம் தேவை",
"remoteEnableError": "தொலைநிலை சேவையகத்தை $ t (பொதுவானது) முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"apiRouteError": "பாதை கோரிக்கை செய்ய முடியவில்லை",
"audioDeviceFetchError": "ஆடியோ சாதனங்களைப் பெற முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"authenticationFailed": "ஏற்பு தோல்வியடைந்தது",
"badAlbum": "இந்த பாடல் ஆல்பத்தின் பகுதியாக இல்லாததால் இந்தப் பக்கத்தைப் பார்க்கிறீர்கள். உங்கள் இசை கோப்புறையின் மேல் மட்டத்தில் ஒரு பாடல் இருந்தால் இந்த சிக்கலைப் பார்க்கிறீர்கள். செல்லிஃபின் ஒரு கோப்புறையில் இருந்தால் தடங்களை மட்டுமே குழுக்கள்.",
"credentialsRequired": "நற்சான்றிதழ்கள் தேவை",
"endpointNotImplementedError": "Endpoint {{endpoint}} {{serverType} க்கு க்கு செயல்படுத்தப்படவில்லை",
"genericError": "பிழை ஏற்பட்டது",
"invalidServer": "தவறான சேவையகம்",
"localFontAccessDenied": "உள்ளக எழுத்துருக்களுக்கு மறுக்கப்பட்டது",
"loginRateError": "பல உள்நுழைவு முயற்சிகள், தயவுசெய்து சில நொடிகளில் மீண்டும் முயற்சிக்கவும்",
"networkError": "பிணைய பிழை ஏற்பட்டது",
"openError": "கோப்பைத் திறக்க முடியவில்லை",
"playbackError": "ஊடகங்களை விளையாட முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"remoteDisableError": "தொலைநிலை சேவையகத்தை $ t (பொதுவானது. குறைக்கக்கூடிய) முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"sessionExpiredError": "உங்கள் அமர்வு காலாவதியானது",
"systemFontError": "கணினி எழுத்துருக்களைப் பெற முயற்சிக்கும்போது பிழை ஏற்பட்டது"
},
"filter": {
"albumArtist": "$ t (entity.albumartist_one)",
"albumCount": "$ t (entity.album_other) எண்ணிக்கை",
"artist": "$ t (entity.artist_one)",
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"channels": "$ t (common.channel_other)",
"comment": "கருத்து",
"communityRating": "சமூக மதிப்பீடு",
"path": "பாதை",
"playCount": "விளையாட்டு எண்ணிக்கை",
"random": "சீரற்ற",
"rating": "செயல்வரம்பு",
"album": "$ t (entity.album_one)",
"criticRating": "விமர்சகர் மதிப்பீடு",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"disc": "வட்டு",
"duration": "காலம்",
"favorited": "பிடித்தது",
"fromYear": "ஆண்டு முதல்",
"genre": "$ t (entity.genre_one)",
"id": "ஐடி",
"isCompilation": "தொகுப்பு",
"isFavorited": "பிடித்தது",
"isPublic": "பொது",
"isRated": "மதிப்பிடப்படுகிறது",
"isRecentlyPlayed": "அண்மைக் காலத்தில் விளையாடியது",
"lastPlayed": "கடைசியாக விளையாடியது",
"mostPlayed": "அதிகம் விளையாடியது",
"name": "பெயர்",
"note": "குறிப்பு",
"owner": "$ t (பொதுவானவர்)",
"recentlyAdded": "அண்மைக் காலத்தில் சேர்க்கப்பட்டது",
"recentlyPlayed": "அண்மைக் காலத்தில் விளையாடியது",
"recentlyUpdated": "அண்மைக் காலத்தில் புதுப்பிக்கப்பட்டது",
"releaseDate": "வெளியீட்டு தேதி",
"releaseYear": "வெளியீட்டு ஆண்டு",
"search": "தேடல்",
"songCount": "பாடல் எண்ணிக்கை",
"title": "தலைப்பு",
"toYear": "ஆண்டு",
"trackNumber": "மின்தடம்"
},
"form": {
"addServer": {
"input_password": "கடவுச்சொல்",
"error_savePassword": "கடவுச்சொல்லை சேமிக்க முயற்சிக்கும்போது பிழை ஏற்பட்டது",
"ignoreCors": "CORS ஐ புறக்கணிக்கவும் ($ t (Common.RestartRequired))",
"ignoreSsl": "SSL ஐ புறக்கணிக்கவும் ($ t (பொதுவானது.",
"input_legacyAuthentication": "மரபு அங்கீகாரத்தை இயக்கவும்",
"input_name": "சேவையக பெயர்",
"input_savePassword": "கடவுச்சொல்லைச் சேமிக்கவும்",
"input_url": "முகவரி",
"input_username": "பயனர்பெயர்",
"success": "சேவையகம் வெற்றிகரமாக சேர்க்கப்பட்டது",
"title": "சேவையகத்தைச் சேர்க்கவும்"
},
"deletePlaylist": {
"input_confirm": "உறுதிப்படுத்த $ t (entity.playlist_one) பெயரைத் தட்டச்சு செய்க",
"success": "$ t (entity.playlist_one) வெற்றிகரமாக நீக்கப்பட்டது",
"title": "$ t (entity.playlist_one) ஐ நீக்கு"
},
"editPlaylist": {
"title": "திருத்து $ t (entity.playlist_one)",
"publicJellyfinNote": "சில காரணங்களால் செல்லிஃபின் ஒரு பிளேலிச்ட் பொதுவில் இல்லையா என்பதை அம்பலப்படுத்தவில்லை. இது பொதுவில் இருக்க விரும்பினால், தயவுசெய்து பின்வரும் உள்ளீட்டைத் தேர்ந்தெடுக்கவும்",
"success": "$ t (entity.playlist_one) வெற்றிகரமாக புதுப்பிக்கப்பட்டது"
},
"lyricSearch": {
"input_artist": "$ t (entity.artist_one)",
"input_name": "$ t (common.name)",
"title": "பாடல் தேடல்"
},
"queryEditor": {
"input_optionMatchAll": "அனைத்தையும் பொருத்துங்கள்",
"input_optionMatchAny": "எந்த பொருத்தவும்"
},
"shareItem": {
"description": "விவரம்",
"setExpiration": "காலாவதியை அமைக்கவும்",
"expireInvalid": "காலாவதி எதிர்காலத்தில் இருக்க வேண்டும்",
"allowDownloading": "பதிவிறக்க அனுமதிக்கவும்",
"success": "இடைநிலைப்பலகைக்கு நகலெடுக்கப்பட்ட இணைப்பைப் பகிரவும் (அல்லது திறக்க இங்கே சொடுக்கு செய்க)",
"createFailed": "பங்கை உருவாக்கத் தவறிவிட்டது (பகிர்வு இயக்கப்பட்டதா?)"
},
"createPlaylist": {
"success": "$ t (entity.playlist_one) வெற்றிகரமாக உருவாக்கப்பட்டது",
"title": "$ t (entity.playlist_one) ஐ உருவாக்கவும்",
"input_description": "$ t (common.description)",
"input_name": "$ t (common.name)",
"input_owner": "$ t (பொதுவானவர்)",
"input_public": "பொது"
},
"addToPlaylist": {
"input_playlists": "$ t (entity.playlist_other)",
"input_skipDuplicates": "நகல்களைத் தவிர்க்கவும்",
"success": "$ t (entity.trackwithCount, {\"count\": {{message}}}) $ t (entity.playlistwithCount, {\"count\": {{numOfPlaylists}}})",
"title": "$ t இல் சேர்க்கவும் (entity.playlist_one)"
},
"updateServer": {
"success": "சேவையகம் வெற்றிகரமாக புதுப்பிக்கப்பட்டது",
"title": "புதுப்பிப்பு சேவையகம்"
}
},
"page": {
"albumArtistDetail": {
"about": "{{artist}} பற்றி",
"appearsOn": "தோன்றும்",
"recentReleases": "அண்மைக் கால வெளியீடுகள்",
"viewDiscography": "டிச்கோகிராஃபி காண்க",
"topSongs": "சிறந்த பாடல்கள்",
"viewAllTracks": "அனைத்தையும் காண்க (entity.track_other)",
"relatedArtists": "தொடர்புடைய $ t (entity.artist_other)",
"topSongsFrom": "{{title}} இலிருந்து சிறந்த பாடல்கள்",
"viewAll": "அனைத்தையும் காண்க"
},
"appMenu": {
"goBack": "திரும்பிச் செல்லுங்கள்",
"collapseSidebar": "பக்கப்பட்டி சரிவு",
"expandSidebar": "பக்கப்பட்டியை விரிவாக்குங்கள்",
"goForward": "முன்னோக்கிச் செல்லுங்கள்",
"manageServers": "சேவையகங்களை நிர்வகிக்கவும்",
"openBrowserDevtools": "திறந்த உலாவி தேவ்டூல்கள்",
"quit": "$ t (common.quit)",
"selectServer": "சேவையகத்தைத் தேர்ந்தெடுக்கவும்",
"settings": "$ t (common.setting_other)",
"version": "பதிப்பு {{version}}"
},
"manageServers": {
"url": "முகவரி",
"title": "சேவையகங்களை நிர்வகிக்கவும்",
"serverDetails": "சேவையக விவரங்கள்",
"username": "பயனர்பெயர்",
"editServerDetailsTooltip": "சேவையக விவரங்களைத் திருத்தவும்",
"removeServer": "சேவையகத்தை அகற்று"
},
"contextMenu": {
"addNext": "$ t (player.addnext)",
"deletePlaylist": "$ t (action.deleteplaylist)",
"deselectAll": "$ t (action.deselectall)",
"download": "பதிவிறக்கம்",
"moveToBottom": "$ t (action.movetobottom)",
"moveToTop": "$ t (action.movetotop)",
"numberSelected": "{{count}} தேர்ந்தெடுக்கப்பட்டது",
"playSimilarSongs": "$ t (player.playsimilarsongs)",
"removeFromFavorites": "$ t (action.removefromfoavites)",
"removeFromPlaylist": "$ t (action.RemoveFrollayList)",
"removeFromQueue": "$ t (action.RemoveFromQueue)",
"setRating": "$ t (action.setrating)",
"playShuffled": "$ t (player.shuffle)",
"addFavorite": "$ t (action.addtofoaverites)",
"addLast": "$ t (player.addlast)",
"moveToNext": "$ t (action.movetonext)",
"play": "$ t (player.play)",
"shareItem": "உருப்படியைப் பகிரவும்",
"showDetails": "தகவலைப் பெறுங்கள்",
"addToFavorites": "$ t (action.addtofoaverites)",
"addToPlaylist": "$ t (action.addtoplailist)",
"createPlaylist": "$ t (action.createplaylist)"
},
"fullscreenPlayer": {
"config": {
"followCurrentLyric": "தற்போதைய பாடலைப் பின்பற்றுங்கள்",
"lyricAlignment": "பாடல் சீரமைப்பு",
"lyricOffset": "பாடல் ஆஃப்செட் (எம்.எச்)",
"synchronized": "ஒத்திசைக்கப்பட்டது",
"dynamicBackground": "மாறும் பின்னணி",
"dynamicImageBlur": "பட மங்கலான அளவு",
"dynamicIsImage": "பின்னணி படத்தை இயக்கவும்",
"lyricGap": "பாடல் இடைவெளி",
"lyricSize": "பாடல் அளவு",
"opacity": "ஒளிபுகாநிலை",
"showLyricMatch": "பாடல் போட்டியைக் காட்டு",
"showLyricProvider": "பாடல் வழங்குநரைக் காட்டு",
"unsynchronized": "ஒத்திசைக்கப்படாதது",
"useImageAspectRatio": "பட விகித விகிதத்தைப் பயன்படுத்தவும்"
},
"upNext": "அடுத்து",
"visualizer": "காட்சிப்படுத்தல்",
"noLyrics": "பாடல் வரிகள் இல்லை",
"lyrics": "பாடல்",
"related": "தொடர்புடைய"
},
"genreList": {
"showAlbums": "$ t (entity.genre_one) $ t (entity.album_other)",
"showTracks": "$ t (entity.genre_one) $ t (entity.track_other)",
"title": "$ t (entity.genre_other)"
},
"globalSearch": {
"commands": {
"goToPage": "பக்கத்திற்குச் செல்லுங்கள்",
"searchFor": "{{query}} ஐத் தேடுங்கள்",
"serverCommands": "சேவையக கட்டளைகள்"
},
"title": "கட்டளைகள்"
},
"home": {
"explore": "உங்கள் நூலகத்திலிருந்து ஆராயுங்கள்",
"mostPlayed": "அதிகம் விளையாடியது",
"newlyAdded": "புதிதாக சேர்க்கப்பட்ட வெளியீடுகள்",
"recentlyPlayed": "அண்மைக் காலத்தில் விளையாடியது",
"title": "$ t (காமன்.ஓம்)"
},
"itemDetail": {
"copyPath": "இடைநிலைப்பலகைக்கு பாதையை நகலெடுக்கவும்",
"copiedPath": "பாதை வெற்றிகரமாக நகலெடுக்கப்பட்டது",
"openFile": "கோப்பு மேலாளரில் தடத்தைக் காட்டு"
},
"playlist": {
"reorder": "ஐடியால் வரிசைப்படுத்தும்போது மட்டுமே மறுசீரமைப்பு இயக்கப்பட்டது"
},
"playlistList": {
"title": "$ t (entity.playlist_other)"
},
"setting": {
"advanced": "மேம்பட்ட",
"generalTab": "பொது",
"hotkeysTab": "ஆட்கீச்",
"playbackTab": "பின்னணி",
"windowTab": "சாளரம்"
},
"sidebar": {
"folders": "$ t (entity.folder_other)",
"genres": "$ t (entity.genre_other)",
"home": "$ t (காமன்.ஓம்)",
"nowPlaying": "இப்போது விளையாடுகிறது",
"playlists": "$ t (entity.playlist_other)",
"search": "$ t (பொதுவானது. தேடல்)",
"settings": "$ t (common.setting_other)",
"albumArtists": "$ t (entity.albumartist_other)",
"albums": "$ t (entity.album_other)",
"artists": "$ t (entity.artist_other)",
"shared": "பகிரப்பட்ட $ t (entity.playlist_other)",
"tracks": "$ t (entity.track_other)"
},
"trackList": {
"title": "$ t (entity.track_other)",
"genreTracks": "\"{{genre}}\" $ t (entity.track_other)",
"artistTracks": "{{artist}}"
},
"albumArtistList": {
"title": "$ t (entity.albumartist_other)"
},
"albumDetail": {
"moreFromArtist": "இந்த $ t (entity.artist_one) இலிருந்து மேலும்",
"moreFromGeneric": "{{item} இருந்து இலிருந்து மேலும்",
"released": "வெளியிடப்பட்டது"
},
"albumList": {
"artistAlbums": "ஆல்பங்கள் {{artist}}",
"genreAlbums": "\"{{genre}}\" $ t (entity.album_other)",
"title": "$ t (entity.album_other)"
}
},
"player": {
"addLast": "கடைசியாக சேர்க்கவும்",
"addNext": "அடுத்து சேர்க்கவும்",
"favorite": "பிடித்த",
"mute": "முடக்கு",
"muted": "முடக்கிய",
"next": "அடுத்தது",
"play": "விளையாடுங்கள்",
"playSimilarSongs": "ஒத்த பாடல்களை வாசிக்கவும்",
"previous": "முந்தைய",
"queue_clear": "தெளிவான வரிசை",
"queue_remove": "தேர்ந்தெடுக்கப்பட்டதை அகற்று",
"repeat": "மீண்டும்",
"repeat_all": "அனைத்தையும் மீண்டும் செய்யவும்",
"repeat_off": "முடக்கப்பட்டதை மீண்டும் செய்யவும்",
"shuffle": "விளையாட்டு மாற்றப்பட்டது",
"shuffle_off": "கலக்கு முடக்கப்பட்டது",
"skip": "தவிர்",
"playbackFetchCancel": "இது சிறிது நேரம் ஆகும்… ரத்து செய்ய அறிவிப்பை மூடு",
"playbackFetchInProgress": "பாடல்களை ஏற்றுகிறது…",
"playbackFetchNoResults": "பாடல்கள் எதுவும் கிடைக்கவில்லை",
"playbackSpeed": "பிளேபேக் விரைவு",
"playRandom": "சீரற்ற முறையில் விளையாடுங்கள்",
"queue_moveToBottom": "மேலே தேர்ந்தெடுக்கப்பட்ட நகர்த்து",
"queue_moveToTop": "தேர்ந்தெடுக்கப்பட்டதை கீழே நகர்த்தவும்",
"skip_back": "பின்னோக்கி தவிர்க்கவும்",
"skip_forward": "முன்னோக்கி தவிர்க்கவும்",
"stop": "நிறுத்து",
"toggleFullscreenPlayer": "முழுத்திரை பிளேயரை மாற்றவும்",
"unfavorite": "மாறாத",
"pause": "இடைநிறுத்தம்",
"viewQueue": "வரிசையைக் காண்க"
},
"setting": {
"accentColor": "உச்சரிப்பு நிறம்",
"accentColor_description": "பயன்பாட்டிற்கான உச்சரிப்பு வண்ணத்தை அமைக்கிறது",
"albumBackground": "ஆல்பம் பின்னணி படம்",
"applicationHotkeys": "பயன்பாட்டு ஆட்கீச்",
"applicationHotkeys_description": "பயன்பாட்டு ஆட்கீசை உள்ளமைக்கவும். உலகளாவிய ஆட்ச்கியாக அமைக்க தேர்வுப்பெட்டியை மாற்றவும் (டெச்க்டாப் மட்டும்)",
"artistConfiguration": "ஆல்பம் கலைஞர் பக்க உள்ளமைவு",
"audioDevice_description": "பிளேபேக்கிற்கு பயன்படுத்த ஆடியோ சாதனத்தைத் தேர்ந்தெடுக்கவும் (வெப் பிளேயர் மட்டும்)",
"audioExclusiveMode": "ஆடியோ பிரத்தியேக பயன்முறை",
"audioPlayer": "ஆடியோ பிளேயர்",
"audioPlayer_description": "பிளேபேக்கிற்கு பயன்படுத்த ஆடியோ பிளேயரைத் தேர்ந்தெடுக்கவும்",
"customCssEnable_description": "தனிப்பயன் சிஎச்எச் ஐ எழுத அனுமதிக்கவும்.",
"customCss": "தனிப்பயன் சிஎச்எச்",
"customFontPath": "தனிப்பயன் எழுத்துரு பாதை",
"customFontPath_description": "பயன்பாட்டிற்கு பயன்படுத்த தனிப்பயன் எழுத்துருவுக்கு பாதையை அமைக்கிறது",
"disableLibraryUpdateOnStartup": "தொடக்கத்தில் புதிய பதிப்புகளைச் சரிபார்ப்பதை முடக்கு",
"discordApplicationId": "{{discord}} பயன்பாட்டு ஐடி",
"discordListening": "கேட்பது என நிலையைக் காட்டுங்கள்",
"exitToTray_description": "கணினி தட்டில் பயன்பாட்டிலிருந்து வெளியேறவும்",
"floatingQueueArea": "மிதக்கும் வரிசை ஓவர் பகுதியைக் காட்டு",
"floatingQueueArea_description": "நாடக வரிசையைக் காண திரையின் வலது பக்கத்தில் ஒரு ஓவர் ஐகானைக் காண்பி",
"followLyric": "தற்போதைய பாடலைப் பின்பற்றுங்கள்",
"followLyric_description": "தற்போதைய விளையாட்டு நிலைக்கு பாடலை உருட்டவும்",
"font": "எழுத்துரு",
"font_description": "பயன்பாட்டிற்கு பயன்படுத்த எழுத்துருவை அமைக்கிறது",
"fontType": "எழுத்துரு வகை",
"fontType_description": "உள்ளமைக்கப்பட்ட எழுத்துரு ஃபீசின் வழங்கிய எழுத்துருக்களில் ஒன்றைத் தேர்ந்தெடுக்கிறது. உங்கள் இயக்க முறைமை வழங்கிய எந்த எழுத்துருவையும் தேர்ந்தெடுக்க கணினி எழுத்துரு உங்களை அனுமதிக்கிறது. உங்கள் சொந்த எழுத்துருவை வழங்க தனிப்பயன் உங்களை அனுமதிக்கிறது",
"fontType_optionBuiltIn": "உள்ளமைக்கப்பட்ட எழுத்துரு",
"fontType_optionCustom": "தனிப்பயன் எழுத்துரு",
"fontType_optionSystem": "கணினி எழுத்துரு",
"gaplessAudio": "இடைவெளி இல்லாத ஆடியோ",
"gaplessAudio_description": "MPV க்கான இடைவெளி இல்லாத ஆடியோ அமைப்பை அமைக்கிறது",
"gaplessAudio_optionWeak": "பலவீனமான (பரிந்துரைக்கப்படுகிறது)",
"genreBehavior": "வகை பக்கம் இயல்புநிலை நடத்தை",
"genreBehavior_description": "ஒரு வகையைக் சொடுக்கு செய்வது டிராக் அல்லது ஆல்பம் பட்டியலில் இயல்பாகத் திறக்கிறதா என்பதை தீர்மானிக்கிறது",
"globalMediaHotkeys_description": "பிளேபேக்கைக் கட்டுப்படுத்த உங்கள் கணினி மீடியா ஆட்கீசின் பயன்பாட்டை இயக்கவும் அல்லது முடக்கவும்",
"homeConfiguration": "முகப்பு பக்க உள்ளமைவு",
"homeFeature": "வீட்டில் கொணர்வி இடம்பெற்றது",
"hotkey_favoriteCurrentSong": "பிடித்த $ t (common.curressong)",
"hotkey_globalSearch": "உலக தேடல்",
"hotkey_playbackPrevious": "முந்தைய பாடல்",
"hotkey_playbackStop": "நிறுத்து",
"hotkey_rate0": "மதிப்பீடு தெளிவாக",
"hotkey_rate1": "மதிப்பீடு 1 விண்மீன்",
"hotkey_rate2": "மதிப்பீடு 2 நட்சத்திரங்கள்",
"hotkey_rate3": "மதிப்பீடு 3 நட்சத்திரங்கள்",
"hotkey_rate4": "மதிப்பீடு 4 நட்சத்திரங்கள்",
"hotkey_rate5": "மதிப்பீடு 5 நட்சத்திரங்கள்",
"hotkey_toggleFullScreenPlayer": "முழு திரை பிளேயரை மாற்றவும்",
"hotkey_togglePreviousSongFavorite": "மாற்றவும் (பொதுவானது. ப்ரீவியச்ங்) பிடித்தது",
"hotkey_toggleQueue": "வரிசையை மாற்றவும்",
"hotkey_toggleRepeat": "மீண்டும் மீண்டும்",
"hotkey_toggleShuffle": "கலக்கு மாற்று",
"hotkey_unfavoriteCurrentSong": "சாதகமற்ற $ t (common.curressong)",
"hotkey_unfavoritePreviousSong": "சாதகமற்ற $ t (காமன்.பிரெவியச்ங்)",
"hotkey_volumeDown": "தொகுதி கீழே",
"hotkey_volumeMute": "தொகுதி முடக்கு",
"hotkey_volumeUp": "தொகுதி",
"language": "மொழி",
"language_description": "பயன்பாட்டிற்கான மொழியை அமைக்கிறது ($ t (பொதுவானது.",
"lastfmApiKey": "{{lastfm}} பநிஇ key",
"lastfmApiKey_description": "{{lastfm} க்கு க்கான பநிஇ விசை. கவர் கலைக்கு தேவை",
"lyricFetch": "இணையத்திலிருந்து வரிகளை பெறுங்கள்",
"lyricFetchProvider_description": "பாடல் பெற வழங்குநர்களைத் தேர்ந்தெடுக்கவும். வழங்குநர்களின் வரிசை அவர்கள் வினவப்படும் ஒழுங்கு",
"lyricOffset": "பாடல் ஆஃப்செட் (எம்.எச்)",
"minimizeToTray": "தட்டில் குறைக்கவும்",
"minimumScrobblePercentage": "குறைந்தபட்ச துணிச்சல் காலம் (சதவீதம்)",
"minimumScrobblePercentage_description": "பாடலின் குறைந்தபட்ச விழுக்காடு அதைத் துடைப்பதற்கு முன்பு இசைக்க வேண்டும்",
"minimumScrobbleSeconds": "குறைந்தபட்ச தோண்டல் (விநாடிகள்)",
"minimumScrobbleSeconds_description": "பாடலின் விநாடிகளில் குறைந்தபட்ச காலம் அது வேட்டையாடப்படுவதற்கு முன்பு இசைக்கப்பட வேண்டும்",
"mpvExecutablePath": "MPV இயங்கக்கூடிய பாதை",
"mpvExecutablePath_description": "MPV இயங்கக்கூடிய பாதையை அமைக்கிறது. காலியாக இருந்தால், இயல்புநிலை பாதை பயன்படுத்தப்படும்",
"mpvExtraParameters": "MPV அளவுருக்கள்",
"mpvExtraParameters_help": "ஒரு வரிக்கு ஒன்று",
"passwordStore": "கடவுச்சொற்கள்/ரகசிய கடை",
"passwordStore_description": "என்ன கடவுச்சொல்/ரகசிய கடை பயன்படுத்த வேண்டும். கடவுச்சொற்களை சேமிப்பதில் சிக்கல்கள் இருந்தால் இதை மாற்றவும்.",
"playbackStyle": "பிளேபேக் பாணி",
"playbackStyle_description": "ஆடியோ பிளேயருக்கு பயன்படுத்த பிளேபேக் பாணியைத் தேர்ந்தெடுக்கவும்",
"playbackStyle_optionCrossFade": "கிராச்ஃபேட்",
"playbackStyle_optionNormal": "சாதாரண",
"playButtonBehavior": "பொத்தான் நடத்தை விளையாடுங்கள்",
"playButtonBehavior_description": "வரிசையில் பாடல்களைச் சேர்க்கும்போது ப்ளே பொத்தானின் இயல்புநிலை நடத்தை அமைக்கிறது",
"playButtonBehavior_optionAddLast": "$ t (player.addlast)",
"playButtonBehavior_optionAddNext": "$ t (player.addnext)",
"playerAlbumArtResolution": "பிளேயர் ஆல்பம் கலைத் தீர்மானம்",
"playerAlbumArtResolution_description": "பெரிய வீரரின் ஆல்பம் கலை முன்னோட்டத்திற்கான தீர்மானம். பெரியது இது மிகவும் மிருதுவானதாக தோற்றமளிக்கிறது, ஆனால் மெதுவாக ஏற்றுவதை மெதுவாகக் கொண்டிருக்கலாம். இயல்புநிலை 0 க்கு, அதாவது ஆட்டோ",
"playerbarOpenDrawer": "பிளேயர்பார் முழுத்திரை மாற்று",
"playerbarOpenDrawer_description": "முழு திரை பிளேயரைத் திறக்க பிளேயர்பாரைக் சொடுக்கு செய்ய அனுமதிக்கிறது",
"remotePassword": "ரிமோட் கண்ட்ரோல் சர்வர் கடவுச்சொல்",
"remotePassword_description": "ரிமோட் கண்ட்ரோல் சேவையகத்திற்கான கடவுச்சொல்லை அமைக்கிறது. இந்த நற்சான்றிதழ்கள் இயல்பாகவே பாதுகாப்பற்ற முறையில் மாற்றப்படுகின்றன, எனவே நீங்கள் கவலைப்படாத தனிப்பட்ட கடவுச்சொல்லைப் பயன்படுத்த வேண்டும்",
"remotePort": "ரிமோட் கண்ட்ரோல் சர்வர் துறைமுகம்",
"remotePort_description": "ரிமோட் கண்ட்ரோல் சேவையகத்திற்கான துறைமுகத்தை அமைக்கிறது",
"remoteUsername": "ரிமோட் கண்ட்ரோல் சர்வர் பயனர்பெயர்",
"remoteUsername_description": "ரிமோட் கண்ட்ரோல் சேவையகத்திற்கான பயனர்பெயரை அமைக்கிறது. பயனர்பெயர் மற்றும் கடவுச்சொல் இரண்டும் காலியாக இருந்தால், ஏற்பு முடக்கப்படும்",
"replayGainClipping": "{{ReplayGain}} கிளிப்பிங்",
"replayGainClipping_description": "ஆதாயத்தை தானாகவே குறைப்பதன் மூலம் {{ReplayGain} by காரணமாக ஏற்படும் கிளிப்பிங்கைத் தடுக்கவும்",
"replayGainFallback": "{{{ReplayGain}}} falback",
"replayGainFallback_description": "கோப்பில் {{ReplayGain}} குறிச்சொற்கள் இல்லையென்றால் விண்ணப்பிக்க DB இல் ஆதாயம்",
"replayGainMode": "{{ReplayGain}} பயன்முறை",
"replayGainMode_description": "{{ReplayGain}}} மதிப்புகளின் படி தொகுதி ஆதாயத்தை சரிசெய்யவும் மேனிலை தரவு கோப்பு",
"replayGainMode_optionAlbum": "$ t (entity.album_one)",
"replayGainMode_optionNone": "$ t (common.none)",
"replayGainMode_optionTrack": "$ t (entity.track_one)",
"replayGainPreamp": "{{ReplayGain}} preamp (db)",
"replayGainPreamp_description": "{{ReplayGain}}} மதிப்புகளுக்கு பயன்படுத்தப்படும் Preamp ஆதாயத்தை சரிசெய்யவும்",
"sampleRate": "மாதிரி வீதம்",
"sampleRate_description": "தேர்ந்தெடுக்கப்பட்ட மாதிரி அதிர்வெண் தற்போதைய மீடியாவிலிருந்து வேறுபட்டால் பயன்படுத்த வேண்டிய வெளியீட்டு மாதிரி வீதத்தைத் தேர்ந்தெடுக்கவும். 8000 க்கும் குறைவான மதிப்பு இயல்புநிலை அதிர்வெண்ணைப் பயன்படுத்தும்",
"themeLight_description": "பயன்பாட்டிற்கு பயன்படுத்த ஒளி கருப்பொருள் அமைக்கிறது",
"transcodeNote": "1 (வலை) - 2 (MPV) பாடல்களுக்குப் பிறகு நடைமுறைக்கு வருகிறது",
"transcode": "டிரான்ச்கோடிங்கை இயக்கவும்",
"transcode_description": "வெவ்வேறு வடிவங்களுக்கு மாற்றுவதை செயல்படுத்துகிறது",
"transcodeBitrate": "டிரான்ச்கோடிற்கு பிட்ரேட்",
"transcodeBitrate_description": "டிரான்ச்கோடிற்கு பிட்ரேட்டைத் தேர்ந்தெடுக்கிறது. 0 என்றால் சேவையகம் எடுக்கட்டும்",
"transcodeFormat": "டிரான்ச்கோடுக்கு வடிவம்",
"transcodeFormat_description": "டிரான்ச்கோடிற்கு வடிவமைப்பைத் தேர்ந்தெடுக்கிறது. சேவையகம் தீர்மானிக்க காலியாக விடவும்",
"translationApiProvider": "மொழிபெயர்ப்பு பநிஇ வழங்குநர்",
"translationApiProvider_description": "மொழிபெயர்ப்புக்கான பநிஇ வழங்குநர்",
"translationApiKey": "மொழிபெயர்ப்பு பநிஇ விசை",
"translationApiKey_description": "மொழிபெயர்ப்பிற்கான பநிஇ விசை (உலகளாவிய பணி இறுதிப்புள்ளியை மட்டும் ஆதரிக்கவும்)",
"translationTargetLanguage": "மொழிபெயர்ப்பு இலக்கு மொழி",
"translationTargetLanguage_description": "மொழிபெயர்ப்பிற்கான இலக்கு மொழி",
"trayEnabled": "தட்டில் காட்டு",
"trayEnabled_description": "தட்டு ஐகான்/மெனுவைக் காட்டவும்/மறைக்கவும். முடக்கப்பட்டால், தட்டில் குறைக்க/வெளியேறவும் முடக்குகிறது",
"volumeWidth_description": "தொகுதி ச்லைடரின் அகலம்",
"webAudio": "வலை ஆடியோவைப் பயன்படுத்தவும்",
"webAudio_description": "வலை ஆடியோவைப் பயன்படுத்தவும். இது ரீப்ளே கெய்ன் போன்ற மேம்பட்ட அம்சங்களை செயல்படுத்துகிறது. நீங்கள் வேறுவிதமாக அனுபவித்தால் முடக்கு",
"artistConfiguration_description": "எந்த உருப்படிகள் காண்பிக்கப்படுகின்றன, எந்த வரிசையில், ஆல்பம் கலைஞர் பக்கத்தில் உள்ளமைக்கவும்",
"audioDevice": "ஆடியோ சாதனம்",
"audioExclusiveMode_description": "பிரத்யேக வெளியீட்டு பயன்முறையை இயக்கவும். இந்த பயன்முறையில், கணினி வழக்கமாக பூட்டப்படுகிறது, மேலும் MPV மட்டுமே ஆடியோவை வெளியிட முடியும்",
"buttonSize": "பிளேயர் பார் பொத்தான் அளவு",
"buttonSize_description": "பிளேயர் பார் பொத்தான்களின் அளவு",
"clearCache": "தெளிவான உலாவி தற்காலிக சேமிப்பு",
"clearCache_description": "ஃபீசினின் ஒரு 'கடினமான தெளிவான'. ஃபெசினின் தற்காலிக சேமிப்பை அழிப்பதைத் தவிர, உலாவி தற்காலிக சேமிப்பை (சேமித்த படங்கள் மற்றும் பிற சொத்துக்கள்) வெறுமை செய்யுங்கள். சேவையக நற்சான்றிதழ்கள் மற்றும் அமைப்புகள் பாதுகாக்கப்படுகின்றன",
"albumBackground_description": "ஆல்பம் கலை கொண்ட ஆல்பம் பக்கங்களுக்கு பின்னணி படத்தை சேர்க்கிறது",
"albumBackgroundBlur": "ஆல்பம் பின்னணி பட மங்கலான அளவு",
"albumBackgroundBlur_description": "ஆல்பத்தின் பின்னணி படத்திற்கு பயன்படுத்தப்படும் மங்கலின் அளவை சரிசெய்கிறது",
"clearQueryCache": "தெளிவான ஃபைசின் கேச்",
"clearQueryCache_description": "ஃபீசினின் 'மென்மையான தெளிவான'. இது பிளேலிச்ட்களைப் புதுப்பிக்கும், மெட்டாடேட்டாவைக் கண்காணிக்கும் மற்றும் சேமித்த பாடல் வரிகளை மீட்டமைக்கும். அமைப்புகள், சேவையக நற்சான்றிதழ்கள் மற்றும் தற்காலிக சேமிப்பு படங்கள் பாதுகாக்கப்படுகின்றன",
"clearCacheSuccess": "கேச் வெற்றிகரமாக அழிக்கப்பட்டது",
"contextMenu": "சூழல் பட்டியல் (வலது கிளிக்) உள்ளமைவு",
"crossfadeDuration": "கிராச்ஃபேட் காலம்",
"crossfadeDuration_description": "கிராச்ஃபேட் விளைவின் காலத்தை அமைக்கிறது",
"crossfadeStyle": "கிராச்ஃபேட் பாணி",
"crossfadeStyle_description": "ஆடியோ பிளேயருக்கு பயன்படுத்த கிராச்ஃபேட் பாணியைத் தேர்ந்தெடுக்கவும்",
"customCssEnable": "தனிப்பயன் சிஎச்எச் ஐ இயக்கவும்",
"customCssNotice": "எச்சரிக்கை: சில சுத்திகரிப்பு (URL () மற்றும் உள்ளடக்கத்தை அனுமதிக்காதது :) இருக்கும்போது, தனிப்பயன் சிஎச்எச் ஐப் பயன்படுத்துவது இடைமுகத்தை மாற்றுவதன் மூலம் ஆபத்துக்களை ஏற்படுத்தக்கூடும்.",
"contextMenu_description": "நீங்கள் ஒரு உருப்படியை வலது சொடுக்கு செய்யும் போது பட்டியலில் காட்டப்பட்டுள்ள உருப்படிகளை மறைக்க உங்களை அனுமதிக்கிறது. சரிபார்க்கப்படாத உருப்படிகள் மறைக்கப்படும்",
"disableAutomaticUpdates": "தானியங்கி புதுப்பிப்புகளை முடக்கு",
"discordApplicationId_description": "{{discord}} பணக்கார இருப்புக்கான பயன்பாட்டு ஐடி (இயல்புநிலை {{defaultId}})",
"discordIdleStatus": "பணக்கார இருப்பு செயலற்ற நிலையைக் காட்டுங்கள்",
"discordIdleStatus_description": "இயக்கப்பட்டால், பிளேயர் சும்மா இருக்கும்போது நிலையைப் புதுப்பிக்கவும்",
"discordListening_description": "விளையாடுவதற்குப் பதிலாக கேட்பது என்று அந்த நிலையைக் காட்டுங்கள்",
"discordRichPresence": "{{discord}} பணக்கார இருப்பு",
"discordRichPresence_description": "{{discord}} பணக்கார இருப்பில் பின்னணி நிலையை இயக்கவும். பட விசைகள்: {{icon}}, {{playing}}, மற்றும் {{paused}} ",
"customCss_description": "தனிப்பயன் சிஎச்எச் உள்ளடக்கம். குறிப்பு: உள்ளடக்கம் மற்றும் தொலைநிலை முகவரி கள் அனுமதிக்கப்படாத பண்புகள். உங்கள் உள்ளடக்கத்தின் முன்னோட்டம் கீழே காட்டப்பட்டுள்ளது. நீங்கள் அமைக்காத கூடுதல் புலங்கள் சுத்திகரிப்பு காரணமாக உள்ளன.",
"doubleClickBehavior": "இரட்டை சொடுக்கு செய்யும் போது தேடப்பட்ட அனைத்து தடங்களையும் வரிசைப்படுத்தவும்",
"doubleClickBehavior_description": "உண்மை என்றால், தட தேடலில் பொருந்தக்கூடிய அனைத்து தடங்களும் வரிசையில் நிற்கப்படும். இல்லையெனில், சொடுக்கு செய்யப்பட்ட ஒன்று மட்டுமே வரிசையில் நிற்கப்படும்",
"enableRemote": "ரிமோட் கண்ட்ரோல் சேவையகத்தை இயக்கவும்",
"enableRemote_description": "பயன்பாட்டைக் கட்டுப்படுத்த மற்ற சாதனங்களை அனுமதிக்க ரிமோட் கண்ட்ரோல் சேவையகத்தை இயக்குகிறது",
"externalLinks": "வெளிப்புற இணைப்புகளைக் காட்டு",
"externalLinks_description": "கலைஞர்/ஆல்பம் பக்கங்களில் வெளிப்புற இணைப்புகளை (last.fm, மியூசிக் ப்ரெய்ன்ச்) காண்பிக்க உதவுகிறது",
"exitToTray": "தட்டில் வெளியேறவும்",
"globalMediaHotkeys": "உலகளாவிய மீடியா ஆட்கீச்",
"discordUpdateInterval": "{{discord}} பணக்கார இருப்பு புதுப்பிப்பு இடைவெளி",
"discordUpdateInterval_description": "ஒவ்வொரு புதுப்பிப்புக்கும் இடையிலான விநாடிகளில் நேரம் (குறைந்தபட்சம் 15 வினாடிகள்)",
"homeConfiguration_description": "என்னென்ன உருப்படிகள் காட்டப்படுகின்றன, எந்த வரிசையில், முகப்பு பக்கத்தில் உள்ளமைக்கவும்",
"homeFeature_description": "முகப்பு பக்கத்தில் பெரிய பிரத்யேக கொணர்வி காட்ட வேண்டுமா என்பதைக் கட்டுப்படுத்துகிறது",
"hotkey_browserBack": "உலாவி மீண்டும்",
"hotkey_browserForward": "முன்னோக்கி உலாவி",
"hotkey_favoritePreviousSong": "பிடித்த $ t (காமன்.பிரெவியச்ங்)",
"hotkey_localSearch": "பக்க தேடல்",
"hotkey_playbackNext": "அடுத்த பாடல்",
"hotkey_playbackPause": "இடைநிறுத்தம்",
"hotkey_playbackPlay": "விளையாடுங்கள்",
"hotkey_playbackPlayPause": "விளையாடு / இடைநிறுத்தம்",
"hotkey_skipBackward": "பின்தங்கிய நிலையில் தவிர்க்கவும்",
"hotkey_zoomIn": "பெரிதாக்கு",
"hotkey_zoomOut": "சிறிதாக்கு",
"imageAspectRatio": "சொந்த கவர் கலை விகித விகிதத்தைப் பயன்படுத்தவும்",
"imageAspectRatio_description": "இயக்கப்பட்டால், கவர் கலை அவற்றின் சொந்த விகித விகிதத்தைப் பயன்படுத்தி காண்பிக்கப்படும். 1: 1 இல்லாத கலைக்கு, மீதமுள்ள இடம் காலியாக இருக்கும்",
"lyricFetch_description": "பல்வேறு இணைய மூலங்களிலிருந்து பாடல் வரிகள்",
"lyricFetchProvider": "பாடல் பெற வழங்குநர்கள்",
"lyricOffset_description": "குறிப்பிட்ட அளவு மில்லி விநாடிகளால் பாடலை ஈடுசெய்யவும்",
"hotkey_skipForward": "முன்னோக்கி செல்லுங்கள்",
"hotkey_toggleCurrentSongFavorite": "மாற்று $ t (common.curressong) பிடித்தது",
"minimizeToTray_description": "கணினி தட்டில் பயன்பாட்டைக் குறைக்கவும்",
"playButtonBehavior_optionPlay": "$ t (player.play)",
"playButtonBehavior_optionPlayShuffled": "$ t (player.shuffle)",
"savePlayQueue": "விளையாட்டு வரிசையை சேமிக்கவும்",
"savePlayQueue_description": "பயன்பாடு மூடப்படும் போது ப்ளே வரிசையை சேமித்து, பயன்பாடு திறக்கப்படும் போது அதை மீட்டெடுக்கவும்",
"scrobble": "ச்க்ரோபில்",
"scrobble_description": "உங்கள் மீடியா சேவையகத்திற்கு ச்க்ரோபில் விளையாடுகிறது",
"showSkipButton": "ச்கிப் பொத்தான்களைக் காட்டு",
"showSkipButton_description": "பிளேயர் பட்டியில் ச்கிப் பொத்தான்களைக் காட்டவும் அல்லது மறைக்கவும்",
"sidebarConfiguration": "பக்கப்பட்டி உள்ளமைவு",
"sidebarConfiguration_description": "பக்கப்பட்டியில் தோன்றும் உருப்படிகள் மற்றும் வரிசையைத் தேர்ந்தெடுக்கவும்",
"showSkipButtons": "ச்கிப் பொத்தான்களைக் காட்டு",
"showSkipButtons_description": "பிளேயர் பட்டியில் ச்கிப் பொத்தான்களைக் காட்டவும் அல்லது மறைக்கவும்",
"sidebarCollapsedNavigation": "பக்கப்பட்டி (சரிந்த) வழிசெலுத்தல்",
"sidebarCollapsedNavigation_description": "சரிந்த பக்கப்பட்டியில் வழிசெலுத்தலைக் காட்டவும் அல்லது மறைக்கவும்",
"sidebarPlaylistList": "பக்கப்பட்டி பிளேலிச்ட் பட்டியல்",
"sidebarPlaylistList_description": "பக்கப்பட்டியில் பிளேலிச்ட் பட்டியலைக் காட்டவும் அல்லது மறைக்கவும்",
"sidePlayQueueStyle": "சைட் பிளே வரிசை பாணி",
"sidePlayQueueStyle_description": "பக்க நாடக வரிசையின் பாணியை அமைக்கிறது",
"sidePlayQueueStyle_optionAttached": "இணைக்கப்பட்டுள்ளது",
"sidePlayQueueStyle_optionDetached": "பிரிக்கப்பட்டது",
"theme_description": "பயன்பாட்டிற்கு பயன்படுத்த கருப்பொருள் அமைக்கிறது",
"themeDark": "கருப்பொருள் (இருண்ட)",
"themeDark_description": "பயன்பாட்டிற்கு பயன்படுத்த இருண்ட கருப்பொருள் அமைக்கிறது",
"skipDuration": "கால அளவைத் தவிர்க்கவும்",
"skipDuration_description": "பிளேயர் பட்டியில் தவிர் பொத்தான்களைப் பயன்படுத்தும் போது தவிர்க்க வேண்டிய காலத்தை அமைக்கிறது",
"skipPlaylistPage": "பிளேலிச்ட் பக்கத்தைத் தவிர்க்கவும்",
"skipPlaylistPage_description": "பிளேலிச்ட்டுக்கு செல்லும்போது, இயல்புநிலை பக்கத்திற்கு பதிலாக பிளேலிச்ட் பாடல் பட்டியல் பக்கத்திற்குச் செல்லவும்",
"startMinimized": "குறைக்கத் தொடங்குங்கள்",
"startMinimized_description": "கணினி தட்டில் பயன்பாட்டைத் தொடங்கவும்",
"theme": "கருப்பொருள்",
"themeLight": "கருப்பொருள் (ஒளி)",
"volumeWheelStep": "தொகுதி சக்கர படி",
"volumeWheelStep_description": "தொகுதி ச்லைடரில் சுட்டி சக்கரத்தை ச்க்ரோலிங் செய்யும் போது மாற்ற வேண்டிய அளவின் அளவு",
"volumeWidth": "தொகுதி ச்லைடர் அகலம்",
"windowBarStyle": "சாளரம் பார் பாணி",
"windowBarStyle_description": "சாளர பட்டியின் பாணியைத் தேர்ந்தெடுக்கவும்",
"useSystemTheme": "கணினி கருப்பொருளைப் பயன்படுத்தவும்",
"useSystemTheme_description": "கணினி வரையறுக்கப்பட்ட ஒளி அல்லது இருண்ட விருப்பத்தைப் பின்பற்றவும்",
"zoom": "சூம் விழுக்காடு",
"zoom_description": "பயன்பாட்டிற்கான சூம் சதவீதத்தை அமைக்கிறது"
},
"table": {
"config": {
"label": {
"album": "$ t (entity.album_one)",
"artist": "$ t (entity.artist_one)",
"biography": "$ t (காமன். -புவியியல்)",
"bitrate": "$ t (common.bitrate)",
"bpm": "$ t (common.bpm)",
"channels": "$ t (common.channel_other)",
"codec": "$ t (common.codec)",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"rating": "$ t (பொதுவானது. ரேட்டிங்)",
"releaseDate": "வெளியீட்டு தேதி",
"rowIndex": "வரிசை அட்டவணை",
"size": "$ t (common.size)",
"trackNumber": "ட்ராக் எண்",
"year": "$ t (பொதுவானது.",
"lastPlayed": "கடைசியாக விளையாடியது",
"note": "$ t (பொதுவானது. குறிப்பு)",
"owner": "$ t (பொதுவானவர்)",
"actions": "$ t (common.action_other)",
"albumArtist": "$ t (entity.albumartist_one)",
"discNumber": "வட்டு எண்",
"duration": "$ t (பொதுவானது.",
"favorite": "$ t (common.foavorite)",
"genre": "$ t (entity.genre_one)",
"path": "$ t (common.path)",
"playCount": "விளையாட்டு எண்ணிக்கை",
"songCount": "$ t (entity.track_other)",
"title": "$ t (common.title)",
"titleCombined": "$ t (common.title) (ஒருங்கிணைந்த)"
},
"view": {
"card": "அட்டை",
"table": "அட்டவணை",
"poster": "சுவரொட்டி"
},
"general": {
"autoFitColumns": "ஆட்டோ பொருத்தம் நெடுவரிசைகள்",
"followCurrentSong": "தற்போதைய பாடலைப் பின்தொடரவும்",
"displayType": "காட்சி வகை",
"gap": "$ t (comman.gap)",
"itemGap": "உருப்படி இடைவெளி (பிஎக்ச்)",
"itemSize": "உருப்படி அளவு (பிஎக்ச்)",
"size": "$ t (common.size)",
"tableColumns": "அட்டவணை நெடுவரிசைகள்"
}
},
"column": {
"album": "ஆல்பம்",
"albumArtist": "ஆல்பம் கலைஞர்",
"albumCount": "$ t (entity.album_other)",
"artist": "$ t (entity.artist_one)",
"biography": "சுயசரிதை",
"bitrate": "பிட்ரேட்",
"bpm": "பிபிஎம்",
"channels": "$ t (common.channel_other)",
"codec": "$ t (common.codec)",
"comment": "கருத்து",
"dateAdded": "தேதி சேர்க்கப்பட்டது",
"discNumber": "வட்டு",
"favorite": "பிடித்த",
"genre": "$ t (entity.genre_one)",
"lastPlayed": "கடைசியாக விளையாடியது",
"path": "பாதை",
"playCount": "நாடகங்கள்",
"rating": "செயல்வரம்பு",
"releaseDate": "வெளியீட்டு தேதி",
"releaseYear": "ஆண்டு",
"size": "$ t (common.size)",
"songCount": "$ t (entity.track_other)",
"title": "தலைப்பு",
"trackNumber": "மின்தடம்"
}
}
}
+8 -3
View File
@@ -109,7 +109,9 @@
"codec": "编解码器",
"share": "分享",
"preview": "预览",
"translation": "翻译"
"translation": "翻译",
"additionalParticipants": "其他参与者",
"tags": "标签"
},
"entity": {
"albumArtist_other": "专辑艺术家",
@@ -389,7 +391,9 @@
"translationTargetLanguage": "目标翻译语言",
"translationTargetLanguage_description": "目标翻译语言",
"lastfmApiKey": "{{lastfm}} API 密钥",
"lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需"
"lastfmApiKey_description": "{{lastfm}} 的 API 密钥。封面艺术图所需",
"discordServeImage": "从服务器提供 {{discord}} 图像",
"discordServeImage_description": "分享 {{discord}} 封面艺术图,来自 rich presence 服务器,仅适用于 jellyfin 和 navidrome"
},
"error": {
"remotePortWarning": "重启服务器使新端口生效",
@@ -413,7 +417,8 @@
"loginRateError": "登录请求尝试次数过多,请稍后再试",
"badAlbum": "您看到此页面是因为这首歌不是专辑的一部分。如果您的音乐文件夹顶层有一首歌曲,您很可能会遇到此问题。jellyfin 仅对位于文件夹中的曲目进行分组。",
"networkError": "发生网络错误",
"openError": "无法打开文件"
"openError": "无法打开文件",
"badValue": "无效的选项 \"{{value}}\". 此值不再存在"
},
"filter": {
"mostPlayed": "最多播放过",
+82 -81
View File
@@ -1,12 +1,13 @@
import axios, { AxiosResponse } from 'axios';
import { load } from 'cheerio';
import { orderSearchResults } from './shared';
import {
LyricSource,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '../../../../renderer/api/types';
LyricSource,
} from '.';
import { orderSearchResults } from './shared';
const SEARCH_URL = 'https://genius.com/api/search/song';
@@ -17,20 +18,6 @@ export interface GeniusResponse {
response: Response;
}
export interface Meta {
status: number;
}
export interface Response {
next_page: number;
sections: Section[];
}
export interface Section {
hits: Hit[];
type: string;
}
export interface Hit {
highlights: any[];
index: string;
@@ -38,6 +25,35 @@ export interface Hit {
type: string;
}
export interface Meta {
status: number;
}
export interface PrimaryArtist {
_type: string;
api_path: string;
header_image_url: string;
id: number;
image_url: string;
index_character: string;
is_meme_verified: boolean;
is_verified: boolean;
name: string;
slug: string;
url: string;
}
export interface ReleaseDateComponents {
day: number;
month: number;
year: number;
}
export interface Response {
next_page: number;
sections: Section[];
}
export interface Result {
_type: string;
annotation_count: number;
@@ -69,24 +85,9 @@ export interface Result {
url: string;
}
export interface PrimaryArtist {
_type: string;
api_path: string;
header_image_url: string;
id: number;
image_url: string;
index_character: string;
is_meme_verified: boolean;
is_verified: boolean;
name: string;
slug: string;
url: string;
}
export interface ReleaseDateComponents {
day: number;
month: number;
year: number;
export interface Section {
hits: Hit[];
type: string;
}
export interface Stats {
@@ -94,6 +95,27 @@ export interface Stats {
unreviewed_annotations: number;
}
export async function getLyricsBySongId(url: string): Promise<null | string> {
let result: AxiosResponse<string, any>;
try {
result = await axios.get<string>(url, { responseType: 'text' });
} catch (e) {
console.error('Genius lyrics request got an error!', e);
return null;
}
const $ = load(result.data.split('<br/>').join('\n'));
const lyricsDiv = $('div.lyrics');
if (lyricsDiv.length > 0) return lyricsDiv.text().trim();
const lyricSections = $('div[class^=Lyrics__Container]')
.map((_, e) => $(e).text())
.toArray()
.join('\n');
return lyricSections;
}
export async function getSearchResults(
params: LyricSearchQuery,
): Promise<InternetProviderLyricSearchResponse[] | null> {
@@ -133,9 +155,33 @@ export async function getSearchResults(
return orderSearchResults({ params, results: songResults });
}
export async function query(
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
const response = await getSongId(params);
if (!response) {
console.error('Could not find the song on Genius!');
return null;
}
const lyrics = await getLyricsBySongId(response.id);
if (!lyrics) {
console.error('Could not get lyrics on Genius!');
return null;
}
return {
artist: response.artist,
id: response.id,
lyrics,
name: response.name,
source: LyricSource.GENIUS,
};
}
async function getSongId(
params: LyricSearchQuery,
): Promise<Omit<InternetProviderLyricResponse, 'lyrics'> | null> {
): Promise<null | Omit<InternetProviderLyricResponse, 'lyrics'>> {
let result: AxiosResponse<GeniusResponse>;
try {
result = await axios.get(SEARCH_URL, {
@@ -162,48 +208,3 @@ async function getSongId(
source: LyricSource.GENIUS,
};
}
export async function getLyricsBySongId(url: string): Promise<string | null> {
let result: AxiosResponse<string, any>;
try {
result = await axios.get<string>(url, { responseType: 'text' });
} catch (e) {
console.error('Genius lyrics request got an error!', e);
return null;
}
const $ = load(result.data.split('<br/>').join('\n'));
const lyricsDiv = $('div.lyrics');
if (lyricsDiv.length > 0) return lyricsDiv.text().trim();
const lyricSections = $('div[class^=Lyrics__Container]')
.map((_, e) => $(e).text())
.toArray()
.join('\n');
return lyricSections;
}
export async function query(
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
const response = await getSongId(params);
if (!response) {
console.error('Could not find the song on Genius!');
return null;
}
const lyrics = await getLyricsBySongId(response.id);
if (!lyrics) {
console.error('Could not get lyrics on Genius!');
return null;
}
return {
artist: response.artist,
id: response.id,
lyrics,
name: response.name,
source: LyricSource.GENIUS,
};
}
+68 -24
View File
@@ -1,36 +1,78 @@
import { ipcMain } from 'electron';
import { store } from '../settings';
import {
getLyricsBySongId as getGenius,
query as queryGenius,
getSearchResults as searchGenius,
getLyricsBySongId as getGenius,
} from './genius';
import {
getLyricsBySongId as getLrcLib,
query as queryLrclib,
getSearchResults as searchLrcLib,
getLyricsBySongId as getLrcLib,
} from './lrclib';
import {
getLyricsBySongId as getNetease,
query as queryNetease,
getSearchResults as searchNetease,
getLyricsBySongId as getNetease,
} from './netease';
import {
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
QueueSong,
LyricGetQuery,
LyricSource,
} from '../../../../renderer/api/types';
import { store } from '../settings/index';
type SongFetcher = (params: LyricSearchQuery) => Promise<InternetProviderLyricResponse | null>;
import { Song } from '/@/shared/types/domain-types';
export enum LyricSource {
GENIUS = 'Genius',
LRCLIB = 'lrclib.net',
NETEASE = 'NetEase',
}
export type FullLyricsMetadata = Omit<InternetProviderLyricResponse, 'id' | 'lyrics' | 'source'> & {
lyrics: LyricsResponse;
remote: boolean;
source: string;
};
export type InternetProviderLyricResponse = {
artist: string;
id: string;
lyrics: string;
name: string;
source: LyricSource;
};
export type InternetProviderLyricSearchResponse = {
artist: string;
id: string;
name: string;
score?: number;
source: LyricSource;
};
export type LyricGetQuery = {
remoteSongId: string;
remoteSource: LyricSource;
song: Song;
};
export type LyricOverride = Omit<InternetProviderLyricResponse, 'lyrics'>;
export type LyricSearchQuery = {
album?: string;
artist?: string;
duration?: number;
name?: string;
};
export type LyricsResponse = string | SynchronizedLyricsArray;
export type SynchronizedLyricsArray = Array<[number, string]>;
type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
type GetFetcher = (id: string) => Promise<null | string>;
type SearchFetcher = (
params: LyricSearchQuery,
) => Promise<InternetProviderLyricSearchResponse[] | null>;
type GetFetcher = (id: string) => Promise<string | null>;
type CachedLyrics = Record<LyricSource, InternetProviderLyricResponse>;
type SongFetcher = (params: LyricSearchQuery) => Promise<InternetProviderLyricResponse | null>;
const FETCHERS: Record<LyricSource, SongFetcher> = {
[LyricSource.GENIUS]: queryGenius,
@@ -54,10 +96,10 @@ const MAX_CACHED_ITEMS = 10;
const lyricCache = new Map<string, CachedLyrics>();
const getRemoteLyrics = async (song: QueueSong) => {
const getRemoteLyrics = async (song: Song) => {
const sources = store.get('lyrics', []) as LyricSource[];
const cached = lyricCache.get(song.id);
const cached = lyricCache.get(song.id.toString());
if (cached) {
for (const source of sources) {
@@ -66,16 +108,16 @@ const getRemoteLyrics = async (song: QueueSong) => {
}
}
let lyricsFromSource = null;
let lyricsFromSource: InternetProviderLyricResponse | null = null;
for (const source of sources) {
const params = {
album: song.album || song.name,
artist: song.artistName,
artist: song.artists[0].name,
duration: song.duration / 1000.0,
name: song.name,
};
const response = await FETCHERS[source](params);
const response = await FETCHERS[source](params as unknown as LyricSearchQuery);
if (response) {
const newResult = cached
@@ -87,10 +129,12 @@ const getRemoteLyrics = async (song: QueueSong) => {
if (lyricCache.size === MAX_CACHED_ITEMS && cached === undefined) {
const toRemove = lyricCache.keys().next().value;
lyricCache.delete(toRemove);
if (toRemove) {
lyricCache.delete(toRemove);
}
}
lyricCache.set(song.id, newResult);
lyricCache.set(song.id.toString(), newResult);
lyricsFromSource = response;
break;
@@ -122,7 +166,7 @@ const searchRemoteLyrics = async (params: LyricSearchQuery) => {
return results;
};
const getRemoteLyricsById = async (params: LyricGetQuery): Promise<string | null> => {
const getRemoteLyricsById = async (params: LyricGetQuery): Promise<null | string> => {
const { remoteSongId, remoteSource } = params;
const response = await GET_FETCHERS[remoteSource](remoteSongId);
@@ -133,7 +177,7 @@ const getRemoteLyricsById = async (params: LyricGetQuery): Promise<string | null
return response;
};
ipcMain.handle('lyric-by-song', async (_event, song: QueueSong) => {
ipcMain.handle('lyric-by-song', async (_event, song: any) => {
const lyric = await getRemoteLyrics(song);
return lyric;
});
+18 -17
View File
@@ -1,12 +1,13 @@
// Credits to https://github.com/tranxuanthang/lrcget for API implementation
import axios, { AxiosResponse } from 'axios';
import { orderSearchResults } from './shared';
import {
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
LyricSource,
} from '../../../../renderer/api/types';
} from '.';
import { orderSearchResults } from './shared';
const FETCH_URL = 'https://lrclib.net/api/get';
const SEEARCH_URL = 'https://lrclib.net/api/search';
@@ -29,10 +30,23 @@ export interface LrcLibTrackResponse {
isrc: string;
lang: string;
name: string;
plainLyrics: string | null;
plainLyrics: null | string;
releaseDate: string;
spotifyId: string;
syncedLyrics: string | null;
syncedLyrics: null | string;
}
export async function getLyricsBySongId(songId: string): Promise<null | string> {
let result: AxiosResponse<LrcLibTrackResponse, any>;
try {
result = await axios.get<LrcLibTrackResponse>(`${FETCH_URL}/${songId}`);
} catch (e) {
console.error('LrcLib lyrics request got an error!', e);
return null;
}
return result.data.syncedLyrics || result.data.plainLyrics || null;
}
export async function getSearchResults(
@@ -69,19 +83,6 @@ export async function getSearchResults(
return orderSearchResults({ params, results: songResults });
}
export async function getLyricsBySongId(songId: string): Promise<string | null> {
let result: AxiosResponse<LrcLibTrackResponse, any>;
try {
result = await axios.get<LrcLibTrackResponse>(`${FETCH_URL}/${songId}`);
} catch (e) {
console.error('LrcLib lyrics request got an error!', e);
return null;
}
return result.data.syncedLyrics || result.data.plainLyrics || null;
}
export async function query(
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
+45 -44
View File
@@ -1,22 +1,18 @@
import axios, { AxiosResponse } from 'axios';
import { LyricSource } from '../../../../renderer/api/types';
import { orderSearchResults } from './shared';
import type {
import {
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '/@/renderer/api/types';
LyricSource,
} from '.';
import { orderSearchResults } from './shared';
const SEARCH_URL = 'https://music.163.com/api/search/get';
const LYRICS_URL = 'https://music.163.com/api/song/lyric';
// Adapted from https://github.com/NyaomiDEV/Sunamu/blob/master/src/main/lyricproviders/netease.ts
export interface NetEaseResponse {
code: number;
result: Result;
}
export interface Result {
hasMore: boolean;
songCount: number;
@@ -35,13 +31,13 @@ export interface Song {
mark: number;
mvid: number;
name: string;
rUrl: null;
rtype: number;
rUrl: null;
status: number;
transNames?: string[];
}
export interface Album {
interface Album {
artist: Artist;
copyrightId: number;
id: number;
@@ -54,7 +50,7 @@ export interface Album {
transNames?: string[];
}
export interface Artist {
interface Artist {
albumSize: number;
alias: any[];
fansGroup: null;
@@ -67,6 +63,29 @@ export interface Artist {
trans: null;
}
interface NetEaseResponse {
code: number;
result: Result;
}
export async function getLyricsBySongId(songId: string): Promise<null | string> {
let result: AxiosResponse<any, any>;
try {
result = await axios.get(LYRICS_URL, {
params: {
id: songId,
kv: '-1',
lv: '-1',
},
});
} catch (e) {
console.error('NetEase lyrics request got an error!', e);
return null;
}
return result.data.klyric?.lyric || result.data.lrc?.lyric;
}
export async function getSearchResults(
params: LyricSearchQuery,
): Promise<InternetProviderLyricSearchResponse[] | null> {
@@ -110,38 +129,6 @@ export async function getSearchResults(
return orderSearchResults({ params, results: songResults });
}
async function getMatchedLyrics(
params: LyricSearchQuery,
): Promise<Omit<InternetProviderLyricResponse, 'lyrics'> | null> {
const results = await getSearchResults(params);
const firstMatch = results?.[0];
if (!firstMatch || (firstMatch?.score && firstMatch.score > 0.5)) {
return null;
}
return firstMatch;
}
export async function getLyricsBySongId(songId: string): Promise<string | null> {
let result: AxiosResponse<any, any>;
try {
result = await axios.get(LYRICS_URL, {
params: {
id: songId,
kv: '-1',
lv: '-1',
},
});
} catch (e) {
console.error('NetEase lyrics request got an error!', e);
return null;
}
return result.data.klyric?.lyric || result.data.lrc?.lyric;
}
export async function query(
params: LyricSearchQuery,
): Promise<InternetProviderLyricResponse | null> {
@@ -165,3 +152,17 @@ export async function query(
source: LyricSource.NETEASE,
};
}
async function getMatchedLyrics(
params: LyricSearchQuery,
): Promise<null | Omit<InternetProviderLyricResponse, 'lyrics'>> {
const results = await getSearchResults(params);
const firstMatch = results?.[0];
if (!firstMatch || (firstMatch?.score && firstMatch.score > 0.5)) {
return null;
}
return firstMatch;
}
+2 -1
View File
@@ -1,8 +1,9 @@
import Fuse from 'fuse.js';
import {
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '../../../../renderer/api/types';
} from '/@/shared/types/domain-types';
export const orderSearchResults = (args: {
params: LyricSearchQuery;
+27 -25
View File
@@ -1,10 +1,11 @@
import console from 'console';
import { rm } from 'fs/promises';
import { pid } from 'node:process';
import { app, ipcMain } from 'electron';
import { rm } from 'fs/promises';
import uniq from 'lodash/uniq';
import MpvAPI from 'node-mpv';
import { getMainWindow, sendToastToRenderer } from '../../../main';
import { pid } from 'node:process';
import { getMainWindow, sendToastToRenderer } from '../../../index';
import { createLog, isWindows } from '../../../utils';
import { store } from '../settings';
@@ -86,7 +87,7 @@ const createMpv = async (data: {
extraParameters?: string[];
properties?: Record<string, any>;
}): Promise<MpvAPI> => {
const { extraParameters, properties, binaryPath } = data;
const { binaryPath, extraParameters, properties } = data;
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
@@ -174,7 +175,7 @@ ipcMain.on('player-set-properties', async (_event, data: Record<string, any>) =>
} else {
getMpvInstance()?.setMultipleProperties(data);
}
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to set properties: ${JSON.stringify(data)}` }, err);
}
});
@@ -199,7 +200,7 @@ ipcMain.handle(
mpvInstance = await createMpv(data);
mpvLog({ action: 'Restarted mpv', toast: 'success' });
setAudioPlayerFallback(false);
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to restart mpv, falling back to web player' }, err);
setAudioPlayerFallback(true);
}
@@ -215,7 +216,7 @@ ipcMain.handle(
});
mpvInstance = await createMpv(data);
setAudioPlayerFallback(false);
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to initialize mpv, falling back to web player' }, err);
setAudioPlayerFallback(true);
}
@@ -226,7 +227,7 @@ ipcMain.on('player-quit', async () => {
try {
await getMpvInstance()?.stop();
await quit();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to quit mpv' }, err);
} finally {
mpvInstance = null;
@@ -245,7 +246,7 @@ ipcMain.handle('player-clean-up', async () => {
ipcMain.on('player-start', async () => {
try {
await getMpvInstance()?.play();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to start mpv playback' }, err);
}
});
@@ -254,7 +255,7 @@ ipcMain.on('player-start', async () => {
ipcMain.on('player-play', async () => {
try {
await getMpvInstance()?.play();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to start mpv playback' }, err);
}
});
@@ -263,7 +264,7 @@ ipcMain.on('player-play', async () => {
ipcMain.on('player-pause', async () => {
try {
await getMpvInstance()?.pause();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to pause mpv playback' }, err);
}
});
@@ -272,7 +273,7 @@ ipcMain.on('player-pause', async () => {
ipcMain.on('player-stop', async () => {
try {
await getMpvInstance()?.stop();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to stop mpv playback' }, err);
}
});
@@ -281,7 +282,7 @@ ipcMain.on('player-stop', async () => {
ipcMain.on('player-next', async () => {
try {
await getMpvInstance()?.next();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to go to next track' }, err);
}
});
@@ -290,7 +291,7 @@ ipcMain.on('player-next', async () => {
ipcMain.on('player-previous', async () => {
try {
await getMpvInstance()?.prev();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: 'Failed to go to previous track' }, err);
}
});
@@ -299,7 +300,7 @@ ipcMain.on('player-previous', async () => {
ipcMain.on('player-seek', async (_event, time: number) => {
try {
await getMpvInstance()?.seek(time);
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to seek by ${time} seconds` }, err);
}
});
@@ -308,7 +309,7 @@ ipcMain.on('player-seek', async (_event, time: number) => {
ipcMain.on('player-seek-to', async (_event, time: number) => {
try {
await getMpvInstance()?.goToPosition(time);
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to seek to ${time} seconds` }, err);
}
});
@@ -320,7 +321,7 @@ ipcMain.on('player-set-queue', async (_event, current?: string, next?: string, p
await getMpvInstance()?.clearPlaylist();
await getMpvInstance()?.pause();
return;
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to clear play queue` }, err);
}
}
@@ -329,7 +330,8 @@ ipcMain.on('player-set-queue', async (_event, current?: string, next?: string, p
if (current) {
try {
await getMpvInstance()?.load(current, 'replace');
} catch (error) {
} catch (error: any | NodeMpvError) {
mpvLog({ action: `Failed to load current song` }, error);
await getMpvInstance()?.play();
}
@@ -344,7 +346,7 @@ ipcMain.on('player-set-queue', async (_event, current?: string, next?: string, p
// Only force play if pause is explicitly false
await getMpvInstance()?.play();
}
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to set play queue` }, err);
}
});
@@ -365,7 +367,7 @@ ipcMain.on('player-set-queue-next', async (_event, url?: string) => {
if (url) {
await getMpvInstance()?.load(url, 'append');
}
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to set play queue` }, err);
}
});
@@ -385,7 +387,7 @@ ipcMain.on('player-auto-next', async (_event, url?: string) => {
if (url) {
await getMpvInstance()?.load(url, 'append');
}
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to load next song` }, err);
}
});
@@ -398,7 +400,7 @@ ipcMain.on('player-volume', async (_event, value: number) => {
}
await getMpvInstance()?.volume(value);
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to set volume to ${value}` }, err);
}
});
@@ -407,7 +409,7 @@ ipcMain.on('player-volume', async (_event, value: number) => {
ipcMain.on('player-mute', async (_event, mute: boolean) => {
try {
await getMpvInstance()?.mute(mute);
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to set mute status` }, err);
}
});
@@ -415,7 +417,7 @@ ipcMain.on('player-mute', async (_event, mute: boolean) => {
ipcMain.handle('player-get-time', async (): Promise<number | undefined> => {
try {
return getMpvInstance()?.getTimePosition();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to get current time` }, err);
return 0;
}
@@ -442,7 +444,7 @@ app.on('before-quit', async (event) => {
event.preventDefault();
await getMpvInstance()?.stop();
await quit();
} catch (err: NodeMpvError | any) {
} catch (err: any | NodeMpvError) {
mpvLog({ action: `Failed to cleanly before-quit` }, err);
} finally {
mpvState = MpvState.DONE;
+1 -1
View File
@@ -1,5 +1,5 @@
/* eslint-disable promise/always-return */
import { BrowserWindow, globalShortcut, systemPreferences } from 'electron';
import { isMacOS } from '../../../utils';
import { store } from '../settings';
+101 -101
View File
@@ -1,23 +1,36 @@
import { Stats, promises } from 'fs';
import { readFile } from 'fs/promises';
import { IncomingMessage, Server, ServerResponse, createServer } from 'http';
import { join } from 'path';
import { deflate, gzip } from 'zlib';
import axios from 'axios';
import { app, ipcMain } from 'electron';
import { Server as WsServer, WebSocketServer, WebSocket } from 'ws';
import { promises, Stats } from 'fs';
import { readFile } from 'fs/promises';
import { createServer, IncomingMessage, Server, ServerResponse } from 'http';
import { join } from 'path';
import { WebSocket, WebSocketServer, Server as WsServer } from 'ws';
import { deflate, gzip } from 'zlib';
import manifest from './manifest.json';
import { ClientEvent, ServerEvent } from '../../../../remote/types';
import { PlayerRepeat, PlayerStatus, SongState } from '../../../../renderer/types';
import { getMainWindow } from '../../../main';
import { isLinux } from '../../../utils';
import type { QueueSong } from '/@/renderer/api/types';
import { getMainWindow } from '/@/main/index';
import { isLinux } from '/@/main/utils';
import { QueueSong } from '/@/shared/types/domain-types';
import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types';
import { PlayerRepeat, PlayerStatus, SongState } from '/@/shared/types/types';
let mprisPlayer: any | undefined;
if (isLinux()) {
// eslint-disable-next-line global-require
mprisPlayer = require('../../linux/mpris').mprisPlayer;
async function initMpris() {
if (isLinux()) {
const mpris = await import('../../linux/mpris');
mprisPlayer = mpris.mprisPlayer;
}
}
initMpris();
interface MimeType {
css: string;
html: string;
ico: string;
js: string;
}
interface RemoteConfig {
@@ -27,21 +40,13 @@ interface RemoteConfig {
username: string;
}
interface MimeType {
css: string;
html: string;
ico: string;
js: string;
}
declare class StatefulWebSocket extends WebSocket {
alive: boolean;
auth: boolean;
}
let server: Server | undefined;
let wsServer: WsServer<typeof StatefulWebSocket> | undefined;
let wsServer: undefined | WsServer<typeof StatefulWebSocket>;
const settings: RemoteConfig = {
enabled: false,
@@ -54,14 +59,6 @@ type SendData = ServerEvent & {
client: StatefulWebSocket;
};
function send({ client, event, data }: SendData): void {
if (client.readyState === WebSocket.OPEN) {
if (client.alive && client.auth) {
client.send(JSON.stringify({ data, event }));
}
}
}
function broadcast(message: ServerEvent): void {
if (wsServer) {
for (const client of wsServer.clients) {
@@ -70,7 +67,15 @@ function broadcast(message: ServerEvent): void {
}
}
const shutdownServer = () => {
function send({ client, data, event }: SendData): void {
if (client.readyState === WebSocket.OPEN) {
if (client.alive && client.auth) {
client.send(JSON.stringify({ data, event }));
}
}
}
export const shutdownServer = () => {
if (wsServer) {
wsServer.clients.forEach((client) => client.close(4000));
wsServer.close();
@@ -121,21 +126,17 @@ const getEncoding = (encoding: string | string[]): Encoding => {
const cache = new Map<string, Map<Encoding, [number, Buffer]>>();
function setOk(
res: ServerResponse,
mtimeMs: number,
extension: keyof MimeType,
encoding: Encoding,
data?: Buffer,
) {
res.statusCode = data ? 200 : 304;
function authorize(req: IncomingMessage): boolean {
if (settings.username || settings.password) {
// https://stackoverflow.com/questions/23616371/basic-http-authentication-with-node-and-express-4
res.setHeader('Content-Type', MIME_TYPES[extension]);
res.setHeader('ETag', `"${mtimeMs}"`);
res.setHeader('Cache-Control', 'public');
const authorization = req.headers.authorization?.split(' ')[1] || '';
const [login, password] = Buffer.from(authorization, 'base64').toString().split(':');
if (encoding !== 'none') res.setHeader('Content-Encoding', encoding);
res.end(data);
return login === settings.username && password === settings.password;
}
return true;
}
async function serveFile(
@@ -147,7 +148,7 @@ async function serveFile(
const fileName = `${file}.${extension}`;
const path = app.isPackaged
? join(__dirname, '../remote', fileName)
: join(__dirname, '../../../../../.erb/dll', fileName);
: join(__dirname, '../../out/remote', fileName);
let stats: Stats;
@@ -252,17 +253,21 @@ async function serveFile(
return Promise.resolve();
}
function authorize(req: IncomingMessage): boolean {
if (settings.username || settings.password) {
// https://stackoverflow.com/questions/23616371/basic-http-authentication-with-node-and-express-4
function setOk(
res: ServerResponse,
mtimeMs: number,
extension: keyof MimeType,
encoding: Encoding,
data?: Buffer,
) {
res.statusCode = data ? 200 : 304;
const authorization = req.headers.authorization?.split(' ')[1] || '';
const [login, password] = Buffer.from(authorization, 'base64').toString().split(':');
res.setHeader('Content-Type', MIME_TYPES[extension]);
res.setHeader('ETag', `"${mtimeMs}"`);
res.setHeader('Cache-Control', 'public');
return login === settings.username && password === settings.password;
}
return true;
if (encoding !== 'none') res.setHeader('Content-Encoding', encoding);
res.end(data);
}
const enableServer = (config: RemoteConfig): Promise<void> => {
@@ -286,28 +291,28 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
await serveFile(req, 'index', 'html', res);
break;
}
case '/credentials': {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(req.headers.authorization);
break;
}
case '/favicon.ico': {
await serveFile(req, 'favicon', 'ico', res);
break;
}
case '/remote.css': {
await serveFile(req, 'remote', 'css', res);
break;
}
case '/remote.js': {
await serveFile(req, 'remote', 'js', res);
break;
}
case '/manifest.json': {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(manifest));
break;
}
case '/credentials': {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(req.headers.authorization);
case '/remote.css': {
await serveFile(req, 'remote', 'css', res);
break;
}
case '/remote.js': {
await serveFile(req, 'remote', 'js', res);
break;
}
default: {
@@ -371,6 +376,21 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
}
switch (event) {
case 'favorite': {
const { favorite, id } = json;
if (id && id === currentState.song?.id) {
getMainWindow()?.webContents.send('request-favorite', {
favorite,
id,
serverId: currentState.song.serverId,
});
}
break;
}
case 'next': {
getMainWindow()?.webContents.send('renderer-player-next');
break;
}
case 'pause': {
getMainWindow()?.webContents.send('renderer-player-pause');
break;
@@ -379,10 +399,6 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
getMainWindow()?.webContents.send('renderer-player-play');
break;
}
case 'next': {
getMainWindow()?.webContents.send('renderer-player-next');
break;
}
case 'previous': {
getMainWindow()?.webContents.send('renderer-player-previous');
break;
@@ -421,6 +437,17 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
break;
}
case 'rating': {
const { id, rating } = json;
if (id && id === currentState.song?.id) {
getMainWindow()?.webContents.send('request-rating', {
id,
rating,
serverId: currentState.song.serverId,
});
}
break;
}
case 'repeat': {
getMainWindow()?.webContents.send('renderer-player-toggle-repeat');
break;
@@ -450,28 +477,6 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
}
break;
}
case 'favorite': {
const { favorite, id } = json;
if (id && id === currentState.song?.id) {
getMainWindow()?.webContents.send('request-favorite', {
favorite,
id,
serverId: currentState.song.serverId,
});
}
break;
}
case 'rating': {
const { rating, id } = json;
if (id && id === currentState.song?.id) {
getMainWindow()?.webContents.send('request-rating', {
id,
rating,
serverId: currentState.song.serverId,
});
}
break;
}
case 'position': {
const { position } = json;
if (mprisPlayer) {
@@ -631,15 +636,10 @@ ipcMain.on('update-volume', (_event, volume: number) => {
if (mprisPlayer) {
mprisPlayer.on('loopStatus', (event: string) => {
const repeat =
event === 'Playlist'
? PlayerRepeat.ALL
: event === 'Track'
? PlayerRepeat.ONE
: PlayerRepeat.NONE;
const repeat = event === 'Playlist' ? 'all' : event === 'Track' ? 'one' : 'none';
currentState.repeat = repeat;
broadcast({ data: repeat, event: 'repeat' });
currentState.repeat = repeat as PlayerRepeat;
broadcast({ data: repeat, event: 'repeat' } as ServerEvent);
});
mprisPlayer.on('shuffle', (shuffle: boolean) => {
+13 -3
View File
@@ -1,6 +1,7 @@
import { ipcMain, nativeTheme, safeStorage } from 'electron';
import type { TitleTheme } from '/@/shared/types/types';
import { dialog, ipcMain, nativeTheme, OpenDialogOptions, safeStorage } from 'electron';
import Store from 'electron-store';
import type { TitleTheme } from '/@/renderer/types';
export const store = new Store();
@@ -12,7 +13,7 @@ ipcMain.on('settings-set', (__event, data: { property: string; value: any }) =>
store.set(`${data.property}`, data.value);
});
ipcMain.handle('password-get', (_event, server: string): string | null => {
ipcMain.handle('password-get', (_event, server: string): null | string => {
if (safeStorage.isEncryptionAvailable()) {
const servers = store.get('server') as Record<string, string> | undefined;
@@ -54,3 +55,12 @@ ipcMain.on('theme-set', (_event, theme: TitleTheme) => {
store.set('theme', theme);
nativeTheme.themeSource = theme;
});
ipcMain.handle('open-file-selector', async (_event, options: OpenDialogOptions) => {
const result = await dialog.showOpenDialog({
...options,
properties: ['openFile'],
});
return result.filePaths[0] || null;
});
+1 -3
View File
@@ -1,4 +1,2 @@
import './core';
// eslint-disable-next-line import/no-dynamic-require
require(`./${process.platform}`);
import(`./${process.platform}`);
+5 -4
View File
@@ -1,8 +1,9 @@
import { ipcMain } from 'electron';
import Player from 'mpris-service';
import { PlayerRepeat, PlayerStatus } from '../../../renderer/types';
import { getMainWindow } from '../../main';
import { QueueSong } from '/@/renderer/api/types';
import { getMainWindow } from '/@/main/index';
import { QueueSong } from '/@/shared/types/domain-types';
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
const mprisPlayer = Player({
identity: 'Feishin',
@@ -124,8 +125,8 @@ ipcMain.on('update-playback', (_event, status: PlayerStatus) => {
const REPEAT_TO_MPRIS: Record<PlayerRepeat, string> = {
[PlayerRepeat.ALL]: 'Playlist',
[PlayerRepeat.ONE]: 'Track',
[PlayerRepeat.NONE]: 'None',
[PlayerRepeat.ONE]: 'Track',
};
ipcMain.on('update-repeat', (_event, arg: PlayerRepeat) => {
+79 -56
View File
@@ -1,51 +1,42 @@
/* eslint global-require: off, no-console: off, promise/always-return: off */
/**
* This module executes inside of electron's main process. You can start
* electron renderer process from here and communicate with the other processes
* through IPC.
*
* When running `npm run build` or `npm run build:main`, this file is compiled to
* `./src/main.js` using webpack. This gives us some performance wins.
*/
import { access, constants, readFile, writeFile } from 'fs';
import path, { join } from 'path';
import { deflate, inflate } from 'zlib';
import { is } from '@electron-toolkit/utils';
import {
app,
BrowserWindow,
shell,
ipcMain,
BrowserWindowConstructorOptions,
globalShortcut,
Tray,
ipcMain,
Menu,
nativeImage,
nativeTheme,
BrowserWindowConstructorOptions,
protocol,
net,
protocol,
Rectangle,
screen,
shell,
Tray,
} from 'electron';
import electronLocalShortcut from 'electron-localshortcut';
import log from 'electron-log/main';
import { autoUpdater } from 'electron-updater';
import { access, constants, readFile, writeFile } from 'fs';
import path, { join } from 'path';
import { deflate, inflate } from 'zlib';
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
import { store } from './features/core/settings/index';
import { shutdownServer } from './features/core/remote';
import { store } from './features/core/settings';
import MenuBuilder from './menu';
import {
autoUpdaterLogInterface,
createLog,
hotkeyToElectronAccelerator,
isLinux,
isMacOS,
isWindows,
resolveHtmlPath,
createLog,
autoUpdaterLogInterface,
} from './utils';
import './features';
import type { TitleTheme } from '/@/renderer/types';
declare module 'node-mpv';
import { TitleTheme } from '/@/shared/types/types';
export default class AppUpdater {
constructor() {
@@ -72,34 +63,51 @@ if (isLinux() && !process.argv.some((a) => a.startsWith('--password-store='))) {
}
let mainWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let tray: null | Tray = null;
let exitFromTray = false;
let forceQuit = false;
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
import('source-map-support').then((sourceMapSupport) => {
sourceMapSupport.install();
});
}
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true';
if (isDevelopment) {
require('electron-debug')();
import('electron-debug').then((electronDebug) => {
electronDebug.default();
});
}
const installExtensions = async () => {
const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
import('electron-devtools-installer').then((installer) => {
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
return installer
.default(
extensions.map((name) => installer[name]),
forceDownload,
)
.catch(console.log);
return installer
.default(
extensions.map((name) => installer[name]),
forceDownload,
)
.then((installedExtensions) => {
createLog({
message: `Installed extension: ${installedExtensions}`,
type: 'info',
});
})
.catch(console.error);
});
};
const userDataPath = app.getPath('userData');
if (isDevelopment) {
const devUserDataPath = `${userDataPath}-dev`;
app.setPath('userData', devUserDataPath);
}
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
: path.join(__dirname, '../../assets');
@@ -117,7 +125,7 @@ export const sendToastToRenderer = ({
type,
}: {
message: string;
type: 'success' | 'error' | 'warning' | 'info';
type: 'error' | 'info' | 'success' | 'warning';
}) => {
getMainWindow()?.webContents.send('toast-from-main', {
message,
@@ -208,7 +216,7 @@ const createTray = () => {
tray.setContextMenu(contextMenu);
};
const createWindow = async (first = true) => {
async function createWindow(first = true): Promise<void> {
if (isDevelopment) {
await installExtensions().catch(console.log);
}
@@ -233,6 +241,7 @@ const createWindow = async (first = true) => {
},
};
// Create the browser window.
mainWindow = new BrowserWindow({
autoHideMenuBar: true,
frame: false,
@@ -247,9 +256,8 @@ const createWindow = async (first = true) => {
contextIsolation: true,
devTools: true,
nodeIntegration: true,
preload: app.isPackaged
? path.join(__dirname, 'preload.js')
: path.join(__dirname, '../../.erb/dll/preload.js'),
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
webSecurity: !store.get('ignore_cors'),
},
width: 1440,
@@ -303,6 +311,7 @@ const createWindow = async (first = true) => {
});
ipcMain.on('window-quit', () => {
shutdownServer();
mainWindow?.close();
app.exit();
});
@@ -374,11 +383,11 @@ const createWindow = async (first = true) => {
enableMediaKeys(mainWindow);
}
mainWindow.loadURL(resolveHtmlPath('index.html'));
const startWindowMinimized = store.get('window_start_minimized', false) as boolean;
mainWindow.on('ready-to-show', () => {
// mainWindow.show()
if (!mainWindow) {
throw new Error('"mainWindow" is not defined');
}
@@ -457,7 +466,7 @@ const createWindow = async (first = true) => {
}
});
mainWindow.on('minimize', (event: any) => {
(mainWindow as any).on('minimize', (event: any) => {
if (store.get('window_minimize_to_tray') === true) {
event.preventDefault();
mainWindow?.hide();
@@ -484,16 +493,31 @@ const createWindow = async (first = true) => {
});
if (store.get('disable_auto_updates') !== true) {
// eslint-disable-next-line
new AppUpdater();
}
const theme = store.get('theme') as TitleTheme | undefined;
nativeTheme.themeSource = theme || 'dark';
};
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url);
return { action: 'deny' };
});
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']);
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
}
}
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
// https://github.com/electron/electron/issues/46538#issuecomment-2808806722
app.commandLine.appendSwitch('gtk-version', '3');
// Must duplicate with the one in renderer process settings.store.ts
enum BindingActions {
GLOBAL_SEARCH = 'globalSearch',
@@ -516,6 +540,8 @@ enum BindingActions {
}
const HOTKEY_ACTIONS: Record<BindingActions, () => void> = {
[BindingActions.GLOBAL_SEARCH]: () => {},
[BindingActions.LOCAL_SEARCH]: () => {},
[BindingActions.MUTE]: () => getMainWindow()?.webContents.send('renderer-player-volume-mute'),
[BindingActions.NEXT]: () => getMainWindow()?.webContents.send('renderer-player-next'),
[BindingActions.PAUSE]: () => getMainWindow()?.webContents.send('renderer-player-pause'),
@@ -530,16 +556,14 @@ const HOTKEY_ACTIONS: Record<BindingActions, () => void> = {
[BindingActions.SKIP_FORWARD]: () =>
getMainWindow()?.webContents.send('renderer-player-skip-forward'),
[BindingActions.STOP]: () => getMainWindow()?.webContents.send('renderer-player-stop'),
[BindingActions.TOGGLE_FULLSCREEN_PLAYER]: () => {},
[BindingActions.TOGGLE_QUEUE]: () => {},
[BindingActions.TOGGLE_REPEAT]: () =>
getMainWindow()?.webContents.send('renderer-player-toggle-repeat'),
[BindingActions.VOLUME_UP]: () =>
getMainWindow()?.webContents.send('renderer-player-volume-up'),
[BindingActions.VOLUME_DOWN]: () =>
getMainWindow()?.webContents.send('renderer-player-volume-down'),
[BindingActions.GLOBAL_SEARCH]: () => {},
[BindingActions.LOCAL_SEARCH]: () => {},
[BindingActions.TOGGLE_QUEUE]: () => {},
[BindingActions.TOGGLE_FULLSCREEN_PLAYER]: () => {},
[BindingActions.VOLUME_UP]: () =>
getMainWindow()?.webContents.send('renderer-player-volume-up'),
};
ipcMain.on(
@@ -582,7 +606,7 @@ ipcMain.on(
_event,
data: {
message: string;
type: 'debug' | 'verbose' | 'success' | 'error' | 'warning' | 'info';
type: 'debug' | 'error' | 'info' | 'success' | 'verbose' | 'warning';
},
) => {
createLog(data);
@@ -594,7 +618,6 @@ app.on('window-all-closed', () => {
// Respect the OSX convention of having the application in memory even
// after all windows have been closed
if (isMacOS()) {
ipcMain.removeHandler('window-clear-cache');
mainWindow = null;
} else {
app.quit();
@@ -610,7 +633,7 @@ const FONT_HEADERS = [
'font/woff2',
];
const singleInstance = app.requestSingleInstanceLock();
const singleInstance = isDevelopment ? true : app.requestSingleInstanceLock();
if (!singleInstance) {
app.quit();
+32 -32
View File
@@ -1,4 +1,4 @@
import { app, Menu, shell, BrowserWindow, MenuItemConstructorOptions } from 'electron';
import { app, BrowserWindow, Menu, MenuItemConstructorOptions, shell } from 'electron';
interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions {
selector?: string;
@@ -12,37 +12,6 @@ export default class MenuBuilder {
this.mainWindow = mainWindow;
}
buildMenu(): Menu {
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
this.setupDevelopmentEnvironment();
}
const template =
process.platform === 'darwin'
? this.buildDarwinTemplate()
: this.buildDefaultTemplate();
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
return menu;
}
setupDevelopmentEnvironment(): void {
this.mainWindow.webContents.on('context-menu', (_, props) => {
const { x, y } = props;
Menu.buildFromTemplate([
{
click: () => {
this.mainWindow.webContents.inspectElement(x, y);
},
label: 'Inspect element',
},
]).popup({ window: this.mainWindow });
});
}
buildDarwinTemplate(): MenuItemConstructorOptions[] {
const subMenuAbout: DarwinMenuItemConstructorOptions = {
label: 'Electron',
@@ -276,4 +245,35 @@ export default class MenuBuilder {
return templateDefault;
}
buildMenu(): Menu {
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
this.setupDevelopmentEnvironment();
}
const template =
process.platform === 'darwin'
? this.buildDarwinTemplate()
: this.buildDefaultTemplate();
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
return menu;
}
setupDevelopmentEnvironment(): void {
this.mainWindow.webContents.on('context-menu', (_, props) => {
const { x, y } = props;
Menu.buildFromTemplate([
{
click: () => {
this.mainWindow.webContents.inspectElement(x, y);
},
label: 'Inspect element',
},
]).popup({ window: this.mainWindow });
});
}
}
-23
View File
@@ -1,23 +0,0 @@
import { contextBridge } from 'electron';
import { browser } from './preload/browser';
import { discordRpc } from './preload/discord-rpc';
import { ipc } from './preload/ipc';
import { localSettings } from './preload/local-settings';
import { lyrics } from './preload/lyrics';
import { mpris } from './preload/mpris';
import { mpvPlayer, mpvPlayerListener } from './preload/mpv-player';
import { remote } from './preload/remote';
import { utils } from './preload/utils';
contextBridge.exposeInMainWorld('electron', {
browser,
discordRpc,
ipc,
localSettings,
lyrics,
mpris,
mpvPlayer,
mpvPlayerListener,
remote,
utils,
});
+6 -3
View File
@@ -1,8 +1,7 @@
/* eslint import/prefer-default-export: off, import/no-mutable-exports: off */
import log from 'electron-log/main';
import path from 'path';
import process from 'process';
import { URL } from 'url';
import log from 'electron-log/main';
export let resolveHtmlPath: (htmlFileName: string) => string;
@@ -35,6 +34,10 @@ export const hotkeyToElectronAccelerator = (hotkey: string) => {
let accelerator = hotkey;
const replacements = {
arrowdown: 'Down',
arrowleft: 'Left',
arrowright: 'Right',
arrowup: 'Up',
mod: 'CmdOrCtrl',
numpad: 'num',
numpadadd: 'numadd',
@@ -72,7 +75,7 @@ const logColor = {
export const createLog = (data: {
message: string;
type: 'debug' | 'verbose' | 'success' | 'error' | 'warning' | 'info';
type: 'debug' | 'error' | 'info' | 'success' | 'verbose' | 'warning';
}) => {
logMethod[data.type](`%c${data.message}`, `color: ${logColor[data.type]}`);
};
+15
View File
@@ -0,0 +1,15 @@
import { ElectronAPI } from '@electron-toolkit/preload';
import { PreloadApi } from './index';
declare global {
interface Window {
api: PreloadApi;
electron: ElectronAPI;
queryLocalFonts?: () => Promise<Font[]>;
SERVER_LOCK?: boolean;
SERVER_NAME?: string;
SERVER_TYPE?: ServerType;
SERVER_URL?: string;
}
}
+45
View File
@@ -0,0 +1,45 @@
import { electronAPI } from '@electron-toolkit/preload';
import { contextBridge } from 'electron';
import { browser } from './browser';
import { discordRpc } from './discord-rpc';
import { ipc } from './ipc';
import { localSettings } from './local-settings';
import { lyrics } from './lyrics';
import { mpris } from './mpris';
import { mpvPlayer, mpvPlayerListener } from './mpv-player';
import { remote } from './remote';
import { utils } from './utils';
// Custom APIs for renderer
const api = {
browser,
discordRpc,
ipc,
localSettings,
lyrics,
mpris,
mpvPlayer,
mpvPlayerListener,
remote,
utils,
};
export type PreloadApi = typeof api;
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI);
contextBridge.exposeInMainWorld('api', api);
} catch (error) {
console.error(error);
}
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI;
// @ts-ignore (define in dts)
window.api = api;
}
@@ -1,12 +1,13 @@
import { IpcRendererEvent, ipcRenderer, webFrame } from 'electron';
import { ipcRenderer, IpcRendererEvent, OpenDialogOptions, webFrame } from 'electron';
import Store from 'electron-store';
import { toServerType, type TitleTheme } from '/@/renderer/types';
import { TitleTheme } from '/@/shared/types/types';
const store = new Store();
const set = (
property: string,
value: string | Record<string, unknown> | boolean | string[] | undefined,
value: boolean | Record<string, unknown> | string | string[] | undefined,
) => {
if (value === undefined) {
store.delete(property);
@@ -32,7 +33,7 @@ const disableMediaKeys = () => {
ipcRenderer.send('global-media-keys-disable');
};
const passwordGet = async (server: string): Promise<string | null> => {
const passwordGet = async (server: string): Promise<null | string> => {
return ipcRenderer.invoke('password-get', server);
};
@@ -56,6 +57,24 @@ const themeSet = (theme: TitleTheme): void => {
ipcRenderer.send('theme-set', theme);
};
const openFileSelector = async (options?: OpenDialogOptions) => {
const result = await ipcRenderer.invoke('open-file-selector', options);
return result;
};
export const toServerType = (value?: string): null | string => {
switch (value?.toLowerCase()) {
case 'jellyfin':
return 'jellyfin';
case 'navidrome':
return 'navidrome';
case 'subsonic':
return 'subsonic';
default:
return null;
}
};
const SERVER_TYPE = toServerType(process.env.SERVER_TYPE);
const env = {
@@ -73,6 +92,7 @@ export const localSettings = {
env,
fontError,
get,
openFileSelector,
passwordGet,
passwordRemove,
passwordSet,
@@ -1,11 +1,13 @@
import { ipcRenderer } from 'electron';
import {
InternetProviderLyricSearchResponse,
LyricGetQuery,
LyricSearchQuery,
LyricSource,
QueueSong,
} from '/@/renderer/api/types';
} from '../main/features/core/lyrics';
import { QueueSong } from '/@/shared/types/domain-types';
const getRemoteLyricsBySong = (song: QueueSong) => {
const result = ipcRenderer.invoke('lyric-by-song', song);
@@ -1,5 +1,6 @@
import { IpcRendererEvent, ipcRenderer } from 'electron';
import type { PlayerRepeat } from '/@/renderer/types';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import { PlayerRepeat } from '/@/shared/types/types';
const updatePosition = (timeSec: number) => {
ipcRenderer.send('mpris-update-position', timeSec);
@@ -1,5 +1,6 @@
import { ipcRenderer, IpcRendererEvent } from 'electron';
import { PlayerData } from '/@/renderer/store';
import { PlayerData } from '/@/shared/types/domain-types';
const initialize = (data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
return ipcRenderer.invoke('player-initialize', data);
@@ -187,8 +188,8 @@ export const mpvPlayerListener = {
rendererNext,
rendererPause,
rendererPlay,
rendererPlayPause,
rendererPlayerFallback,
rendererPlayPause,
rendererPrevious,
rendererQuit,
rendererSkipBackward,
@@ -1,6 +1,7 @@
import { IpcRendererEvent, ipcRenderer } from 'electron';
import { QueueSong } from '/@/renderer/api/types';
import { PlayerStatus } from '/@/renderer/types';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import { QueueSong } from '/@/shared/types/domain-types';
import { PlayerStatus } from '/@/shared/types/types';
const requestFavorite = (
cb: (
@@ -29,12 +30,12 @@ const requestVolume = (cb: (event: IpcRendererEvent, data: { volume: number }) =
ipcRenderer.on('request-volume', cb);
};
const setRemoteEnabled = (enabled: boolean): Promise<string | null> => {
const setRemoteEnabled = (enabled: boolean): Promise<null | string> => {
const result = ipcRenderer.invoke('remote-enable', enabled);
return result;
};
const setRemotePort = (port: number): Promise<string | null> => {
const setRemotePort = (port: number): Promise<null | string> => {
const result = ipcRenderer.invoke('remote-port', port);
return result;
};
@@ -56,7 +57,7 @@ const updateSetting = (
port: number,
username: string,
password: string,
): Promise<string | null> => {
): Promise<null | string> => {
return ipcRenderer.invoke('remote-settings', enabled, port, username, password);
};
@@ -1,6 +1,6 @@
import { IpcRendererEvent, ipcRenderer } from 'electron';
import { isMacOS, isWindows, isLinux } from '../utils';
import { PlayerState } from '/@/renderer/store';
import { ipcRenderer, IpcRendererEvent } from 'electron';
import { isLinux, isMacOS, isWindows } from '../main/utils';
const saveQueue = (data: Record<string, any>) => {
ipcRenderer.send('player-save-queue', data);
@@ -18,7 +18,7 @@ const onSaveQueue = (cb: (event: IpcRendererEvent) => void) => {
ipcRenderer.on('renderer-save-queue', cb);
};
const onRestoreQueue = (cb: (event: IpcRendererEvent, data: Partial<PlayerState>) => void) => {
const onRestoreQueue = (cb: (event: IpcRendererEvent, data: Partial<any>) => void) => {
ipcRenderer.on('renderer-restore-queue', cb);
};
@@ -29,7 +29,7 @@ const playerErrorListener = (cb: (event: IpcRendererEvent, data: { code: number
const mainMessageListener = (
cb: (
event: IpcRendererEvent,
data: { message: string; type: 'success' | 'error' | 'warning' | 'info' },
data: { message: string; type: 'error' | 'info' | 'success' | 'warning' },
) => void,
) => {
ipcRenderer.on('toast-from-main', cb);
@@ -40,7 +40,7 @@ const logger = (
event: IpcRendererEvent,
data: {
message: string;
type: 'debug' | 'verbose' | 'error' | 'warning' | 'info';
type: 'debug' | 'error' | 'info' | 'verbose' | 'warning';
},
) => void,
) => {

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