Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| adc09e6bbf | |||
| ba20dc0972 | |||
| 999b6afd0e | |||
| bf0cd0e76f | |||
| f98950fe19 | |||
| 4e58feedd0 | |||
| 50aa3960e6 | |||
| 5a821bf5ce | |||
| 123c9795dc | |||
| 6ac0b8734a | |||
| 8f45d01d00 | |||
| 809f2683a2 | |||
| 8e0ef96596 | |||
| f74c2bc4e2 | |||
| 8e8380854e | |||
| 0a48add9f4 | |||
| b05d532941 | |||
| d517afdfd3 | |||
| 63e441429e | |||
| c46fa75266 | |||
| fd851714ae | |||
| 364f53e124 | |||
| 9756ed2d27 | |||
| 6bd836fad0 | |||
| 327fa2b02f | |||
| 88922766db | |||
| f5b185f5f0 | |||
| ce08a70d89 | |||
| b635eb20cf | |||
| 9b0fad6742 | |||
| 401912a70c | |||
| cd3ec158d3 | |||
| d51ca37e1b | |||
| 9d8dcc7ade | |||
| 8430b1ec95 | |||
| 11863fd4c1 | |||
| cf9c7e2640 | |||
| 9d780e0342 | |||
| 4ec981df83 | |||
| e5564c2ac2 | |||
| 7a580c2c65 | |||
| ac84088c69 | |||
| 3c2e4d40ec | |||
| fdff79496a | |||
| ccfadda729 | |||
| f21b8d6bbd | |||
| 244c00c4c6 | |||
| 2664a80851 | |||
| 742b13d65e | |||
| 8dcd49d574 | |||
| 02c8cbcad6 | |||
| 86fb52f6d4 | |||
| 452ef783f2 | |||
| 74cab01013 | |||
| e6ed9229c2 | |||
| 3a144ab821 | |||
| 913e89b01b | |||
| 768a88de8f | |||
| 8e2a107d4a | |||
| e77efcf836 | |||
| 818f155993 | |||
| b28fe4cbc9 | |||
| 8a53fab751 | |||
| 9964f95d5d | |||
| fe298d3232 | |||
| 03e582f301 | |||
| d7b3d5c0bd | |||
| 5fdf4c06f9 | |||
| c7aa5d09c9 | |||
| f4f73289c9 | |||
| ac7ec133db | |||
| 1a948ab86b | |||
| f6667a39a0 | |||
| cbeb4ab7d8 | |||
| 3675146f1f | |||
| 946f4ff306 | |||
| 277669c413 | |||
| 49b6478b72 | |||
| ca39409cc3 | |||
| cca6fa21db | |||
| 5e1059870c | |||
| 6bac172bbe | |||
| 118a9f73d1 | |||
| c464be8cea | |||
| 3bbe696f4c | |||
| f7cacd2b73 | |||
| 62794623a3 | |||
| 9e3e038d42 | |||
| b375238baf | |||
| 02b06a07be | |||
| d7f21b3c6b | |||
| 1113ef972f | |||
| 46a2c29b22 | |||
| ebcb7bc4d1 | |||
| 0b62bee3a6 | |||
| d3503af12c | |||
| 571ea3c653 | |||
| f0e518d3c8 | |||
| 47dc83f360 | |||
| 8cbc25a932 | |||
| 0cba405b45 | |||
| 45b80ac395 | |||
| 8b0fe69e1c | |||
| 25e621372c | |||
| 14f4649b93 | |||
| 1a87adb728 | |||
| bb9bf7ba6a | |||
| fb7e7bfa3e | |||
| a8a14a62c0 | |||
| cd836d54db | |||
| c90c43944d | |||
| fd7468a4fe | |||
| c4f9868a6b | |||
| fbb0907a70 | |||
| 201ee895f9 | |||
| 51be0153d3 | |||
| 29a9a11085 | |||
| 65f28bb9dc | |||
| fd264daffc | |||
| 18e35f2ba9 | |||
| 487e9be8ec | |||
| d9049ed066 | |||
| 6e62448b88 | |||
| ec457d5125 | |||
| d45b01625b | |||
| 2defa5cc13 | |||
| 9cc9c3a87f | |||
| 153d8ce6ce | |||
| 5e33212112 | |||
| 7d6990eb90 | |||
| d75ea94161 | |||
| 1badecc20a | |||
| c90a56811d | |||
| 4e5e3bc9a1 | |||
| c8397bb5ef | |||
| 0ae53b023c | |||
| 1acfa93f1a | |||
| b60ba27892 | |||
| 7ddba8ede7 | |||
| a8bd53b757 | |||
| 877b2e9f3b | |||
| 663893dccb |
@@ -7,53 +7,58 @@ 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 || {})],
|
||||
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,
|
||||
},
|
||||
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',
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
output: {
|
||||
// https://github.com/webpack/webpack/issues/1114
|
||||
library: {
|
||||
type: 'commonjs2',
|
||||
/**
|
||||
* 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'],
|
||||
},
|
||||
|
||||
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',
|
||||
stats: 'errors-only',
|
||||
};
|
||||
|
||||
export default configuration;
|
||||
|
||||
@@ -9,111 +9,119 @@ 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');
|
||||
checkNodeEnv('development');
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 4343;
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
devtool: 'inline-source-map',
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
mode: 'development',
|
||||
mode: 'development',
|
||||
|
||||
target: ['web'],
|
||||
target: ['web'],
|
||||
|
||||
entry: [path.join(webpackPaths.srcRemotePath, 'index.tsx')],
|
||||
|
||||
output: {
|
||||
path: webpackPaths.dllPath,
|
||||
publicPath: '/',
|
||||
filename: 'remote.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
entry: {
|
||||
remote: path.join(webpackPaths.srcRemotePath, 'index.tsx'),
|
||||
worker: path.join(webpackPaths.srcRemotePath, 'service-worker.ts'),
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
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',
|
||||
},
|
||||
},
|
||||
'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,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
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',
|
||||
}),
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: true,
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.join('index.html'),
|
||||
template: path.join(webpackPaths.srcRemotePath, 'index.ejs'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: true,
|
||||
env: process.env.NODE_ENV,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: webpackPaths.appNodeModulesPath,
|
||||
}),
|
||||
],
|
||||
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
watch: true,
|
||||
watch: true,
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
|
||||
@@ -17,115 +17,126 @@ 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',
|
||||
}
|
||||
: {};
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? {
|
||||
devtool: 'source-map',
|
||||
}
|
||||
: {};
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
...devtoolsConfig,
|
||||
...devtoolsConfig,
|
||||
|
||||
mode: 'production',
|
||||
mode: 'production',
|
||||
|
||||
target: ['web'],
|
||||
target: ['web'],
|
||||
|
||||
entry: [path.join(webpackPaths.srcRemotePath, 'index.tsx')],
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distRemotePath,
|
||||
publicPath: './',
|
||||
filename: 'remote.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
entry: {
|
||||
remote: path.join(webpackPaths.srcRemotePath, 'index.tsx'),
|
||||
worker: path.join(webpackPaths.srcRemotePath, 'service-worker.ts'),
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
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',
|
||||
},
|
||||
},
|
||||
'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,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
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.srcRendererPath, 'index.ejs'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: false,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
|
||||
@@ -13,131 +13,132 @@ 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');
|
||||
checkNodeEnv('development');
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 4343;
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
devtool: 'inline-source-map',
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
mode: 'development',
|
||||
mode: 'development',
|
||||
|
||||
target: ['web', 'electron-renderer'],
|
||||
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',
|
||||
},
|
||||
entry: [
|
||||
`webpack-dev-server/client?http://localhost:${port}/dist`,
|
||||
'webpack/hot/only-dev-server',
|
||||
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
|
||||
],
|
||||
},
|
||||
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'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: false,
|
||||
env: process.env.NODE_ENV,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: webpackPaths.appNodeModulesPath,
|
||||
}),
|
||||
],
|
||||
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
devServer: {
|
||||
port,
|
||||
compress: true,
|
||||
hot: true,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
static: {
|
||||
publicPath: '/',
|
||||
output: {
|
||||
path: webpackPaths.distRendererPath,
|
||||
publicPath: '/',
|
||||
filename: 'renderer.dev.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
historyApiFallback: {
|
||||
verbose: true,
|
||||
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
setupMiddlewares(middlewares) {
|
||||
return middlewares;
|
||||
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,
|
||||
}),
|
||||
],
|
||||
|
||||
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);
|
||||
|
||||
@@ -38,7 +38,7 @@ const configuration: webpack.Configuration = {
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distWebPath,
|
||||
publicPath: '/',
|
||||
publicPath: 'auto',
|
||||
filename: 'renderer.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
@@ -120,6 +120,7 @@ const configuration: webpack.Configuration = {
|
||||
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,
|
||||
|
||||
@@ -5,6 +5,7 @@ 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');
|
||||
@@ -24,21 +25,22 @@ const distWebPath = path.join(distPath, 'web');
|
||||
const buildPath = path.join(releasePath, 'build');
|
||||
|
||||
export default {
|
||||
rootPath,
|
||||
dllPath,
|
||||
srcPath,
|
||||
srcMainPath,
|
||||
srcRemotePath,
|
||||
srcRendererPath,
|
||||
releasePath,
|
||||
appPath,
|
||||
appPackagePath,
|
||||
appNodeModulesPath,
|
||||
srcNodeModulesPath,
|
||||
distPath,
|
||||
distMainPath,
|
||||
distRemotePath,
|
||||
distRendererPath,
|
||||
distWebPath,
|
||||
buildPath,
|
||||
assetsPath,
|
||||
rootPath,
|
||||
dllPath,
|
||||
srcPath,
|
||||
srcMainPath,
|
||||
srcRemotePath,
|
||||
srcRendererPath,
|
||||
releasePath,
|
||||
appPath,
|
||||
appPackagePath,
|
||||
appNodeModulesPath,
|
||||
srcNodeModulesPath,
|
||||
distPath,
|
||||
distMainPath,
|
||||
distRemotePath,
|
||||
distRendererPath,
|
||||
distWebPath,
|
||||
buildPath,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# Referenced from: https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#introduction
|
||||
name: Publish Docker to GHCR
|
||||
permissions: write-all
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm/v7
|
||||
linux/arm64/v8
|
||||
@@ -0,0 +1,46 @@
|
||||
# Referenced from: https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#introduction
|
||||
name: Publish Docker to GHCR (Manual)
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm/v7
|
||||
linux/arm64/v8
|
||||
@@ -1,27 +1,17 @@
|
||||
{
|
||||
"customSyntax": "postcss-styled-syntax",
|
||||
"extends": [
|
||||
"stylelint-config-standard-scss",
|
||||
"stylelint-config-css-modules",
|
||||
"stylelint-config-rational-order"
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-styled-components",
|
||||
"stylelint-config-recess-order"
|
||||
],
|
||||
"rules": {
|
||||
"indentation": 4,
|
||||
"color-function-notation": ["legacy"],
|
||||
"declaration-empty-line-before": null,
|
||||
"order/properties-order": [],
|
||||
"plugin/rational-order": [
|
||||
true,
|
||||
{
|
||||
"border-in-box-model": false,
|
||||
"empty-line-between-groups": false
|
||||
}
|
||||
],
|
||||
"string-quotes": "single",
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"selector-class-pattern": null,
|
||||
"selector-type-case": ["lower", { "ignoreTypes": ["/^\\$\\w+/"] }],
|
||||
"selector-type-no-unknown": [true, { "ignoreTypes": ["/-styled-mixin/", "/^\\$\\w+/"] }],
|
||||
"value-keyword-case": ["lower", { "ignoreKeywords": ["dummyValue"] }],
|
||||
"declaration-colon-newline-after": null
|
||||
"declaration-colon-newline-after": null,
|
||||
"property-no-vendor-prefix": null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"i18n-ally.localesPaths": ["src/i18n", "src/i18n/locales"],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"stylelint.validate": ["css", "less", "postcss", "scss"],
|
||||
"stylelint.validate": ["css", "scss", "typescript", "typescriptreact"],
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# --- Builder stage
|
||||
FROM node:18-alpine as builder
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
# Scripts include electron-specific dependencies, which we don't need
|
||||
RUN npm install --legacy-peer-deps --ignore-scripts
|
||||
RUN npm run build:web
|
||||
|
||||
# --- Production stage
|
||||
FROM nginx:alpine-slim
|
||||
|
||||
COPY --chown=nginx:nginx --from=builder /app/release/app/dist/web /usr/share/nginx/html
|
||||
COPY ng.conf.template /etc/nginx/templates/default.conf.template
|
||||
|
||||
ENV PUBLIC_PATH="/"
|
||||
EXPOSE 9180
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,3 +1,5 @@
|
||||
<img src="assets/icons/icon.png" alt="logo" title="feishin" align="right" height="60px" />
|
||||
|
||||
# Feishin
|
||||
|
||||
<p align="center">
|
||||
@@ -29,13 +31,13 @@ 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
|
||||
|
||||
@@ -43,9 +45,26 @@ Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Download the [latest desktop client](https://github.com/jeffvli/feishin/releases).
|
||||
### Desktop (recommended)
|
||||
|
||||
If you're using an M1 macOS device, [check here](https://github.com/jeffvli/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
|
||||
Download the [latest desktop client](https://github.com/jeffvli/feishin/releases). The desktop client is the recommended way to use Feishin. It supports both the MPV and web player backends, as well as includes built-in fetching for lyrics.
|
||||
|
||||
If you're using a device running macOS 12 (Monterey) or higher, [check here](https://github.com/jeffvli/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
|
||||
|
||||
### Web and Docker
|
||||
|
||||
Visit [https://feishin.vercel.app](https://feishin.vercel.app) to use the hosted web version of Feishin. The web client only supports the web player backend.
|
||||
|
||||
Feishin is also available as a Docker image. The images are hosted via `ghcr.io` and are available to view [here](https://github.com/jeffvli/feishin/pkgs/container/feishin). You can run the container using the following commands:
|
||||
|
||||
```bash
|
||||
# Run the latest version
|
||||
docker run --name feishin --port 9180:9180 ghcr.io/jeffvli/feishin:latest
|
||||
|
||||
# Build the image locally
|
||||
docker build -t feishin .
|
||||
docker run --name feishin --port 9180:9180 feishin
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
@@ -53,18 +72,24 @@ If you're using an M1 macOS device, [check here](https://github.com/jeffvli/feis
|
||||
|
||||
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).
|
||||
- **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).
|
||||
|
||||
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`.
|
||||
|
||||
## FAQ
|
||||
|
||||
### MPV is either not working or is rapidly switching between pause/play states
|
||||
|
||||
First thing to do is check that your MPV binary path is correct. Navigate to the settings page and re-set the path and restart the app. If your issue still isn't resolved, try reinstalling MPV. Known working versions include `v0.35.x` and `v0.36.x`. `v0.34.x` is a known broken version.
|
||||
|
||||
### What music servers does Feishin support?
|
||||
|
||||
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)
|
||||
- [Funkwhale](https://funkwhale.audio/) - TBD
|
||||
- Subsonic-compatible servers - TBD
|
||||
- [Navidrome](https://github.com/navidrome/navidrome)
|
||||
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
||||
- [Funkwhale](https://funkwhale.audio/) - TBD
|
||||
- Subsonic-compatible servers - TBD
|
||||
|
||||
## Development
|
||||
|
||||
@@ -72,6 +97,10 @@ Built and tested using Node `v16.15.0`.
|
||||
|
||||
This project is built off of [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate) v4.6.0.
|
||||
|
||||
## Translation
|
||||
|
||||
This project uses [Weblate](https://hosted.weblate.org/projects/feishin/) for translations. If you would like to contribute, please visit the link and submit a translation.
|
||||
|
||||
## License
|
||||
|
||||
[GNU General Public License v3.0 ©](https://github.com/jeffvli/feishin/blob/dev/LICENSE)
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 521 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 980 B After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 101 KiB |
@@ -0,0 +1,19 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,18 @@
|
||||
"name": "feishin",
|
||||
"productName": "Feishin",
|
||||
"description": "Feishin music server",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.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": "npm run build:web && docker build -t jeffvli/feishin .",
|
||||
"rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
|
||||
"lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"lint:fix": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"lint:styles": "npx stylelint **/*.tsx",
|
||||
"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": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never",
|
||||
"package:pr": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --win --mac --linux",
|
||||
"package:dev": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --dir",
|
||||
@@ -25,7 +26,7 @@
|
||||
"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/renderer/i18n/i18next-parser.config.js",
|
||||
"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\""
|
||||
},
|
||||
@@ -55,7 +56,7 @@
|
||||
"package.json"
|
||||
],
|
||||
"afterSign": ".erb/scripts/notarize.js",
|
||||
"electronVersion": "25.3.0",
|
||||
"electronVersion": "25.8.1",
|
||||
"mac": {
|
||||
"target": {
|
||||
"target": "default",
|
||||
@@ -64,6 +65,7 @@
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
"icon": "assets/icons/icon.icns",
|
||||
"type": "distribution",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "assets/entitlements.mac.plist",
|
||||
@@ -88,6 +90,40 @@
|
||||
"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": {
|
||||
@@ -95,7 +131,7 @@
|
||||
"AppImage",
|
||||
"tar.xz"
|
||||
],
|
||||
"icon": "assets/icons/placeholder.png",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"category": "Development"
|
||||
},
|
||||
"directories": {
|
||||
@@ -194,7 +230,7 @@
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"detect-port": "^1.3.0",
|
||||
"electron": "^25.3.0",
|
||||
"electron": "^25.8.1",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-notarize": "^1.2.1",
|
||||
@@ -216,12 +252,13 @@
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"husky": "^7.0.4",
|
||||
"i18next-parser": "^6.3.0",
|
||||
"i18next-parser": "^6.6.0",
|
||||
"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": "^2.6.2",
|
||||
"react-refresh": "^0.12.0",
|
||||
@@ -231,16 +268,19 @@
|
||||
"sass": "^1.49.11",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"stylelint": "^14.9.1",
|
||||
"stylelint": "^15.10.3",
|
||||
"stylelint-config-css-modules": "^4.3.0",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"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.7.0",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.8.4",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-plugin-styled-components": "^3.0.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.71.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
@@ -266,6 +306,7 @@
|
||||
"@tanstack/react-query-devtools": "^4.32.1",
|
||||
"@tanstack/react-query-persist-client": "^4.32.1",
|
||||
"@ts-rest/core": "^3.23.0",
|
||||
"@xhayper/discord-rpc": "^1.0.24",
|
||||
"axios": "^1.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
@@ -280,7 +321,7 @@
|
||||
"framer-motion": "^10.13.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^21.6.16",
|
||||
"i18next": "^21.10.0",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"immer": "^9.0.21",
|
||||
"is-electron": "^2.2.2",
|
||||
@@ -295,22 +336,22 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-i18next": "^11.16.7",
|
||||
"react-i18next": "^11.18.6",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-player": "^2.11.0",
|
||||
"react-router": "^6.5.0",
|
||||
"react-router-dom": "^6.5.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-simple-img": "^3.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.17",
|
||||
"react-window": "^1.8.9",
|
||||
"react-window-infinite-loader": "^1.0.9",
|
||||
"styled-components": "^5.3.11",
|
||||
"styled-components": "^6.0.8",
|
||||
"swiper": "^9.3.1",
|
||||
"zod": "^3.21.4",
|
||||
"zustand": "^4.3.9"
|
||||
},
|
||||
"resolutions": {
|
||||
"styled-components": "^5"
|
||||
"styled-components": "^6"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": ">=14.x",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "feishin",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.1",
|
||||
"description": "",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
const en = require('./locales/en.json');
|
||||
|
||||
const resources = {
|
||||
en: { translation: en },
|
||||
};
|
||||
|
||||
export const Languages = [
|
||||
{
|
||||
label: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
];
|
||||
|
||||
i18n
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
fallbackLng: 'en',
|
||||
// language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
|
||||
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
|
||||
// if you're using a language detector, do not define the lng option
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
|
||||
lng: 'en',
|
||||
|
||||
resources,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -0,0 +1,126 @@
|
||||
import { PostProcessorModule } from 'i18next';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import en from './locales/en.json';
|
||||
import es from './locales/es.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 it from './locales/it.json';
|
||||
import ru from './locales/ru.json';
|
||||
import ptBr from './locales/pt-BR.json';
|
||||
|
||||
const resources = {
|
||||
en: { translation: en },
|
||||
es: { translation: es },
|
||||
de: { translation: de },
|
||||
it: { translation: it },
|
||||
ru: { translation: ru },
|
||||
'pt-BR': { translation: ptBr },
|
||||
fr: { translation: fr },
|
||||
ja: { translation: ja },
|
||||
pl: { translation: pl },
|
||||
'zh-Hans': { translation: zhHans },
|
||||
};
|
||||
|
||||
export const languages = [
|
||||
{
|
||||
label: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
label: 'Español',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
label: 'Deutsch',
|
||||
value: 'de',
|
||||
},
|
||||
{
|
||||
label: 'Français',
|
||||
value: 'fr',
|
||||
},
|
||||
{
|
||||
label: 'Italiano',
|
||||
value: 'it',
|
||||
},
|
||||
{
|
||||
label: '日本語',
|
||||
value: 'ja',
|
||||
},
|
||||
{
|
||||
label: 'Русский',
|
||||
value: 'ru',
|
||||
},
|
||||
{
|
||||
label: 'Português (Brasil)',
|
||||
value: 'pt-BR',
|
||||
},
|
||||
{
|
||||
label: 'Polski',
|
||||
value: 'pl',
|
||||
},
|
||||
{
|
||||
label: '简体中文',
|
||||
value: 'zh-Hans',
|
||||
},
|
||||
];
|
||||
|
||||
const lowerCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'lowerCase',
|
||||
process: (value: string) => {
|
||||
return value.toLocaleLowerCase();
|
||||
},
|
||||
};
|
||||
|
||||
const upperCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'upperCase',
|
||||
process: (value: string) => {
|
||||
return value.toLocaleUpperCase();
|
||||
},
|
||||
};
|
||||
|
||||
const titleCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'titleCase',
|
||||
process: (value: string) => {
|
||||
return value.replace(/\w\S*/g, (txt) => {
|
||||
return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const sentenceCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'sentenceCase',
|
||||
process: (value: string) => {
|
||||
const sentences = value.split('. ');
|
||||
|
||||
return sentences
|
||||
.map((sentence) => {
|
||||
return sentence.charAt(0).toUpperCase() + sentence.slice(1).toLocaleLowerCase();
|
||||
})
|
||||
.join('. ');
|
||||
},
|
||||
};
|
||||
i18n.use(lowerCasePostProcessor)
|
||||
.use(upperCasePostProcessor)
|
||||
.use(titleCasePostProcessor)
|
||||
.use(sentenceCasePostProcessor)
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
fallbackLng: 'en',
|
||||
// language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
|
||||
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
|
||||
// if you're using a language detector, do not define the lng option
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
resources,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -1,117 +1,44 @@
|
||||
// i18next-parser.config.js
|
||||
// Reference: https://github.com/i18next/i18next-parser#options
|
||||
|
||||
module.exports = {
|
||||
contextSeparator: '_',
|
||||
// Key separator used in your translation keys
|
||||
|
||||
createOldCatalogs: true,
|
||||
|
||||
// Exit with an exit code of 1 when translations are updated (for CI purpose)
|
||||
customValueTemplate: null,
|
||||
|
||||
// Save the \_old files
|
||||
defaultNamespace: 'translation',
|
||||
|
||||
// Default namespace used in your i18next config
|
||||
defaultValue: '',
|
||||
|
||||
// Exit with an exit code of 1 on warnings
|
||||
failOnUpdate: false,
|
||||
|
||||
// Display info about the parsing including some stats
|
||||
failOnWarnings: false,
|
||||
|
||||
// The locale to compare with default values to determine whether a default value has been changed.
|
||||
// If this is set and a default value differs from a translation in the specified locale, all entries
|
||||
// for that key across locales are reset to the default value, and existing translations are moved to
|
||||
// the `_old` file.
|
||||
i18nextOptions: null,
|
||||
|
||||
// Default value to give to empty keys
|
||||
// You may also specify a function accepting the locale, namespace, and key as arguments
|
||||
indentation: 2,
|
||||
|
||||
// Plural separator used in your translation keys
|
||||
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
|
||||
input: [
|
||||
'../components/**/*.{js,jsx,ts,tsx}',
|
||||
'../features/**/*.{js,jsx,ts,tsx}',
|
||||
'../layouts/**/*.{js,jsx,ts,tsx}',
|
||||
'!../../src/node_modules/**',
|
||||
'!../../src/**/*.prod.js',
|
||||
],
|
||||
|
||||
// Indentation of the catalog files
|
||||
keepRemoved: false,
|
||||
|
||||
// Keep keys from the catalog that are no longer in code
|
||||
keySeparator: '.',
|
||||
|
||||
// Key separator used in your translation keys
|
||||
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||
// see below for more details
|
||||
lexers: {
|
||||
default: ['JavascriptLexer'],
|
||||
handlebars: ['HandlebarsLexer'],
|
||||
|
||||
hbs: ['HandlebarsLexer'],
|
||||
htm: ['HTMLLexer'],
|
||||
|
||||
html: ['HTMLLexer'],
|
||||
js: ['JavascriptLexer'],
|
||||
jsx: ['JsxLexer'],
|
||||
|
||||
mjs: ['JavascriptLexer'],
|
||||
// if you're writing jsx inside .js files, change this to JsxLexer
|
||||
ts: ['JavascriptLexer'],
|
||||
|
||||
tsx: ['JsxLexer'],
|
||||
},
|
||||
|
||||
lineEnding: 'auto',
|
||||
|
||||
// Control the line ending. See options at https://github.com/ryanve/eol
|
||||
locales: ['en'],
|
||||
|
||||
// An array of the locales in your applications
|
||||
namespaceSeparator: false,
|
||||
|
||||
// Namespace separator used in your translation keys
|
||||
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||
output: 'src/renderer/i18n/locales/$LOCALE.json',
|
||||
|
||||
// Supports $LOCALE and $NAMESPACE injection
|
||||
// Supports JSON (.json) and YAML (.yml) file formats
|
||||
// Where to write the locale files relative to process.cwd()
|
||||
pluralSeparator: '_',
|
||||
|
||||
// If you wish to customize the value output the value as an object, you can set your own format.
|
||||
// ${defaultValue} is the default value you set in your translation function.
|
||||
// Any other custom property will be automatically extracted.
|
||||
//
|
||||
// Example:
|
||||
// {
|
||||
// message: "${defaultValue}",
|
||||
// description: "${maxLength}", //
|
||||
// }
|
||||
resetDefaultValueLocale: 'en',
|
||||
|
||||
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
|
||||
skipDefaultValues: false,
|
||||
|
||||
// An array of globs that describe where to look for source files
|
||||
// relative to the location of the configuration file
|
||||
sort: true,
|
||||
|
||||
// Whether to ignore default values
|
||||
// You may also specify a function accepting the locale and namespace as arguments
|
||||
useKeysAsDefaultValue: true,
|
||||
|
||||
// Whether to use the keys as the default value; ex. "Hello": "Hello", "World": "World"
|
||||
// This option takes precedence over the `defaultValue` and `skipDefaultValues` options
|
||||
// You may also specify a function accepting the locale and namespace as arguments
|
||||
verbose: false,
|
||||
// If you wish to customize options in internally used i18next instance, you can define an object with any
|
||||
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
|
||||
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
|
||||
contextSeparator: '_',
|
||||
createOldCatalogs: true,
|
||||
customValueTemplate: null,
|
||||
defaultNamespace: 'translation',
|
||||
defaultValue: '',
|
||||
failOnUpdate: false,
|
||||
failOnWarnings: false,
|
||||
i18nextOptions: null,
|
||||
indentation: 4,
|
||||
input: [
|
||||
'../renderer/components/**/*.{js,jsx,ts,tsx}',
|
||||
'../renderer/features/**/*.{js,jsx,ts,tsx}',
|
||||
'../renderer/layouts/**/*.{js,jsx,ts,tsx}',
|
||||
'!../src/node_modules/**',
|
||||
'!../src/**/*.prod.js',
|
||||
],
|
||||
keepRemoved: false,
|
||||
keySeparator: '.',
|
||||
lexers: {
|
||||
default: ['JavascriptLexer'],
|
||||
handlebars: ['HandlebarsLexer'],
|
||||
hbs: ['HandlebarsLexer'],
|
||||
htm: ['HTMLLexer'],
|
||||
html: ['HTMLLexer'],
|
||||
js: ['JavascriptLexer'],
|
||||
jsx: ['JsxLexer'],
|
||||
mjs: ['JavascriptLexer'],
|
||||
ts: ['JavascriptLexer'],
|
||||
tsx: ['JsxLexer'],
|
||||
},
|
||||
lineEnding: 'auto',
|
||||
locales: ['en'],
|
||||
namespaceSeparator: false,
|
||||
output: 'src/renderer/i18n/locales/$LOCALE.json',
|
||||
pluralSeparator: '_',
|
||||
resetDefaultValueLocale: 'en',
|
||||
skipDefaultValues: false,
|
||||
sort: true,
|
||||
useKeysAsDefaultValue: true,
|
||||
verbose: false,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"action": {
|
||||
"editPlaylist": "bearbeite $t(entity.playlist_one)",
|
||||
"clearQueue": "warteschlange löschen",
|
||||
"addToFavorites": "hinzufügen zu $t(entity.favorite_other)",
|
||||
"addToPlaylist": "hinzufügen zu $t(entity.playlist_one)",
|
||||
"createPlaylist": "erstelle $t(entity.playlist_one)",
|
||||
"deletePlaylist": "lösche $t(entity.playlist_one)",
|
||||
"deselectAll": "alle abwählen"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,618 @@
|
||||
{
|
||||
"player": {
|
||||
"next": "player.next",
|
||||
"play": "player.play",
|
||||
"prev": "player.prev",
|
||||
"seekBack": "player.seekBack",
|
||||
"seekForward": "player.seekForward"
|
||||
}
|
||||
"action": {
|
||||
"addToFavorites": "add to $t(entity.favorite_other)",
|
||||
"addToPlaylist": "add to $t(entity.playlist_one)",
|
||||
"clearQueue": "clear queue",
|
||||
"createPlaylist": "create $t(entity.playlist_one)",
|
||||
"deletePlaylist": "delete $t(entity.playlist_one)",
|
||||
"deselectAll": "deselect all",
|
||||
"editPlaylist": "edit $t(entity.playlist_one)",
|
||||
"goToPage": "go to page",
|
||||
"moveToBottom": "move to bottom",
|
||||
"moveToTop": "move to top",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"removeFromFavorites": "remove from $t(entity.favorite_other)",
|
||||
"removeFromPlaylist": "remove from $t(entity.playlist_one)",
|
||||
"removeFromQueue": "remove from queue",
|
||||
"setRating": "set rating",
|
||||
"toggleSmartPlaylistEditor": "toggle $t(entity.smartPlaylist) editor",
|
||||
"viewPlaylists": "view $t(entity.playlist_other)"
|
||||
},
|
||||
"common": {
|
||||
"action_one": "action",
|
||||
"action_other": "actions",
|
||||
"add": "add",
|
||||
"areYouSure": "are you sure?",
|
||||
"ascending": "ascending",
|
||||
"backward": "backward",
|
||||
"biography": "biography",
|
||||
"bitrate": "bitrate",
|
||||
"bpm": "bpm",
|
||||
"cancel": "cancel",
|
||||
"center": "center",
|
||||
"channel_one": "channel",
|
||||
"channel_other": "channels",
|
||||
"clear": "clear",
|
||||
"collapse": "collapse",
|
||||
"comingSoon": "coming soon…",
|
||||
"configure": "configure",
|
||||
"confirm": "confirm",
|
||||
"create": "create",
|
||||
"currentSong": "current $t(entity.track_one)",
|
||||
"decrease": "decrease",
|
||||
"delete": "delete",
|
||||
"descending": "descending",
|
||||
"description": "description",
|
||||
"disable": "disable",
|
||||
"disc": "disc",
|
||||
"dismiss": "dismiss",
|
||||
"duration": "duration",
|
||||
"edit": "edit",
|
||||
"enable": "enable",
|
||||
"expand": "expand",
|
||||
"favorite": "favorite",
|
||||
"filter_one": "filter",
|
||||
"filter_other": "filters",
|
||||
"filters": "filters",
|
||||
"forceRestartRequired": "restart to apply changes… close the notification to restart",
|
||||
"forward": "forward",
|
||||
"gap": "gap",
|
||||
"home": "home",
|
||||
"increase": "increase",
|
||||
"left": "left",
|
||||
"limit": "limit",
|
||||
"manage": "manage",
|
||||
"maximize": "maximize",
|
||||
"menu": "menu",
|
||||
"minimize": "minimize",
|
||||
"modified": "modified",
|
||||
"name": "name",
|
||||
"no": "no",
|
||||
"none": "none",
|
||||
"noResultsFromQuery": "the query returned no results",
|
||||
"note": "note",
|
||||
"ok": "ok",
|
||||
"owner": "owner",
|
||||
"path": "path",
|
||||
"playerMustBePaused": "player must be paused",
|
||||
"previousSong": "previous $t(entity.track_one)",
|
||||
"quit": "quit",
|
||||
"random": "random",
|
||||
"rating": "rating",
|
||||
"refresh": "refresh",
|
||||
"reset": "reset",
|
||||
"resetToDefault": "reset to default",
|
||||
"restartRequired": "restart required",
|
||||
"right": "right",
|
||||
"save": "save",
|
||||
"saveAndReplace": "save and replace",
|
||||
"saveAs": "save as",
|
||||
"search": "search",
|
||||
"setting": "setting",
|
||||
"setting_one": "setting",
|
||||
"setting_other": "settings",
|
||||
"size": "size",
|
||||
"sortOrder": "order",
|
||||
"title": "title",
|
||||
"trackNumber": "track",
|
||||
"unknown": "unknown",
|
||||
"version": "version",
|
||||
"year": "year",
|
||||
"yes": "yes"
|
||||
},
|
||||
"entity": {
|
||||
"album_one": "album",
|
||||
"album_other": "albums",
|
||||
"albumArtist_one": "album artist",
|
||||
"albumArtist_other": "album artists",
|
||||
"albumArtistCount_one": "{{count}} album artist",
|
||||
"albumArtistCount_other": "{{count}} album artists",
|
||||
"albumWithCount_one": "{{count}} album",
|
||||
"albumWithCount_other": "{{count}} albums",
|
||||
"artist_one": "artist",
|
||||
"artist_other": "artists",
|
||||
"artistWithCount_one": "{{count}} artist",
|
||||
"artistWithCount_other": "{{count}} artists",
|
||||
"favorite_one": "favorite",
|
||||
"favorite_other": "favorites",
|
||||
"folder_one": "folder",
|
||||
"folder_other": "folders",
|
||||
"folderWithCount_one": "{{count}} folder",
|
||||
"folderWithCount_other": "{{count}} folders",
|
||||
"genre_one": "genre",
|
||||
"genre_other": "genres",
|
||||
"genreWithCount_one": "{{count}} genre",
|
||||
"genreWithCount_other": "{{count}} genres",
|
||||
"playlist_one": "playlist",
|
||||
"playlist_other": "playlists",
|
||||
"playlistWithCount_one": "{{count}} playlist",
|
||||
"playlistWithCount_other": "{{count}} playlists",
|
||||
"smartPlaylist": "smart $t(entity.playlist_one)",
|
||||
"track_one": "track",
|
||||
"track_other": "tracks",
|
||||
"trackWithCount_one": "{{count}} track",
|
||||
"trackWithCount_other": "{{count}} tracks"
|
||||
},
|
||||
"error": {
|
||||
"apiRouteError": "unable to route request",
|
||||
"audioDeviceFetchError": "an error occurred when trying to get audio devices",
|
||||
"authenticationFailed": "authentication failed",
|
||||
"credentialsRequired": "credentials required",
|
||||
"endpointNotImplementedError": "endpoint {{endpoint}} is not implemented for {{serverType}}",
|
||||
"genericError": "an error occurred",
|
||||
"invalidServer": "invalid server",
|
||||
"localFontAccessDenied": "access denied to local fonts",
|
||||
"loginRateError": "too many login attempts, please try again in a few seconds",
|
||||
"mpvRequired": "MPV required",
|
||||
"playbackError": "an error occurred when trying to play the media",
|
||||
"remoteDisableError": "an error occurred when trying to $t(common.disable) the remote server",
|
||||
"remoteEnableError": "an error occurred when trying to $t(common.enable) the remote server",
|
||||
"remotePortError": "an error occurred when trying to set the remote server port",
|
||||
"remotePortWarning": "restart the server to apply the new port",
|
||||
"serverNotSelectedError": "no server selected",
|
||||
"serverRequired": "server required",
|
||||
"sessionExpiredError": "your session has expired",
|
||||
"systemFontError": "an error occurred when trying to get system fonts"
|
||||
},
|
||||
"filter": {
|
||||
"album": "$t(entity.album_one)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"albumCount": "$t(entity.album_other) count",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"biography": "biography",
|
||||
"bitrate": "bitrate",
|
||||
"bpm": "bpm",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"comment": "comment",
|
||||
"communityRating": "community rating",
|
||||
"criticRating": "critic rating",
|
||||
"dateAdded": "date added",
|
||||
"disc": "disc",
|
||||
"duration": "duration",
|
||||
"favorited": "favorited",
|
||||
"fromYear": "from year",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"id": "id",
|
||||
"isCompilation": "is compilation",
|
||||
"isFavorited": "is favorited",
|
||||
"isPublic": "is public",
|
||||
"isRated": "is rated",
|
||||
"isRecentlyPlayed": "is recently played",
|
||||
"lastPlayed": "last played",
|
||||
"mostPlayed": "most played",
|
||||
"name": "name",
|
||||
"note": "note",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "path",
|
||||
"playCount": "play count",
|
||||
"random": "random",
|
||||
"rating": "rating",
|
||||
"recentlyAdded": "recently added",
|
||||
"recentlyPlayed": "recently played",
|
||||
"recentlyUpdated": "recently updated",
|
||||
"releaseDate": "release date",
|
||||
"releaseYear": "release year",
|
||||
"search": "search",
|
||||
"songCount": "song count",
|
||||
"title": "title",
|
||||
"toYear": "to year",
|
||||
"trackNumber": "track"
|
||||
},
|
||||
"form": {
|
||||
"addServer": {
|
||||
"error_savePassword": "an error occurred when trying to save the password",
|
||||
"ignoreCors": "ignore cors ($t(common.restartRequired))",
|
||||
"ignoreSsl": "ignore ssl ($t(common.restartRequired))",
|
||||
"input_legacyAuthentication": "enable legacy authentication",
|
||||
"input_name": "server name",
|
||||
"input_password": "password",
|
||||
"input_savePassword": "save password",
|
||||
"input_url": "url",
|
||||
"input_username": "username",
|
||||
"success": "server added successfully",
|
||||
"title": "add server"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"input_playlists": "$t(entity.playlist_other)",
|
||||
"input_skipDuplicates": "skip duplicates",
|
||||
"success": "added {{message}} $t(entity.song_other) to {{numOfPlaylists}} $t(entity.playlist_other)",
|
||||
"title": "add to $t(entity.playlist_one)"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
"input_name": "$t(common.name)",
|
||||
"input_owner": "$t(common.owner)",
|
||||
"input_public": "public",
|
||||
"success": "$t(entity.playlist_one) created successfully",
|
||||
"title": "create $t(entity.playlist_one)"
|
||||
},
|
||||
"deletePlaylist": {
|
||||
"input_confirm": "type the name of the $t(entity.playlist_one) to confirm",
|
||||
"success": "$t(entity.playlist_one) deleted successfully",
|
||||
"title": "delete $t(entity.playlist_one)"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "edit $t(entity.playlist_one)"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"input_artist": "$t(entity.artist_one)",
|
||||
"input_name": "$t(common.name)",
|
||||
"title": "lyric search"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "match all",
|
||||
"input_optionMatchAny": "match any"
|
||||
},
|
||||
"updateServer": {
|
||||
"success": "server updated successfully",
|
||||
"title": "update server"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "more from this $t(entity.genre_one)",
|
||||
"moreFromGeneric": "more from {{item}}"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)"
|
||||
},
|
||||
"appMenu": {
|
||||
"collapseSidebar": "collapse sidebar",
|
||||
"expandSidebar": "expand sidebar",
|
||||
"goBack": "go back",
|
||||
"goForward": "go forward",
|
||||
"manageServers": "manage servers",
|
||||
"openBrowserDevtools": "open browser devtools",
|
||||
"quit": "$t(common.quit)",
|
||||
"selectServer": "select server",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"version": "version {{version}}"
|
||||
},
|
||||
"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)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"numberSelected": "{{count}} selected",
|
||||
"play": "$t(player.play)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)",
|
||||
"setRating": "$t(action.setRating)"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"dynamicBackground": "dynamic background",
|
||||
"followCurrentLyric": "follow current lyric",
|
||||
"lyricAlignment": "lyric alignment",
|
||||
"lyricGap": "lyric gap",
|
||||
"lyricSize": "lyric size",
|
||||
"opacity": "opacity",
|
||||
"showLyricMatch": "show lyric match",
|
||||
"showLyricProvider": "show lyric provider",
|
||||
"synchronized": "synchronized",
|
||||
"unsynchronized": "unsynchronized",
|
||||
"useImageAspectRatio": "use image aspect ratio"
|
||||
},
|
||||
"lyrics": "lyrics",
|
||||
"related": "related",
|
||||
"upNext": "up next"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"goToPage": "go to page",
|
||||
"searchFor": "search for {{query}}",
|
||||
"serverCommands": "server commands"
|
||||
},
|
||||
"title": "commands"
|
||||
},
|
||||
"home": {
|
||||
"explore": "explore from your library",
|
||||
"mostPlayed": "most played",
|
||||
"newlyAdded": "newly added releases",
|
||||
"recentlyPlayed": "recently played",
|
||||
"title": "$t(common.home)"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
},
|
||||
"setting": {
|
||||
"generalTab": "general",
|
||||
"hotkeysTab": "hotkeys",
|
||||
"playbackTab": "playback",
|
||||
"windowTab": "window"
|
||||
},
|
||||
"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": "now playing",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"tracks": "$t(entity.track_other)"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"addLast": "add last",
|
||||
"addNext": "add next",
|
||||
"favorite": "favorite",
|
||||
"mute": "mute",
|
||||
"muted": "muted",
|
||||
"next": "next",
|
||||
"play": "play",
|
||||
"playbackFetchCancel": "this is taking a while… close the notification to cancel",
|
||||
"playbackFetchInProgress": "loading songs…",
|
||||
"playbackFetchNoResults": "no songs found",
|
||||
"playbackSpeed": "playback speed",
|
||||
"playRandom": "play random",
|
||||
"previous": "previous",
|
||||
"queue_clear": "clear queue",
|
||||
"queue_moveToBottom": "move selected to top",
|
||||
"queue_moveToTop": "move selected to bottom",
|
||||
"queue_remove": "remove selected",
|
||||
"repeat": "repeat",
|
||||
"repeat_all": "repeat all",
|
||||
"repeat_off": "repeat disabled",
|
||||
"repeat_one": "repeat one",
|
||||
"repeat_other": "",
|
||||
"shuffle": "shuffle",
|
||||
"shuffle_off": "shuffle disabled",
|
||||
"skip": "skip",
|
||||
"skip_back": "skip backwards",
|
||||
"skip_forward": "skip forwards",
|
||||
"stop": "stop",
|
||||
"toggleFullscreenPlayer": "toggle fullscreen player",
|
||||
"unfavorite": "unfavorite",
|
||||
"pause": "pause"
|
||||
},
|
||||
"setting": {
|
||||
"accentColor": "accent color",
|
||||
"accentColor_description": "sets the accent color for the application",
|
||||
"applicationHotkeys": "application hotkeys",
|
||||
"applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)",
|
||||
"audioDevice": "audio device",
|
||||
"audioDevice_description": "select the audio device to use for playback (web player only)",
|
||||
"audioExclusiveMode": "audio exclusive mode",
|
||||
"audioExclusiveMode_description": "enable exclusive output mode. In this mode, the system is usually locked out, and only mpv will be able to output audio",
|
||||
"audioPlayer": "audio player",
|
||||
"audioPlayer_description": "select the audio player to use for playback",
|
||||
"crossfadeDuration": "crossfade duration",
|
||||
"crossfadeDuration_description": "sets the duration of the crossfade effect",
|
||||
"crossfadeStyle": "crossfade style",
|
||||
"crossfadeStyle_description": "select the crossfade style to use for the audio player",
|
||||
"customFontPath": "custom font path",
|
||||
"customFontPath_description": "sets the path to the custom font to use for the application",
|
||||
"disableAutomaticUpdates": "disable automatic updates",
|
||||
"disableLibraryUpdateOnStartup": "disable checking for new versions on startup",
|
||||
"discordApplicationId": "{{discord}} application id",
|
||||
"discordApplicationId_description": "the application id for {{discord}} rich presence (defaults to {{defaultId}})",
|
||||
"discordIdleStatus": "show rich presence idle status",
|
||||
"discordIdleStatus_description": "when enabled, update status while player is idle",
|
||||
"discordRichPresence": "{{discord}} rich presence",
|
||||
"discordRichPresence_description": "enable playback status in {{discord}} rich presence. Image keys are: {{icon}}, {{playing}}, and {{paused}} ",
|
||||
"discordUpdateInterval": "{{discord}} rich presence update interval",
|
||||
"discordUpdateInterval_description": "the time in seconds between each update (minimum 15 seconds)",
|
||||
"enableRemote": "enable remote control server",
|
||||
"enableRemote_description": "enables the remote control server to allow other devices to control the application",
|
||||
"exitToTray": "exit to tray",
|
||||
"exitToTray_description": "exit the application to the system tray",
|
||||
"floatingQueueArea": "show floating queue hover area",
|
||||
"floatingQueueArea_description": "display a hover icon on the right side of the screen to view the play queue",
|
||||
"followLyric": "follow current lyric",
|
||||
"followLyric_description": "scroll the lyric to the current playing position",
|
||||
"font": "font",
|
||||
"font_description": "sets the font to use for the application",
|
||||
"fontType": "font type",
|
||||
"fontType_description": "built-in font selects one of the fonts provided by Feishin. system font allows you to select any font provided by your operating system. custom allows you to provide your own font",
|
||||
"fontType_optionBuiltIn": "built-in font",
|
||||
"fontType_optionCustom": "custom font",
|
||||
"fontType_optionSystem": "system font",
|
||||
"gaplessAudio": "gapless audio",
|
||||
"gaplessAudio_description": "sets the gapless audio setting for mpv",
|
||||
"gaplessAudio_optionWeak": "weak (recommended)",
|
||||
"globalMediaHotkeys": "global media hotkeys",
|
||||
"globalMediaHotkeys_description": "enable or disable the usage of your system media hotkeys to control playback",
|
||||
"hotkey_browserBack": "browser back",
|
||||
"hotkey_browserForward": "browser forward",
|
||||
"hotkey_favoriteCurrentSong": "favorite $t(common.currentSong)",
|
||||
"hotkey_favoritePreviousSong": "favorite $t(common.previousSong)",
|
||||
"hotkey_globalSearch": "global search",
|
||||
"hotkey_localSearch": "in-page search",
|
||||
"hotkey_playbackNext": "next track",
|
||||
"hotkey_playbackPause": "pause",
|
||||
"hotkey_playbackPlay": "play",
|
||||
"hotkey_playbackPlayPause": "play / pause",
|
||||
"hotkey_playbackPrevious": "previous track",
|
||||
"hotkey_playbackStop": "stop",
|
||||
"hotkey_rate0": "rating clear",
|
||||
"hotkey_rate1": "rating 1 star",
|
||||
"hotkey_rate2": "rating 2 stars",
|
||||
"hotkey_rate3": "rating 3 stars",
|
||||
"hotkey_rate4": "rating 4 stars",
|
||||
"hotkey_rate5": "rating 5 stars",
|
||||
"hotkey_skipBackward": "skip backward",
|
||||
"hotkey_skipForward": "skip forward",
|
||||
"hotkey_toggleCurrentSongFavorite": "toggle $t(common.currentSong) favorite",
|
||||
"hotkey_toggleFullScreenPlayer": "toggle full screen player",
|
||||
"hotkey_togglePreviousSongFavorite": "toggle $t(common.previousSong) favorite",
|
||||
"hotkey_toggleQueue": "toggle queue",
|
||||
"hotkey_toggleRepeat": "toggle repeat",
|
||||
"hotkey_toggleShuffle": "toggle shuffle",
|
||||
"hotkey_unfavoriteCurrentSong": "unfavorite $t(common.currentSong)",
|
||||
"hotkey_unfavoritePreviousSong": "unfavorite $t(common.previousSong)",
|
||||
"hotkey_volumeDown": "volume down",
|
||||
"hotkey_volumeMute": "volume mute",
|
||||
"hotkey_volumeUp": "volume up",
|
||||
"hotkey_zoomIn": "zoom in",
|
||||
"hotkey_zoomOut": "zoom out",
|
||||
"language": "language",
|
||||
"language_description": "sets the language for the application ($t(common.restartRequired))",
|
||||
"lyricFetch": "fetch lyrics from the internet",
|
||||
"lyricFetch_description": "fetch lyrics from various internet sources",
|
||||
"lyricFetchProvider": "providers to fetch lyrics from",
|
||||
"lyricFetchProvider_description": "select the providers to fetch lyrics from. the order of the providers is the order in which they will be queried",
|
||||
"lyricOffset": "lyric offset (ms)",
|
||||
"lyricOffset_description": "offset the lyric by the specified amount of milliseconds",
|
||||
"minimizeToTray": "minimize to tray",
|
||||
"minimizeToTray_description": "minimize the application to the system tray",
|
||||
"minimumScrobblePercentage": "minimum scrobble duration (percentage)",
|
||||
"minimumScrobblePercentage_description": "the minimum percentage of the song that must be played before it is scrobbled",
|
||||
"minimumScrobbleSeconds": "minimum scrobble (seconds)",
|
||||
"minimumScrobbleSeconds_description": "the minimum duration in seconds of the song that must be played before it is scrobbled",
|
||||
"mpvExecutablePath": "mpv executable path",
|
||||
"mpvExecutablePath_description": "sets the path to the mpv executable",
|
||||
"mpvExecutablePath_help": "one per line",
|
||||
"mpvExtraParameters": "mpv parameters",
|
||||
"playbackStyle": "playback style",
|
||||
"playbackStyle_description": "select the playback style to use for the audio player",
|
||||
"playbackStyle_optionCrossFade": "crossfade",
|
||||
"playbackStyle_optionNormal": "normal",
|
||||
"playButtonBehavior": "play button behavior",
|
||||
"playButtonBehavior_description": "sets the default behavior of the play button when adding songs to the queue",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"remotePassword": "remote control server password",
|
||||
"remotePassword_description": "sets the password for the remote control server. These credentials are by default transferred insecurely, so you should use a unique password that you do not care about",
|
||||
"remotePort": "remote control server port",
|
||||
"remotePort_description": "sets the port for the remote control server",
|
||||
"remoteUsername": "remote control server username",
|
||||
"remoteUsername_description": "sets the username for the remote control server. if both username and password are empty, authentication will be disabled",
|
||||
"replayGainClipping": "{{ReplayGain}} clipping",
|
||||
"replayGainClipping_description": "Prevent clipping caused by {{ReplayGain}} by automatically lowering the gain",
|
||||
"replayGainFallback": "{{ReplayGain}} fallback",
|
||||
"replayGainFallback_description": "gain in db to apply if the file has no {{ReplayGain}} tags",
|
||||
"replayGainMode": "{{ReplayGain}} mode",
|
||||
"replayGainMode_description": "adjust volume gain according to {{ReplayGain}} values stored in the file metadata",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"replayGainPreamp": "{{ReplayGain}} preamp (dB)",
|
||||
"replayGainPreamp_description": "adjust the preamp gain applied to the {{ReplayGain}} values",
|
||||
"sampleRate": "sample rate",
|
||||
"sampleRate_description": "select the output sample rate to be used if the sample frequency selected is different from that of the current media",
|
||||
"savePlayQueue": "save play queue",
|
||||
"savePlayQueue_description": "save the play queue when the application is closed and restore it when the application is opened",
|
||||
"scrobble": "scrobble",
|
||||
"scrobble_description": "scrobble plays to your media server",
|
||||
"showSkipButton": "show skip buttons",
|
||||
"showSkipButton_description": "show or hide the skip buttons on the player bar",
|
||||
"showSkipButtons": "show skip buttons",
|
||||
"showSkipButtons_description": "show or hide the skip buttons on the player bar",
|
||||
"sidebarCollapsedNavigation": "sidebar (collapsed) navigation",
|
||||
"sidebarCollapsedNavigation_description": "show or hide the navigation in the collapsed sidebar",
|
||||
"sidebarConfiguration": "sidebar configuration",
|
||||
"sidebarConfiguration_description": "select the items and order in which they appear in the sidebar",
|
||||
"sidebarPlaylistList": "sidebar playlist list",
|
||||
"sidebarPlaylistList_description": "show or hide the playlist list in the sidebar",
|
||||
"sidePlayQueueStyle": "side play queue style",
|
||||
"sidePlayQueueStyle_description": "sets the style of the side play queue",
|
||||
"sidePlayQueueStyle_optionAttached": "attached",
|
||||
"sidePlayQueueStyle_optionDetached": "detached",
|
||||
"skipDuration": "skip duration",
|
||||
"skipDuration_description": "sets the duration to skip when using the skip buttons on the player bar",
|
||||
"skipPlaylistPage": "skip playlist page",
|
||||
"skipPlaylistPage_description": "when navigating to a playlist, go to the playlist song list page instead of the default page",
|
||||
"theme": "theme",
|
||||
"theme_description": "sets the theme to use for the application",
|
||||
"themeDark": "theme (dark)",
|
||||
"themeDark_description": "sets the dark theme to use for the application",
|
||||
"themeLight": "theme (light)",
|
||||
"themeLight_description": "sets the light theme to use for the application",
|
||||
"useSystemTheme": "use system theme",
|
||||
"useSystemTheme_description": "follow the system-defined light or dark preference",
|
||||
"volumeWheelStep": "volume wheel step",
|
||||
"volumeWheelStep_description": "the amount of volume to change when scrolling the mouse wheel on the volume slider",
|
||||
"windowBarStyle": "window bar style",
|
||||
"windowBarStyle_description": "select the style of the window bar",
|
||||
"zoom": "zoom percentage",
|
||||
"zoom_description": "sets the zoom percentage for the application"
|
||||
},
|
||||
"table": {
|
||||
"column": {
|
||||
"album": "album",
|
||||
"albumArtist": "album artist",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"biography": "biography",
|
||||
"bitrate": "bitrate",
|
||||
"bpm": "bpm",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"comment": "comment",
|
||||
"dateAdded": "date added",
|
||||
"discNumber": "disc",
|
||||
"favorite": "favorite",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"lastPlayed": "last played",
|
||||
"path": "path",
|
||||
"playCount": "plays",
|
||||
"rating": "rating",
|
||||
"releaseDate": "release date",
|
||||
"releaseYear": "year",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"title": "title",
|
||||
"trackNumber": "track"
|
||||
},
|
||||
"config": {
|
||||
"general": {
|
||||
"autoFitColumns": "auto fit columns",
|
||||
"displayType": "display type",
|
||||
"gap": "$t(common.gap)",
|
||||
"size": "$t(common.size)",
|
||||
"tableColumns": "table columns"
|
||||
},
|
||||
"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)",
|
||||
"dateAdded": "date added",
|
||||
"discNumber": "disc number",
|
||||
"duration": "$t(common.duration)",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"lastPlayed": "last played",
|
||||
"note": "$t(common.note)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"playCount": "play count",
|
||||
"rating": "$t(common.rating)",
|
||||
"releaseDate": "release date",
|
||||
"rowIndex": "row index",
|
||||
"size": "$t(common.size)",
|
||||
"title": "$t(common.title)",
|
||||
"titleCombined": "$t(common.title) (combined)",
|
||||
"trackNumber": "track number",
|
||||
"year": "$t(common.year)"
|
||||
},
|
||||
"view": {
|
||||
"card": "card",
|
||||
"poster": "poster",
|
||||
"table": "table"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,632 @@
|
||||
{
|
||||
"player": {
|
||||
"repeat_all": "repetir todo",
|
||||
"stop": "detener",
|
||||
"repeat": "repetir",
|
||||
"queue_remove": "eliminar seleccionado",
|
||||
"playRandom": "reproducción aleatoria",
|
||||
"skip": "saltar",
|
||||
"previous": "anterior",
|
||||
"toggleFullscreenPlayer": "activar el reproductor a pantalla completa",
|
||||
"skip_back": "saltar hacia atrás",
|
||||
"favorite": "favorito",
|
||||
"next": "siguiente",
|
||||
"shuffle": "mezclar",
|
||||
"playbackFetchNoResults": "ninguna canción encontrada",
|
||||
"playbackFetchInProgress": "cargando canciones…",
|
||||
"addNext": "añadir siguiente",
|
||||
"playbackSpeed": "velocidad de reproducción",
|
||||
"playbackFetchCancel": "esto está tomando un tiempo... cierra la notificación para cancelar",
|
||||
"play": "reproducir",
|
||||
"repeat_off": "repetir desactivado",
|
||||
"queue_clear": "limpiar cola",
|
||||
"muted": "silenciado",
|
||||
"unfavorite": "no favorito",
|
||||
"queue_moveToTop": "mover seleccionado al fondo",
|
||||
"queue_moveToBottom": "mover seleccionado al principio",
|
||||
"shuffle_off": "mezclar desactivado",
|
||||
"addLast": "añadir último",
|
||||
"mute": "silencio",
|
||||
"skip_forward": "saltar hacia delante",
|
||||
"pause": "pausa"
|
||||
},
|
||||
"setting": {
|
||||
"crossfadeStyle_description": "selecciona el estilo de crossfade a usar por el reproductor de audio",
|
||||
"remotePort_description": "establece el puerto para el control remoto del servidor",
|
||||
"hotkey_skipBackward": "saltar hacia atrás",
|
||||
"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)",
|
||||
"theme_description": "establece el tema a usar para la aplicación",
|
||||
"hotkey_playbackPause": "pausa",
|
||||
"replayGainFallback": "{{ReplayGain}} alternativa",
|
||||
"sidebarCollapsedNavigation_description": "mostrar u ocultar la navegación en la barra lateral contraída",
|
||||
"mpvExecutablePath_help": "uno por línea",
|
||||
"hotkey_volumeUp": "subir volumen",
|
||||
"skipDuration": "duración de salto",
|
||||
"discordIdleStatus_description": "cuando se activa, actualiza el estado mientras el reproductor está inactivo",
|
||||
"showSkipButtons": "mostrar botones de saltar",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"minimumScrobblePercentage": "mínima duración de scrobble (porcentaje)",
|
||||
"lyricFetch": "busca letras en Internet",
|
||||
"scrobble": "scrobble",
|
||||
"skipDuration_description": "establece la duración a saltar cuando se usa los botones de saltar en la barra del reproductor",
|
||||
"enableRemote_description": "activa el control remoto del servidor para permitir a otros dispositivos controlar la aplicación",
|
||||
"fontType_optionSystem": "fuente del sistema",
|
||||
"mpvExecutablePath_description": "establece la ruta del ejecutable mpv",
|
||||
"replayGainClipping_description": "previene el recorte causado por {{ReplayGain}} bajando automáticamente la ganancia",
|
||||
"replayGainPreamp": "preamplificador (dB) de {{ReplayGain}}",
|
||||
"hotkey_favoriteCurrentSong": "$t(common.currentSong) favorita",
|
||||
"sampleRate": "ratio de muestreo",
|
||||
"crossfadeStyle": "estilo de crossfade",
|
||||
"sidePlayQueueStyle_optionAttached": "acoplada",
|
||||
"sidebarConfiguration": "configuración de la barra lateral",
|
||||
"sampleRate_description": "selecciona el ratio de muestreo de salida a ser usado si la frecuencia de muestreo seleccionada es diferente de la del medio actual",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"replayGainClipping": "recortar {{ReplayGain}}",
|
||||
"hotkey_zoomIn": "ampliar",
|
||||
"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)",
|
||||
"fontType_optionBuiltIn": "fuente incorporada",
|
||||
"hotkey_playbackPlayPause": "play / pausa",
|
||||
"hotkey_rate1": "calificar con 1 estrella",
|
||||
"hotkey_skipForward": "saltar hacia delante",
|
||||
"disableLibraryUpdateOnStartup": "desactiva la comprobación de nuevas versiones al inicio",
|
||||
"discordApplicationId_description": "el id de aplicación para el estado de actividad de {{discord}} (por defecto es {{defaultId}})",
|
||||
"sidePlayQueueStyle": "estilo de la cola de reproducción lateral",
|
||||
"gaplessAudio": "audio sin pausas",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"minimizeToTray_description": "minimiza la aplicación a la bandeja del sistema",
|
||||
"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",
|
||||
"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",
|
||||
"remoteUsername_description": "establece el nombre de usuario para el control remoto del servidor. si el usuario y la contraseña están vacíos, la autenticación será deshabilitada",
|
||||
"disableAutomaticUpdates": "desactiva las actualizaciones automáticas",
|
||||
"exitToTray_description": "sale de la aplicación a la bandeja del sistema",
|
||||
"followLyric_description": "desplaza la letra a la posición de reproducción actual",
|
||||
"hotkey_favoritePreviousSong": "$t(common.previousSong) favorita",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"lyricOffset": "desfase de letra (ms)",
|
||||
"discordUpdateInterval_description": "el tiempo en segundos entre cada actualización (mínimo 15 segundos)",
|
||||
"fontType_optionCustom": "fuente personalizada",
|
||||
"themeDark_description": "establece el tema oscuro a usar para la aplicación",
|
||||
"audioExclusiveMode": "modo de audio exclusivo",
|
||||
"remotePassword": "contraseña del control remoto del servidor",
|
||||
"lyricFetchProvider": "proveedores para buscar letras",
|
||||
"language_description": "establece el idioma para la aplicación ($t(common.restartRequired))",
|
||||
"playbackStyle_optionCrossFade": "crossfade",
|
||||
"hotkey_rate3": "calificar con 3 estrellas",
|
||||
"font": "fuente",
|
||||
"mpvExtraParameters": "parámetros de mpv",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"themeLight_description": "establece el tema luminoso a usar para 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",
|
||||
"hotkey_rate5": "calificar con 5 estrellas",
|
||||
"hotkey_playbackPrevious": "pista anterior",
|
||||
"showSkipButtons_description": "muestra o esconde los botones de saltar en la barra del reproductor",
|
||||
"crossfadeDuration_description": "establece la duración del efecto de crossfade",
|
||||
"language": "idioma",
|
||||
"playbackStyle": "estilo de reproducción",
|
||||
"hotkey_toggleShuffle": "alterna aleatorio",
|
||||
"theme": "tema",
|
||||
"playbackStyle_description": "selecciona el estilo de reproducción a usar por el reproductor de audio",
|
||||
"discordRichPresence_description": "activa el estado de reproducción en el estado de actividad de {{discord}}. Las teclas de imagen son: {{icon}}, {{playing}}, y {{paused}} ",
|
||||
"mpvExecutablePath": "ruta del ejecutable mpv",
|
||||
"audioDevice": "dispositivo de audio",
|
||||
"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",
|
||||
"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",
|
||||
"savePlayQueue": "guardar cola de reproducción",
|
||||
"minimumScrobbleSeconds_description": "la duración mínima en segundos de la canción que debe ser reproducida antes de hacer scrobble",
|
||||
"fontType_description": "fuente incorporada selecciona una de las fuentes proporcionadas por Feishin. fuente del sistema te permite seleccionar cualquier fuente proporcionada por tu sistema operativo. personalizada te permite proporcionar tu propia fuente",
|
||||
"playButtonBehavior": "comportamiento del botón de reproducción",
|
||||
"sidebarPlaylistList_description": "muestra o esconde las listas de reproducción en la barra lateral",
|
||||
"sidePlayQueueStyle_description": "establece el estilo de la cola de reproducción lateral",
|
||||
"replayGainMode": "modo de {{ReplayGain}}",
|
||||
"playbackStyle_optionNormal": "normal",
|
||||
"floatingQueueArea": "mostrar área flotante de cola",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"globalMediaHotkeys_description": "activa o desactiva el uso de las teclas de acceso rápidas del sistema a medios para controlar la reproducción",
|
||||
"customFontPath": "ruta de fuente personalizada",
|
||||
"followLyric": "seguir la letra actual",
|
||||
"crossfadeDuration": "duración del crossfade",
|
||||
"discordIdleStatus": "mostrar el estado inactivo en el estado de actividad",
|
||||
"sidePlayQueueStyle_optionDetached": "separada",
|
||||
"audioPlayer": "reproductor de audio",
|
||||
"hotkey_zoomOut": "reducir",
|
||||
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) no favorito",
|
||||
"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",
|
||||
"remoteUsername": "nombre de usuario del control remoto del servidor",
|
||||
"showSkipButton": "mostrar botones de saltar",
|
||||
"sidebarPlaylistList": "listas de reproducción de la barra lateral",
|
||||
"minimizeToTray": "minimizar a la bandeja",
|
||||
"themeDark": "tema (oscuro)",
|
||||
"sidebarCollapsedNavigation": "navegación de barra lateral (contraída)",
|
||||
"customFontPath_description": "establece la ruta de la fuente personalizada a usar por la aplicación",
|
||||
"gaplessAudio_optionWeak": "débil (recomendado)",
|
||||
"minimumScrobbleSeconds": "mínimo scrobble (segundos)",
|
||||
"hotkey_playbackStop": "parar",
|
||||
"discordRichPresence": "estado de actividad de {{discord}}",
|
||||
"font_description": "establece la fuente a usar por la aplicación",
|
||||
"savePlayQueue_description": "guarda la cola de reproducción cuando la aplicación es cerrada y la restaura cuando la aplicación es abierta",
|
||||
"useSystemTheme": "usar tema del sistema",
|
||||
"volumeWheelStep_description": "la cantidad de volumen a cambiar cuando se desplaza la rueda del ratón en el control deslizante del volumen",
|
||||
"zoom": "porcentaje de zoom",
|
||||
"zoom_description": "establece el porcentaje de zoom para la aplicación",
|
||||
"volumeWheelStep": "paso de rueda del volumen",
|
||||
"windowBarStyle": "estilo de la barra de ventana",
|
||||
"windowBarStyle_description": "selecciona el estilo de la barra de ventana",
|
||||
"skipPlaylistPage_description": "cuando se navega a una lista de reproducción, se va a la página de lista de canciones de la lista de reproducción en lugar de a la página por defecto",
|
||||
"accentColor": "color de realce",
|
||||
"accentColor_description": "establece el color de realce para la aplicación",
|
||||
"skipPlaylistPage": "saltar página de lista de reproducción",
|
||||
"hotkey_browserForward": "avance",
|
||||
"hotkey_browserBack": "retroceso"
|
||||
},
|
||||
"action": {
|
||||
"editPlaylist": "editar $t(entity.playlist_one)",
|
||||
"goToPage": "ir a la página",
|
||||
"moveToTop": "mover al principio",
|
||||
"clearQueue": "limpiar cola",
|
||||
"addToFavorites": "añadir a $t(entity.favorite_other)",
|
||||
"addToPlaylist": "añadir a $t(entity.playlist_one)",
|
||||
"createPlaylist": "crear $t(entity.playlist_one)",
|
||||
"removeFromPlaylist": "eliminar de $t(entity.playlist_one)",
|
||||
"viewPlaylists": "ver $t(entity.playlist_other)",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"deletePlaylist": "eliminar $t(entity.playlist_one)",
|
||||
"removeFromQueue": "eliminar de la cola",
|
||||
"deselectAll": "desmarcar todo",
|
||||
"moveToBottom": "mover al fondo",
|
||||
"setRating": "establecer calificación",
|
||||
"toggleSmartPlaylistEditor": "cambiar editor $t(entity.smartPlaylist)",
|
||||
"removeFromFavorites": "eliminar de $t(entity.favorite_other)"
|
||||
},
|
||||
"common": {
|
||||
"backward": "hacia atrás",
|
||||
"increase": "aumentar",
|
||||
"rating": "calificación",
|
||||
"bpm": "bpm",
|
||||
"refresh": "actualizar",
|
||||
"unknown": "desconocido",
|
||||
"areYouSure": "estás seguro?",
|
||||
"edit": "editar",
|
||||
"favorite": "favorito",
|
||||
"left": "izquierda",
|
||||
"save": "guardar",
|
||||
"right": "derecha",
|
||||
"currentSong": "actual $t(entity.track_one)",
|
||||
"collapse": "contraer",
|
||||
"trackNumber": "pista",
|
||||
"descending": "descendiente",
|
||||
"add": "añadir",
|
||||
"ascending": "ascendente",
|
||||
"dismiss": "descartar",
|
||||
"year": "año",
|
||||
"manage": "gestionar",
|
||||
"limit": "limitar",
|
||||
"minimize": "minimizar",
|
||||
"modified": "modificado",
|
||||
"duration": "duración",
|
||||
"name": "nombre",
|
||||
"maximize": "maximizar",
|
||||
"decrease": "reducir",
|
||||
"ok": "vale",
|
||||
"description": "descripción",
|
||||
"configure": "configurar",
|
||||
"path": "ruta",
|
||||
"center": "centrar",
|
||||
"no": "no",
|
||||
"owner": "propietario",
|
||||
"enable": "activar",
|
||||
"clear": "limpiar",
|
||||
"forward": "hacia delante",
|
||||
"delete": "eliminar",
|
||||
"cancel": "cancelar",
|
||||
"forceRestartRequired": "reiniciar para aplicar cambios... cerrar la notificación para reiniciar",
|
||||
"setting": "configuración",
|
||||
"version": "versión",
|
||||
"title": "título",
|
||||
"filters": "filtros",
|
||||
"create": "crear",
|
||||
"bitrate": "tasa de bits",
|
||||
"saveAndReplace": "guardar y reemplazar",
|
||||
"playerMustBePaused": "el reproductor debe pausarse",
|
||||
"confirm": "confirmar",
|
||||
"resetToDefault": "restablecer a valor por defecto",
|
||||
"home": "inicio",
|
||||
"comingSoon": "próximamente…",
|
||||
"reset": "restablecer",
|
||||
"disable": "desactivar",
|
||||
"sortOrder": "ordenar",
|
||||
"none": "ninguno",
|
||||
"menu": "menú",
|
||||
"restartRequired": "reinicio requerido",
|
||||
"previousSong": "anterior $t(entity.track_one)",
|
||||
"noResultsFromQuery": "la petición no devolvió resultados",
|
||||
"quit": "salir",
|
||||
"expand": "ampliar",
|
||||
"search": "buscar",
|
||||
"saveAs": "guardar como",
|
||||
"disc": "disco",
|
||||
"yes": "sí",
|
||||
"random": "aleatorio",
|
||||
"size": "tamaño",
|
||||
"biography": "biografía",
|
||||
"note": "nota",
|
||||
"gap": "desfase",
|
||||
"filter_one": "filtro",
|
||||
"filter_many": "filtros",
|
||||
"filter_other": "filtros",
|
||||
"action_one": "acción",
|
||||
"action_many": "acciones",
|
||||
"action_other": "acciones",
|
||||
"channel_one": "Canal",
|
||||
"channel_many": "Canales",
|
||||
"channel_other": "Canales"
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "reiniciar el servidor para aplicar el nuevo puerto",
|
||||
"systemFontError": "un error ocurrió cuando se intentó obtener las fuentes del sistema",
|
||||
"playbackError": "un error ocurrió cuando se intentó reproducir el medio",
|
||||
"endpointNotImplementedError": "el punto final {{endpoint}} no está implementado para {{serverType}}",
|
||||
"remotePortError": "un error ocurrió cuando se intentó establecer el puerto del servidor remoto",
|
||||
"serverRequired": "servidor requerido",
|
||||
"authenticationFailed": "autenticación fallida",
|
||||
"apiRouteError": "no se puede encaminar la solicitud",
|
||||
"genericError": "sucedió un error",
|
||||
"credentialsRequired": "credenciales requeridas",
|
||||
"sessionExpiredError": "tu sesión ha expirado",
|
||||
"remoteEnableError": "un error ocurrió cuando se intentó $t(common.enable) el servidor remoto",
|
||||
"localFontAccessDenied": "acceso denegado a las fuentes locales",
|
||||
"serverNotSelectedError": "ningún servidor seleccionado",
|
||||
"remoteDisableError": "un error ocurrió cuando se intentó $t(common.disable) el servidor remoto",
|
||||
"mpvRequired": "MPV requerido",
|
||||
"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"
|
||||
},
|
||||
"filter": {
|
||||
"mostPlayed": "más reproducido",
|
||||
"isCompilation": "es compilación",
|
||||
"recentlyPlayed": "recientemente reproducido",
|
||||
"isRated": "es clasificado",
|
||||
"title": "título",
|
||||
"rating": "calificación",
|
||||
"search": "buscar",
|
||||
"bitrate": "tasa de bits",
|
||||
"recentlyAdded": "recientemente añadido",
|
||||
"note": "nota",
|
||||
"name": "nombre",
|
||||
"dateAdded": "fecha añadida",
|
||||
"releaseDate": "fecha de lanzamiento",
|
||||
"communityRating": "calificación de la comunidad",
|
||||
"path": "ruta",
|
||||
"favorited": "favoritos",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"isRecentlyPlayed": "reproducido recientemente",
|
||||
"isFavorited": "es favorito",
|
||||
"bpm": "bpm",
|
||||
"releaseYear": "año de lanzamiento",
|
||||
"disc": "disco",
|
||||
"biography": "biografía",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"duration": "duración",
|
||||
"random": "aleatorio",
|
||||
"lastPlayed": "última reproducción",
|
||||
"toYear": "hasta el año",
|
||||
"fromYear": "desde el año",
|
||||
"criticRating": "calificación de la crítica",
|
||||
"trackNumber": "pista",
|
||||
"comment": "comentarios",
|
||||
"playCount": "número de reproducción",
|
||||
"recentlyUpdated": "actualizado recientemente",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"owner": "$t(common.owner)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"id": "id",
|
||||
"songCount": "número de canción",
|
||||
"isPublic": "es público",
|
||||
"album": "$t(entity.album_one)",
|
||||
"albumCount": "Contar $t(entity.album_other)"
|
||||
},
|
||||
"page": {
|
||||
"sidebar": {
|
||||
"nowPlaying": "en reproducción",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"tracks": "$t(entity.track_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"home": "$t(common.home)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"albumArtists": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"appMenu": {
|
||||
"selectServer": "seleccionar servidor",
|
||||
"version": "versión {{version}}",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"manageServers": "gestionar servidores",
|
||||
"expandSidebar": "ampliar barra lateral",
|
||||
"collapseSidebar": "contraer barra lateral",
|
||||
"openBrowserDevtools": "abrir herramientas de desarrollador del navegador",
|
||||
"quit": "$t(common.quit)",
|
||||
"goBack": "retroceder",
|
||||
"goForward": "avanzar"
|
||||
},
|
||||
"contextMenu": {
|
||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||
"addToFavorites": "$t(action.addToFavorites)",
|
||||
"setRating": "$t(action.setRating)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"createPlaylist": "$t(action.createPlaylist)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"addNext": "$t(player.addNext)",
|
||||
"deselectAll": "$t(action.deselectAll)",
|
||||
"addLast": "$t(player.addLast)",
|
||||
"addFavorite": "$t(action.addToFavorites)",
|
||||
"play": "$t(player.play)",
|
||||
"numberSelected": "{{count}} seleccionado",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "más reproducidos",
|
||||
"newlyAdded": "nuevos lanzamientos añadidos",
|
||||
"title": "$t(common.home)",
|
||||
"explore": "explorar desde tu biblioteca",
|
||||
"recentlyPlayed": "recientemente reproducidos"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"upNext": "siguiente",
|
||||
"config": {
|
||||
"dynamicBackground": "fondo dinámico",
|
||||
"synchronized": "sincronizado",
|
||||
"followCurrentLyric": "seguir la letra actual",
|
||||
"opacity": "opacidad",
|
||||
"lyricSize": "tamaño de letra",
|
||||
"showLyricProvider": "mostrar proveedor de letra",
|
||||
"unsynchronized": "no sincronizado",
|
||||
"lyricAlignment": "alineación de letra",
|
||||
"useImageAspectRatio": "usar ratio de aspecto de imagen",
|
||||
"showLyricMatch": "mostrar coincidencia de letras",
|
||||
"lyricGap": "desfase de letra"
|
||||
},
|
||||
"lyrics": "letras",
|
||||
"related": "relacionado"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "más de este $t(entity.genre_one)",
|
||||
"moreFromGeneric": "más de {{item}}"
|
||||
},
|
||||
"setting": {
|
||||
"playbackTab": "reproducción",
|
||||
"generalTab": "general",
|
||||
"hotkeysTab": "teclas de acceso rápido",
|
||||
"windowTab": "ventana"
|
||||
},
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"serverCommands": "comandos del servidor",
|
||||
"goToPage": "ir a la página",
|
||||
"searchFor": "buscar por {{query}}"
|
||||
},
|
||||
"title": "comandos"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
"title": "eliminar $t(entity.playlist_one)",
|
||||
"success": "$t(entity.playlist_one) eliminado correctamente",
|
||||
"input_confirm": "escribe el nombre de $t(entity.playlist_one) para confirmar"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
"title": "crear $t(entity.playlist_one)",
|
||||
"input_public": "público",
|
||||
"input_name": "$t(common.name)",
|
||||
"success": "$t(entity.playlist_one) creado correctamente",
|
||||
"input_owner": "$t(common.owner)"
|
||||
},
|
||||
"addServer": {
|
||||
"title": "añadir servidor",
|
||||
"input_username": "nombre de usuario",
|
||||
"input_url": "url",
|
||||
"input_password": "contraseña",
|
||||
"input_legacyAuthentication": "permitir autenticación heredada",
|
||||
"input_name": "nombre del servidor",
|
||||
"success": "servidor añadido correctamente",
|
||||
"input_savePassword": "guardar contraseña",
|
||||
"ignoreSsl": "ignorar ssl ($t(common.restartRequired))",
|
||||
"ignoreCors": "ignorar cors ($t(common.restartRequired))",
|
||||
"error_savePassword": "un error ocurrió cuando se intentó guardar la contraseña"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"success": "añadido {{message}} $t(entity.song_other) a {{numOfPlaylists}} $t(entity.playlist_other)",
|
||||
"title": "añadir a $t(entity.playlist_one)",
|
||||
"input_skipDuplicates": "saltar duplicados",
|
||||
"input_playlists": "$t(entity.playlist_other)"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "actualizar servidor",
|
||||
"success": "servidor actualizado correctamente"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"input_name": "$t(common.name)",
|
||||
"input_artist": "$t(entity.artist_one)",
|
||||
"title": "buscar letras"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "editar $t(entity.playlist_one)"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "coincidir todos",
|
||||
"input_optionMatchAny": "coincidir cualquiera"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"column": {
|
||||
"rating": "calificación",
|
||||
"comment": "comentarios",
|
||||
"album": "álbum",
|
||||
"favorite": "favorito",
|
||||
"playCount": "reproducciones",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"releaseYear": "año",
|
||||
"lastPlayed": "última reproducción",
|
||||
"biography": "biografía",
|
||||
"releaseDate": "fecha de lanzamiento",
|
||||
"bitrate": "tasa de bits",
|
||||
"title": "título",
|
||||
"bpm": "bpm",
|
||||
"dateAdded": "fecha de adición",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"trackNumber": "pista",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"albumArtist": "artista de álbum",
|
||||
"path": "ruta",
|
||||
"discNumber": "disco",
|
||||
"channels": "$t(common.channel_other)"
|
||||
},
|
||||
"config": {
|
||||
"label": {
|
||||
"rating": "$t(common.rating)",
|
||||
"dateAdded": "fecha de adición",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"lastPlayed": "última reproducción",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"biography": "$t(common.biography)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"discNumber": "número de disco",
|
||||
"releaseDate": "fecha de lanzamiento",
|
||||
"title": "$t(common.title)",
|
||||
"duration": "$t(common.duration)",
|
||||
"titleCombined": "$t(common.title) (combinado)",
|
||||
"size": "$t(common.size)",
|
||||
"trackNumber": "número de pista",
|
||||
"rowIndex": "índice de filas",
|
||||
"note": "$t(common.note)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"playCount": "número de reproducción",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"year": "$t(common.year)"
|
||||
},
|
||||
"general": {
|
||||
"gap": "$t(common.gap)",
|
||||
"tableColumns": "columnas de la tabla",
|
||||
"autoFitColumns": "ajuste automático de columnas",
|
||||
"size": "$t(common.size)",
|
||||
"displayType": "tipo de visualización"
|
||||
},
|
||||
"view": {
|
||||
"card": "tarjeta",
|
||||
"table": "tabla",
|
||||
"poster": "cartel"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"smartPlaylist": "$t(entity.playlist_one) inteligente",
|
||||
"genre_one": "género",
|
||||
"genre_many": "géneros",
|
||||
"genre_other": "géneros",
|
||||
"playlistWithCount_one": "{{count}} lista de reproducción",
|
||||
"playlistWithCount_many": "{{count}} listas de reproducción",
|
||||
"playlistWithCount_other": "{{count}} listas de reproducción",
|
||||
"playlist_one": "lista de reproducción",
|
||||
"playlist_many": "listas de reproducción",
|
||||
"playlist_other": "listas de reproducción",
|
||||
"artist_one": "artista",
|
||||
"artist_many": "artistas",
|
||||
"artist_other": "artistas",
|
||||
"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",
|
||||
"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",
|
||||
"albumWithCount_one": "{{count}} álbum",
|
||||
"albumWithCount_many": "{{count}} álbumes",
|
||||
"albumWithCount_other": "{{count}} álbumes",
|
||||
"favorite_one": "favorito",
|
||||
"favorite_many": "favoritos",
|
||||
"favorite_other": "favoritos",
|
||||
"artistWithCount_one": "{{count}} artista",
|
||||
"artistWithCount_many": "{{count}} artistas",
|
||||
"artistWithCount_other": "{{count}} artistas",
|
||||
"folder_one": "carpeta",
|
||||
"folder_many": "carpetas",
|
||||
"folder_other": "carpetas",
|
||||
"album_one": "álbum",
|
||||
"album_many": "álbumes",
|
||||
"album_other": "álbumes",
|
||||
"genreWithCount_one": "{{count}} género",
|
||||
"genreWithCount_many": "{{count}} géneros",
|
||||
"genreWithCount_other": "{{count}} géneros",
|
||||
"trackWithCount_one": "{{count}} pista",
|
||||
"trackWithCount_many": "{{count}} pistas",
|
||||
"trackWithCount_other": "{{count}} pistas"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
{
|
||||
"player": {
|
||||
"repeat_all": "répète tout",
|
||||
"stop": "stop",
|
||||
"repeat": "répéter",
|
||||
"queue_remove": "effacer la sélection",
|
||||
"playRandom": "jouer au hasard",
|
||||
"skip": "sauter",
|
||||
"previous": "précédant",
|
||||
"toggleFullscreenPlayer": "basculer le lecteur plein écran",
|
||||
"skip_back": "sauter en arrière",
|
||||
"favorite": "favori",
|
||||
"next": "suivant",
|
||||
"shuffle": "lecture aléatoire",
|
||||
"playbackFetchNoResults": "aucune chansons trouvées",
|
||||
"playbackFetchInProgress": "chargement des chansons…",
|
||||
"addNext": "ajouter ensuite",
|
||||
"playbackSpeed": "vitesse de lecture",
|
||||
"playbackFetchCancel": "cela prend du temps… fermez la notification pour annuler",
|
||||
"play": "jouer",
|
||||
"repeat_off": "répétition désactivée",
|
||||
"queue_clear": "effacer la file d'attente",
|
||||
"muted": "en sourdine",
|
||||
"queue_moveToTop": "déplacer la sélection vers le bas",
|
||||
"queue_moveToBottom": "déplacer la sélection vers le haut",
|
||||
"shuffle_off": "lecture aléatoire désactivée",
|
||||
"addLast": "ajouter en dernier",
|
||||
"mute": "muet",
|
||||
"skip_forward": "suivant",
|
||||
"pause": "pause"
|
||||
},
|
||||
"action": {
|
||||
"editPlaylist": "éditer $t(entity.playlist_one)",
|
||||
"goToPage": "aller à la page",
|
||||
"moveToTop": "déplacer en haut",
|
||||
"clearQueue": "effacer la file d'attente",
|
||||
"addToFavorites": "ajouter à $t(entity.favorite_other)",
|
||||
"addToPlaylist": "ajouter à $t(entity.playlist_one)",
|
||||
"createPlaylist": "créer $t(entity.playlist_one)",
|
||||
"removeFromPlaylist": "supprimer de $t(entity.playlist_one)",
|
||||
"viewPlaylists": "voir $t(entity.playlist_other)",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"deletePlaylist": "supprimer $t(entity.playlist_one)",
|
||||
"removeFromQueue": "retirer de la file d'attente",
|
||||
"deselectAll": "désélectionner tout",
|
||||
"moveToBottom": "déplacer en bas",
|
||||
"setRating": "noter",
|
||||
"toggleSmartPlaylistEditor": "basculer l'éditeur $t(entity.smartPlaylist)",
|
||||
"removeFromFavorites": "supprimer de $t(entity.favorite_other)"
|
||||
},
|
||||
"common": {
|
||||
"backward": "reculer",
|
||||
"increase": "augmenter",
|
||||
"rating": "notation",
|
||||
"bpm": "bpm",
|
||||
"refresh": "rafraichir",
|
||||
"unknown": "inconnu",
|
||||
"areYouSure": "êtes vous sûr ?",
|
||||
"edit": "éditer",
|
||||
"favorite": "favoris",
|
||||
"left": "gauche",
|
||||
"save": "sauvegarder",
|
||||
"right": "droite",
|
||||
"currentSong": "en cours $t(entity.track_one)",
|
||||
"collapse": "réduire",
|
||||
"trackNumber": "piste",
|
||||
"descending": "décroisant",
|
||||
"add": "ajouter",
|
||||
"gap": "gap",
|
||||
"ascending": "croissant",
|
||||
"dismiss": "annuler",
|
||||
"year": "année",
|
||||
"manage": "gérer",
|
||||
"limit": "limite",
|
||||
"minimize": "minimiser",
|
||||
"modified": "modifier",
|
||||
"duration": "durée",
|
||||
"name": "nom",
|
||||
"maximize": "agrandir",
|
||||
"decrease": "baisser",
|
||||
"ok": "ok",
|
||||
"description": "description",
|
||||
"configure": "configurer",
|
||||
"path": "chemin",
|
||||
"center": "centre",
|
||||
"no": "non",
|
||||
"owner": "propriétaire",
|
||||
"enable": "activer",
|
||||
"clear": "effacer",
|
||||
"forward": "avancer",
|
||||
"delete": "supprimer",
|
||||
"cancel": "annuler",
|
||||
"forceRestartRequired": "redémarrer pour appliquer les changements… fermer la notification pour redémarrer",
|
||||
"setting": "paramètre",
|
||||
"version": "version",
|
||||
"title": "titre",
|
||||
"filter_one": "filtre",
|
||||
"filter_many": "filtres",
|
||||
"filter_other": "filtres",
|
||||
"filters": "filtres",
|
||||
"create": "créer",
|
||||
"bitrate": "bitrate",
|
||||
"saveAndReplace": "sauvegarder et remplacer",
|
||||
"action_one": "action",
|
||||
"action_many": "actions",
|
||||
"action_other": "actions",
|
||||
"playerMustBePaused": "le lecteur doit être en pause",
|
||||
"confirm": "confirmer",
|
||||
"resetToDefault": "réinitialiser par défaut",
|
||||
"home": "accueil",
|
||||
"comingSoon": "prochainement…",
|
||||
"reset": "réinitialiser",
|
||||
"channel_one": "canal",
|
||||
"channel_many": "canaux",
|
||||
"channel_other": "canaux",
|
||||
"disable": "désactiver",
|
||||
"sortOrder": "ordre",
|
||||
"none": "aucun",
|
||||
"menu": "menu",
|
||||
"restartRequired": "redémarrage requis",
|
||||
"previousSong": "précédant $t(entity.track_one)",
|
||||
"noResultsFromQuery": "la requête n'a retourné aucun résultat",
|
||||
"quit": "quitter",
|
||||
"expand": "étendre",
|
||||
"search": "recherche",
|
||||
"saveAs": "sauvegarder en tant que",
|
||||
"disc": "disque",
|
||||
"yes": "oui",
|
||||
"random": "aléatoire",
|
||||
"size": "taille",
|
||||
"biography": "biographie",
|
||||
"note": "note"
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "redémarrer le serveur pour appliquer le nouveau port",
|
||||
"systemFontError": "une erreur s’est produite lors de la tentative d’obtenir les polices système",
|
||||
"playbackError": "une erreur s'est produite lors de la tentative de lecture du média",
|
||||
"endpointNotImplementedError": "endpoint {{endpoint} is not implemented for {{serverType}}",
|
||||
"remotePortError": "une erreur s'est produite lors de la tentative de définir le port du serveur distant",
|
||||
"serverRequired": "serveur requis",
|
||||
"authenticationFailed": "l'authentification à échoué",
|
||||
"apiRouteError": "incapable d’acheminer la demande",
|
||||
"genericError": "une erreur s'est produite",
|
||||
"credentialsRequired": "identifiants requis",
|
||||
"sessionExpiredError": "votre session a expiré",
|
||||
"remoteEnableError": "une erreur s'est produite lors de la tentative de $t(common.enable) le serveur distant",
|
||||
"localFontAccessDenied": "accès refusé aux polices locales",
|
||||
"serverNotSelectedError": "aucun serveur sélectionner",
|
||||
"remoteDisableError": "une erreur s'est produite lors de la tentative de $t(common.disable) le serveur distant",
|
||||
"mpvRequired": "MPV requis",
|
||||
"audioDeviceFetchError": "une erreur s’est produite lors de la tentative d’obtenir les périphériques audio",
|
||||
"invalidServer": "serveur invalide",
|
||||
"loginRateError": "trop de tentative de connexion, merci d'essayer dans quelque secondes"
|
||||
},
|
||||
"filter": {
|
||||
"mostPlayed": "plus joués",
|
||||
"playCount": "nombre d'écoutes",
|
||||
"isCompilation": "est une compilation",
|
||||
"recentlyPlayed": "récemment joués",
|
||||
"isRated": "est noté",
|
||||
"title": "titre",
|
||||
"rating": "évalué",
|
||||
"search": "recherche",
|
||||
"bitrate": "bitrate",
|
||||
"recentlyAdded": "récemment ajoutés",
|
||||
"note": "note",
|
||||
"name": "nom",
|
||||
"dateAdded": "date d'ajout",
|
||||
"releaseDate": "date de sortie",
|
||||
"communityRating": "note de la communauté",
|
||||
"path": "chemin",
|
||||
"favorited": "favoris",
|
||||
"isRecentlyPlayed": "est récemment joués",
|
||||
"isFavorited": "est favoris",
|
||||
"bpm": "bpm",
|
||||
"releaseYear": "année de sortie",
|
||||
"disc": "disque",
|
||||
"biography": "biographie",
|
||||
"songCount": "nombre de chansons",
|
||||
"duration": "durée",
|
||||
"random": "aléatoire",
|
||||
"lastPlayed": "dernière joué",
|
||||
"toYear": "à l'année",
|
||||
"fromYear": "année",
|
||||
"criticRating": "note des critiques",
|
||||
"trackNumber": "piste",
|
||||
"albumArtist": "$t(entity.albumArtist_one)"
|
||||
},
|
||||
"page": {
|
||||
"sidebar": {
|
||||
"nowPlaying": "lecture en cours"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"showLyricMatch": "montrer la correspondance des paroles",
|
||||
"dynamicBackground": "arrière-plan dynamique",
|
||||
"synchronized": "synchronisé",
|
||||
"followCurrentLyric": "suivre les paroles actuelles",
|
||||
"showLyricProvider": "montrer la source des paroles",
|
||||
"unsynchronized": "désynchronisé",
|
||||
"lyricAlignment": "alignement des paroles",
|
||||
"useImageAspectRatio": "utiliser le rapport hauteur/largeur de l'image"
|
||||
},
|
||||
"upNext": "suivant",
|
||||
"lyrics": "paroles",
|
||||
"related": "en rapport"
|
||||
},
|
||||
"appMenu": {
|
||||
"selectServer": "sélectionner le serveur",
|
||||
"manageServers": "gérer les serveurs",
|
||||
"expandSidebar": "développer la barre latérale",
|
||||
"collapseSidebar": "réduire la barre latérale",
|
||||
"openBrowserDevtools": "ouvrir les outils de développement du navigateur",
|
||||
"goBack": "retour arrière",
|
||||
"goForward": "avancer"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "plus joués",
|
||||
"newlyAdded": "versions récemment ajoutés",
|
||||
"explore": "explorer depuis votre bibliothèque",
|
||||
"recentlyPlayed": "récemment joués"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "plus de $t(entity.genre_one)",
|
||||
"moreFromGeneric": "plus de {{item}}"
|
||||
},
|
||||
"setting": {
|
||||
"generalTab": "générale",
|
||||
"hotkeysTab": "raccourci",
|
||||
"windowTab": "fenêtre"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"serverCommands": "commandes du serveur",
|
||||
"goToPage": "aller à la page",
|
||||
"searchFor": "recherche pour {{query}}"
|
||||
},
|
||||
"title": "commandes"
|
||||
},
|
||||
"contextMenu": {
|
||||
"numberSelected": "{{count}} sélectionné"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"audioDevice_description": "sélectionnez le périphérique audio à utiliser pour la lecture (lecteur Web uniquement)",
|
||||
"crossfadeStyle": "style du fondu enchaîné",
|
||||
"audioExclusiveMode_description": "activer le mode de sortie exclusif. Dans ce mode, le système est généralement verrouillé et seul mpv pourra émettre de l'audio",
|
||||
"audioPlayer_description": "sélectionnez le lecteur audio à utiliser pour la lecture",
|
||||
"crossfadeDuration_description": "définit la durée du fondu enchaîné",
|
||||
"audioDevice": "périphérique audio",
|
||||
"accentColor": "couleur d'accent",
|
||||
"accentColor_description": "définit la couleur d'accentuation de l'application",
|
||||
"applicationHotkeys": "raccourcis clavier d'application",
|
||||
"crossfadeDuration": "durée de fondue enchaînée",
|
||||
"audioPlayer": "lecteur audio",
|
||||
"applicationHotkeys_description": "configurer les raccourcis clavier d’application. activer la case à cocher pour définir comme raccourci clavier global (bureau uniquement)",
|
||||
"crossfadeStyle_description": "sélectionnez le style du fondu enchaîné à utiliser pour le lecteur audio",
|
||||
"customFontPath": "chemin de police personnalisé",
|
||||
"disableAutomaticUpdates": "désactiver les mises à jour automatique",
|
||||
"customFontPath_description": "définit le chemin de police personnalisé pour l'application",
|
||||
"remotePort_description": "définit le port du serveur de contrôle à distance",
|
||||
"hotkey_skipBackward": "sauter en arrière",
|
||||
"hotkey_playbackPause": "pause",
|
||||
"mpvExecutablePath_help": "line par line",
|
||||
"hotkey_volumeUp": "monter le volume",
|
||||
"discordIdleStatus_description": "quand activé, mettre à jour le status pendant que le lecteur est inactif",
|
||||
"showSkipButtons": "affiche les boutons suivants et précédents",
|
||||
"minimumScrobblePercentage": "durée minimal du scobble (pourcentage)",
|
||||
"lyricFetch": "récupère les paroles depuis internet",
|
||||
"scrobble": "scrobble",
|
||||
"enableRemote_description": "activer le serveur de contrôle à distance, qui permet à d'autres appareils de contrôler l'application",
|
||||
"fontType_optionSystem": "police système",
|
||||
"mpvExecutablePath_description": "définit le chemin vers l'exécutable mpv",
|
||||
"hotkey_favoriteCurrentSong": "favori $t(common.currentSong)",
|
||||
"sampleRate": "taux d'échantillonnage",
|
||||
"sampleRate_description": "sélectionnez le taux d'échantillonnage de sortie utilisé si la fréquence d'échantillonnage sélectionnée est différente du média actuel",
|
||||
"hotkey_zoomIn": "zoom avant",
|
||||
"scrobble_description": "scrobble les lectures à votre serveur multimédia",
|
||||
"hotkey_browserForward": "avancer",
|
||||
"discordUpdateInterval": "interval de mise à jour de {{discord}} rich presence",
|
||||
"fontType_optionBuiltIn": "police intégrée",
|
||||
"hotkey_playbackPlayPause": "play / pause",
|
||||
"hotkey_rate1": "noter 1 étoile",
|
||||
"hotkey_skipForward": "avancer",
|
||||
"disableLibraryUpdateOnStartup": "désactive la vérification de mise à jour au démarrage",
|
||||
"gaplessAudio": "audio sans interruption",
|
||||
"minimizeToTray_description": "minimise l'application dans la barre d'état système",
|
||||
"hotkey_playbackPlay": "play",
|
||||
"hotkey_togglePreviousSongFavorite": "basculer $t(common.previousSong) favoris",
|
||||
"hotkey_volumeDown": "baisser le volume",
|
||||
"hotkey_unfavoritePreviousSong": "défavorisé $t(common.previousSong)",
|
||||
"globalMediaHotkeys": "raccourci clavier multimédia global",
|
||||
"hotkey_globalSearch": "recherche globale",
|
||||
"gaplessAudio_description": "définit les paramètres d'audio sans interruption pour mpv",
|
||||
"remoteUsername_description": "définit le nom d'utilisateur du serveur de contrôle à distance. si le nom d'utilisateur et le mot de passe sont vides, l'authentification sera désactivée",
|
||||
"exitToTray_description": "minime l'application dans la barre des tâches",
|
||||
"followLyric_description": "faire défiler les paroles jusqu'à la position de lecture actuelle",
|
||||
"hotkey_favoritePreviousSong": "favori $t(common.previousSong)",
|
||||
"lyricOffset": "décalage des paroles (ms)",
|
||||
"discordUpdateInterval_description": "temps en seconde entre chaque mise à jour (minimum de 15 secondes)",
|
||||
"fontType_optionCustom": "police personnalisée",
|
||||
"remotePassword": "mot de passe du serveur de contrôle à distance",
|
||||
"lyricFetchProvider": "fournisseur depuis lequel récupérer les paroles",
|
||||
"language_description": "définit la langue de l 'application $t(common.restartRequired)",
|
||||
"playbackStyle_optionCrossFade": "fondu enchaîné",
|
||||
"hotkey_rate3": "noter 3 étoiles",
|
||||
"font": "police",
|
||||
"mpvExtraParameters": "paramètres de mpv",
|
||||
"hotkey_toggleFullScreenPlayer": "basculer le lecteur plein écran",
|
||||
"hotkey_localSearch": "recherche dans la page",
|
||||
"hotkey_toggleQueue": "basculer la liste de lecteur",
|
||||
"remotePassword_description": "définit le mot de passe du serveur de contrôle à distance. Ces identifiants sont par défaut transmises de façon non sécurisées, donc vous devriez utiliser un mot de passe unique dont vous n'avez pas grand-chose à faire",
|
||||
"hotkey_rate5": "noter 5 étoiles",
|
||||
"hotkey_playbackPrevious": "piste précédente",
|
||||
"showSkipButtons_description": "affiche ou cache les boutons suivants et précédents de la barre de lecture",
|
||||
"language": "language",
|
||||
"playbackStyle": "style de lecture",
|
||||
"hotkey_toggleShuffle": "basculer la lecture aléatoire",
|
||||
"playbackStyle_description": "sélectionnez le style de lecture à utiliser pour le lecteur audio",
|
||||
"discordRichPresence_description": "activer le status du lecteur dans {{discord}} rich presence. Les images clés sont : {{icon}}, {{playing}}, et {{paused}} ",
|
||||
"mpvExecutablePath": "chemin de l'exécutable mpv",
|
||||
"hotkey_rate2": "noter 2 étoiles",
|
||||
"playButtonBehavior_description": "définit le comportement par défaut du bouton play, lors de l'ajout de chanson à la file d'attente",
|
||||
"minimumScrobblePercentage_description": "le pourcentage minimum de la chanson qui doit être joué avant qu'elle ne soit scrobbleée",
|
||||
"exitToTray": "minimiser dans la barre des tâches",
|
||||
"hotkey_rate4": "noter 4 étoiles",
|
||||
"enableRemote": "activer le serveur de contrôle à distance",
|
||||
"showSkipButton_description": "affiche ou cache les boutons suivants et précédents de la barre de lecture",
|
||||
"savePlayQueue": "sauvegarder la liste de lecture",
|
||||
"minimumScrobbleSeconds_description": "la durée minimale en secondes de la chanson qui doit être jouée avant qu'elle ne soit scrobbleée",
|
||||
"fontType_description": "le sélecteur de police intégré sélectionne des polices fourni par Feishin. Police système vous permet de sélectionner des polices fourni par votre système d'éxploitation. personnalisé vous permet de fournir votre propre police",
|
||||
"playButtonBehavior": "comportement du bouton play",
|
||||
"playbackStyle_optionNormal": "normale",
|
||||
"floatingQueueArea": "afficher le zone de survol de la file d'attente flottante",
|
||||
"hotkey_toggleRepeat": "basculer la répétition",
|
||||
"lyricOffset_description": "décale les paroles par le nombre de millisecondes spécifiées",
|
||||
"fontType": "type de police",
|
||||
"remotePort": "port du serveur de contrôle à distance",
|
||||
"hotkey_playbackNext": "piste suivante",
|
||||
"lyricFetch_description": "récupère les paroles depuis divers source d'internet",
|
||||
"lyricFetchProvider_description": "sélectionnez le fournisseur auprès desquels récupérer les paroles. l'ordre des fournisseurs et l'ordre dans lequel ils seront interrogés",
|
||||
"globalMediaHotkeys_description": "active ou désactive l'utilisation des raccourcis clavier multimédia du système pour contrôler la lecture",
|
||||
"followLyric": "suivre les paroles actuelles",
|
||||
"discordIdleStatus": "afficher le statut d'inactivité de rich presence",
|
||||
"hotkey_zoomOut": "zoom arrière",
|
||||
"hotkey_unfavoriteCurrentSong": "défavorisé $t(common.currentSong)",
|
||||
"hotkey_rate0": "supprimer la note",
|
||||
"hotkey_volumeMute": "couper le son",
|
||||
"hotkey_toggleCurrentSongFavorite": "basculer $t(common.currentSong) favori",
|
||||
"remoteUsername": "nom d'utilisateur du serveur de contrôle à distance",
|
||||
"hotkey_browserBack": "retour arrière",
|
||||
"showSkipButton": "affiche les boutons suivants et précédents",
|
||||
"minimizeToTray": "minimise dans la barre des tâches",
|
||||
"gaplessAudio_optionWeak": "faible (recommandée)",
|
||||
"minimumScrobbleSeconds": "scrobble minimum (secondes)",
|
||||
"hotkey_playbackStop": "stop",
|
||||
"font_description": "définit la police à utiliser pour l'application",
|
||||
"savePlayQueue_description": "sauvegarde la liste de lecture quand l'application est fermée et restaure là quand l'application est ouverte",
|
||||
"sidebarCollapsedNavigation_description": "affiche ou cache la navigation dans la barre latérale réduite",
|
||||
"sidebarConfiguration": "configuration de la barre latérale",
|
||||
"sidebarConfiguration_description": "sélectionnez les items et l'ordre dans lesquels ils seront affichaient dans la barre latérale",
|
||||
"sidebarPlaylistList": "liste de playlist de la barre latérale",
|
||||
"sidebarCollapsedNavigation": "navigation de la barre latéral (réduite)",
|
||||
"skipDuration": "temps de l'avance rapide",
|
||||
"sidePlayQueueStyle_optionAttached": "attaché",
|
||||
"sidePlayQueueStyle": "style de la liste de lecture latérale",
|
||||
"sidebarPlaylistList_description": "affiche ou cache la liste de playlist de la barre latérale",
|
||||
"sidePlayQueueStyle_description": "définit le style de la liste de lecture latérale",
|
||||
"sidePlayQueueStyle_optionDetached": "détaché",
|
||||
"volumeWheelStep_description": "la quantité de volume à modifier lors du défilement de la molette de la souris sur le curseur de volume",
|
||||
"theme_description": "définit le thème à utiliser pour l'application",
|
||||
"skipDuration_description": "définit le durée de l'avance rapide, lors de l'utilisation des boutons skip dans la barre de lecture",
|
||||
"themeLight": "thème (clair)",
|
||||
"zoom": "pourcentage de zoom",
|
||||
"themeDark_description": "définit le thème sombre à utiliser pour l'application",
|
||||
"themeLight_description": "définit le thème clair à utiliser pour l'application",
|
||||
"zoom_description": "définit le pourcentage de zoom de l'application",
|
||||
"theme": "thème",
|
||||
"skipPlaylistPage_description": "lors de la navigation dans une playlist, aller directement vers le liste des morceaux, au lieu de la page par défaut",
|
||||
"volumeWheelStep": "marche du curseur de volume",
|
||||
"windowBarStyle": "style de la barre de la fenêtre",
|
||||
"useSystemTheme_description": "suivre le système en termes de thème sombre ou clair",
|
||||
"skipPlaylistPage": "sauter la page de playlist",
|
||||
"themeDark": "thème (sombre)",
|
||||
"windowBarStyle_description": "sélectionner le style de la barre de la fenêtre",
|
||||
"useSystemTheme": "utiliser le thème du système"
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
"title": "supprimer $t(entity.playlist_one)",
|
||||
"success": "$t(entity.playlist_one) supprimée avec succès",
|
||||
"input_confirm": "taper le nom de la $t(entity.playlist_one) pour confirmer"
|
||||
},
|
||||
"addServer": {
|
||||
"title": "ajouter un serveur",
|
||||
"input_username": "nom d'utilisateur",
|
||||
"input_url": "url",
|
||||
"input_password": "mot de passe",
|
||||
"input_legacyAuthentication": "activer l'authtication legacy",
|
||||
"input_name": "nom du serveur",
|
||||
"success": "serveur ajouté avec succès",
|
||||
"input_savePassword": "enregister le mot de passe",
|
||||
"ignoreSsl": "ignorer ssl $t(common.restartRequired)",
|
||||
"ignoreCors": "ignorer cors $t(common.restartRequired)",
|
||||
"error_savePassword": "une erreur s’est produite lors de la tentative de sauvegarde du mot de passe"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"success": "{{message}} $t(entity.song_other) ajouté à {{numOfPlaylists}} $t(entity.playlist_other)",
|
||||
"title": "ajouter à $t(entity.playlist_one)",
|
||||
"input_skipDuplicates": "sauter les doublons"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"title": "créer $t(entity.playlist_one)",
|
||||
"input_public": "publique",
|
||||
"success": "$t(entity.playlist_one) créée avec succès"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "mettre à jour le serveur",
|
||||
"success": "serveur mis à jour avec succès"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "correspondre à tous",
|
||||
"input_optionMatchAny": "correspondre à n'importe quel"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "modifier $t(entity.playlist_one)"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"title": "rechercher parole"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"genre_one": "genre",
|
||||
"genre_many": "genres",
|
||||
"genre_other": "genres",
|
||||
"playlistWithCount_one": "{{count}} playlist",
|
||||
"playlistWithCount_many": "{{count}} playlists",
|
||||
"playlistWithCount_other": "{{count}} playlists",
|
||||
"playlist_one": "playlist",
|
||||
"playlist_many": "playlists",
|
||||
"playlist_other": "playlists",
|
||||
"artist_one": "artiste",
|
||||
"artist_many": "artistes",
|
||||
"artist_other": "artistes",
|
||||
"folderWithCount_one": "{{count}} dossier",
|
||||
"folderWithCount_many": "{{count}} dossiers",
|
||||
"folderWithCount_other": "{{count}} dossiers",
|
||||
"albumArtist_one": "artiste de l'album",
|
||||
"albumArtist_many": "artistes d'albums",
|
||||
"albumArtist_other": "artistes d'albums",
|
||||
"track_one": "piste",
|
||||
"track_many": "pistes",
|
||||
"track_other": "pistes",
|
||||
"albumArtistCount_one": "{{count}} artiste de l'album",
|
||||
"albumArtistCount_many": "{{count}} artistes d'albums",
|
||||
"albumArtistCount_other": "{{count}} artistes d'albums",
|
||||
"albumWithCount_one": "{{count}} album",
|
||||
"albumWithCount_many": "{{count}} albums",
|
||||
"albumWithCount_other": "{{count}} albums",
|
||||
"favorite_one": "favori",
|
||||
"favorite_many": "favoris",
|
||||
"favorite_other": "favoris",
|
||||
"artistWithCount_one": "{{count}} artiste",
|
||||
"artistWithCount_many": "{{count}} artistes",
|
||||
"artistWithCount_other": "{{count}} artistes",
|
||||
"folder_one": "dossier",
|
||||
"folder_many": "dossiers",
|
||||
"folder_other": "dossiers",
|
||||
"smartPlaylist": "intelligente $t(entity.playlist_one)",
|
||||
"album_one": "album",
|
||||
"album_many": "albums",
|
||||
"album_other": "albums",
|
||||
"genreWithCount_one": "{{count}} genre",
|
||||
"genreWithCount_many": "{{count}} genres",
|
||||
"genreWithCount_other": "{{count}} genres",
|
||||
"trackWithCount_one": "{{count}} piste",
|
||||
"trackWithCount_many": "{{count}} pistes",
|
||||
"trackWithCount_other": "{{count}} pistes"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"general": {
|
||||
"displayType": "Type d'affichage",
|
||||
"tableColumns": "colonnes du tableau",
|
||||
"autoFitColumns": "colonnes à ajustement automatique"
|
||||
},
|
||||
"view": {
|
||||
"table": "tableau",
|
||||
"poster": "poster"
|
||||
},
|
||||
"label": {
|
||||
"releaseDate": "date de sortie",
|
||||
"titleCombined": "$t(common.title) (combiné)",
|
||||
"dateAdded": "date d'ajout",
|
||||
"lastPlayed": "dernière écoute",
|
||||
"trackNumber": "numéro de piste",
|
||||
"rowIndex": "index de ligne",
|
||||
"playCount": "nombre de lecture",
|
||||
"discNumber": "disque n°"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"comment": "commentaire",
|
||||
"album": "album",
|
||||
"rating": "notation",
|
||||
"favorite": "favoris",
|
||||
"playCount": "lectures",
|
||||
"releaseYear": "année",
|
||||
"biography": "biographie",
|
||||
"releaseDate": "date de sortie",
|
||||
"bitrate": "bitrate",
|
||||
"title": "titre",
|
||||
"bpm": "bpm",
|
||||
"dateAdded": "date d'ajout",
|
||||
"trackNumber": "piste",
|
||||
"albumArtist": "artiste de l'album",
|
||||
"path": "chemin",
|
||||
"discNumber": "disque"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
{
|
||||
"action": {
|
||||
"editPlaylist": "modifica $t(entity.playlist_one)",
|
||||
"goToPage": "vai alla pagina",
|
||||
"clearQueue": "cancella la coda",
|
||||
"addToFavorites": "aggiungi a $t(entity.favorite_other)",
|
||||
"addToPlaylist": "aggiungi a $t(entity.playlist_one)",
|
||||
"createPlaylist": "crea $t(entity.playlist_one)",
|
||||
"removeFromPlaylist": "rimuovi da $t(entity.playlist_one)",
|
||||
"viewPlaylists": "visualizza $t(entity.playlist_other)",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"deletePlaylist": "elimina $t(entity.playlist_one)",
|
||||
"removeFromQueue": "rimuovi dalla coda",
|
||||
"deselectAll": "deseleziona tutto",
|
||||
"setRating": "vota",
|
||||
"toggleSmartPlaylistEditor": "attiva/disattiva editor $t(entity.smartPlaylist)",
|
||||
"removeFromFavorites": "rimuovi da $t(entity.favorite_other)",
|
||||
"moveToTop": "sposta in cima",
|
||||
"moveToBottom": "sposta in fondo"
|
||||
},
|
||||
"common": {
|
||||
"backward": "indietro",
|
||||
"areYouSure": "sei sicurə?",
|
||||
"add": "aggiungi",
|
||||
"ascending": "crescente",
|
||||
"bitrate": "bitrate",
|
||||
"action_one": "azione",
|
||||
"action_many": "azioni",
|
||||
"action_other": "azioni",
|
||||
"biography": "biografia",
|
||||
"bpm": "bpm",
|
||||
"center": "centro",
|
||||
"cancel": "annulla",
|
||||
"channel_one": "canale",
|
||||
"channel_many": "canali",
|
||||
"channel_other": "canali",
|
||||
"collapse": "collassa",
|
||||
"configure": "configura",
|
||||
"comingSoon": "a seguire…",
|
||||
"increase": "incrementa",
|
||||
"rating": "voto",
|
||||
"refresh": "ricarica",
|
||||
"unknown": "sconosciuto",
|
||||
"edit": "modifica",
|
||||
"favorite": "preferito",
|
||||
"left": "sinistra",
|
||||
"save": "salva",
|
||||
"right": "destra",
|
||||
"currentSong": "$t(entity.track_one) corrent",
|
||||
"trackNumber": "traccia",
|
||||
"descending": "decrescente",
|
||||
"gap": "gap",
|
||||
"dismiss": "dimetti",
|
||||
"year": "anno",
|
||||
"manage": "gestisci",
|
||||
"limit": "limite",
|
||||
"minimize": "minimizza",
|
||||
"modified": "modificato",
|
||||
"duration": "durata",
|
||||
"name": "nome",
|
||||
"maximize": "massimizza",
|
||||
"decrease": "decrementa",
|
||||
"ok": "ok",
|
||||
"description": "descrizione",
|
||||
"path": "percorso",
|
||||
"no": "no",
|
||||
"owner": "proprietariə",
|
||||
"enable": "abilita",
|
||||
"clear": "svuota",
|
||||
"forward": "successivo",
|
||||
"delete": "elimina",
|
||||
"forceRestartRequired": "riavvia per applicare le modifiche... chiudi la notifica per riavviare",
|
||||
"setting": "impostazione",
|
||||
"version": "versione",
|
||||
"title": "titolo",
|
||||
"filter_one": "filtro",
|
||||
"filter_many": "filtri",
|
||||
"filter_other": "filtri",
|
||||
"filters": "filtri",
|
||||
"create": "crea",
|
||||
"saveAndReplace": "salva e sovrascrivi",
|
||||
"playerMustBePaused": "il player deve essere messo in pausa",
|
||||
"confirm": "conferma",
|
||||
"resetToDefault": "ripristina ai valori di default",
|
||||
"home": "home",
|
||||
"reset": "ripristina",
|
||||
"disable": "disabilita",
|
||||
"sortOrder": "ordine",
|
||||
"none": "nessuno",
|
||||
"menu": "menù",
|
||||
"restartRequired": "riavvio richiesto",
|
||||
"previousSong": "$t(entity.track_one) precedente",
|
||||
"noResultsFromQuery": "la query non ha ritornato risultati",
|
||||
"quit": "esci",
|
||||
"expand": "espandi",
|
||||
"search": "cerca",
|
||||
"saveAs": "salva come",
|
||||
"disc": "disco",
|
||||
"yes": "si",
|
||||
"random": "casuale",
|
||||
"size": "dimensione",
|
||||
"note": "nota"
|
||||
},
|
||||
"player": {
|
||||
"repeat_all": "ripeti tutto",
|
||||
"stop": "ferma",
|
||||
"repeat": "ripeti",
|
||||
"queue_remove": "rimuovi selezionati",
|
||||
"playRandom": "riproduci casuale",
|
||||
"skip": "salta",
|
||||
"previous": "precedente",
|
||||
"toggleFullscreenPlayer": "attiva/disattiva player a schermo intero",
|
||||
"skip_back": "salta indietro",
|
||||
"favorite": "preferito",
|
||||
"next": "successivo",
|
||||
"shuffle": "mischia",
|
||||
"playbackFetchNoResults": "nessuna canzone trovata",
|
||||
"playbackFetchInProgress": "caricamento canzoni…",
|
||||
"addNext": "aggiungi successivo",
|
||||
"playbackSpeed": "velocità riproduzione",
|
||||
"playbackFetchCancel": "ci sta mettendo un po'... chiudi la notifica per annullare",
|
||||
"play": "riproduci",
|
||||
"repeat_off": "ripeti disabilitato",
|
||||
"pause": "pausa",
|
||||
"queue_clear": "cancella coda",
|
||||
"muted": "silenziato",
|
||||
"unfavorite": "togli dai preferiti",
|
||||
"queue_moveToTop": "sposta selezionati in fondo",
|
||||
"queue_moveToBottom": "sposta selezionati in cima",
|
||||
"shuffle_off": "mischia disabilitato",
|
||||
"addLast": "aggiungi in coda",
|
||||
"mute": "silenzia",
|
||||
"skip_forward": "salta avanti"
|
||||
},
|
||||
"setting": {
|
||||
"crossfadeStyle_description": "seleziona lo stile dissolvenza da usare per il player audio",
|
||||
"remotePort_description": "imposta la porta del server di controllo remoto",
|
||||
"hotkey_skipBackward": "salta a precedente",
|
||||
"volumeWheelStep_description": "la quantità di volume da cambiare quando si scorre la rotellina del mouse sullo slider del volume",
|
||||
"audioDevice_description": "seleziona il device audioda usare per la riproduzione (solo web player)",
|
||||
"theme_description": "imposta il tema da usare per l'applicazione",
|
||||
"hotkey_playbackPause": "pausa",
|
||||
"mpvExecutablePath_help": "uno per linea",
|
||||
"hotkey_volumeUp": "alza volume",
|
||||
"skipDuration": "salta durata",
|
||||
"discordIdleStatus_description": "quando è attivo, aggiorna lo stato mentre il player è inattivo",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"minimumScrobblePercentage": "durata minima scrobble (percentuale)",
|
||||
"lyricFetch": "ottieni testi da internet",
|
||||
"scrobble": "scrobble",
|
||||
"skipDuration_description": "imposta la durata da saltare quando vengono usati i pulsanti di salto nella barra del player",
|
||||
"enableRemote_description": "abilita il controllo remoto del server per permettere ad altri dispositivi di controllare l'applicazione",
|
||||
"fontType_optionSystem": "font di sistema",
|
||||
"mpvExecutablePath_description": "imposta il percorso dell'eseguibile di mpv",
|
||||
"hotkey_favoriteCurrentSong": "$t(common.currentSong) preferita",
|
||||
"crossfadeStyle": "stile dissolvenza",
|
||||
"sidebarConfiguration": "configurazione barra laterale",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"hotkey_zoomIn": "ingrandisci",
|
||||
"scrobble_description": "esegui scrobble delle riproduzioni al tuo media server",
|
||||
"audioExclusiveMode_description": "abilità modalità output esclusiva. In questa modalità il sistema è di solito chiuso fuori, e solo mpv potrà riprodurre audio",
|
||||
"discordUpdateInterval": "intervallo aggiornamento stato attività {{discord}}",
|
||||
"themeLight": "tema (chiaro)",
|
||||
"fontType_optionBuiltIn": "font built-in",
|
||||
"hotkey_playbackPlayPause": "riproduci / pausa",
|
||||
"hotkey_rate1": "voto 1 stella",
|
||||
"hotkey_skipForward": "salta a successivo",
|
||||
"disableLibraryUpdateOnStartup": "disabilita il controllo di nuove versioni all'avvio",
|
||||
"discordApplicationId_description": "l'application id per lo stato attività di {{discord}} ({{defaultId}} è il valore predefinito)",
|
||||
"gaplessAudio": "audio gapless",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"zoom": "percentuale zoom",
|
||||
"minimizeToTray_description": "riduce a icona nella barra di sistema",
|
||||
"hotkey_playbackPlay": "riproduci",
|
||||
"hotkey_volumeDown": "abbassa volume",
|
||||
"audioPlayer_description": "seleziona il player audio da usare per la riproduzione",
|
||||
"globalMediaHotkeys": "tasti media globali",
|
||||
"hotkey_globalSearch": "ricerca globale",
|
||||
"gaplessAudio_description": "imposta l'audio gapless per mpv",
|
||||
"remoteUsername_description": "imposta l'username del server di controllo remoto. Se username e password sono vuoti, l'autenticazione sarà disattivata",
|
||||
"disableAutomaticUpdates": "disabilita aggiornamenti automatici",
|
||||
"exitToTray_description": "riduce a icona nella barra di sistema all'uscita",
|
||||
"followLyric_description": "scorre il testo alla posizione di riproduzione corrente",
|
||||
"hotkey_favoritePreviousSong": "$t(common.previousSong) preferita",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"lyricOffset": "offset testi (ms)",
|
||||
"discordUpdateInterval_description": "il tempo in secondi tra ogni aggiornamento (minimo 15 secondi)",
|
||||
"fontType_optionCustom": "font personalizzato",
|
||||
"themeDark_description": "imposta il tema scuro da usare per l'applicazione",
|
||||
"audioExclusiveMode": "modalità audio esclusiva",
|
||||
"remotePassword": "password server controllo remoto",
|
||||
"lyricFetchProvider": "providers da dove ottenere testi",
|
||||
"language_description": "imposta la lingua dell'applicazione ($t(common.restartRequired))",
|
||||
"playbackStyle_optionCrossFade": "dissolvenza",
|
||||
"hotkey_rate3": "voto 3 stelle",
|
||||
"font": "font",
|
||||
"mpvExtraParameters": "parametri mpv",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"themeLight_description": "imposta il tema chiaro da usare per l'applicazione",
|
||||
"hotkey_toggleFullScreenPlayer": "attiva/disattiva player a schermo intero",
|
||||
"hotkey_localSearch": "ricerca in-pagina",
|
||||
"hotkey_toggleQueue": "attiva/disattiva coda",
|
||||
"zoom_description": "imposta la percentuale zoom per l'applicazione",
|
||||
"remotePassword_description": "imposta la password del server di controllo remoto. Queste credenziali sono di default trasferite in modo non sicuro, quindi dovresti usare una password unica di cui non ti importa",
|
||||
"hotkey_rate5": "voto 5 stelle",
|
||||
"hotkey_playbackPrevious": "traccia precedente",
|
||||
"crossfadeDuration_description": "imposta la durata dell'effetto di dissolvenza",
|
||||
"language": "lingua",
|
||||
"playbackStyle": "stile riproduzione",
|
||||
"hotkey_toggleShuffle": "attiva/disattiva mischia",
|
||||
"theme": "tema",
|
||||
"playbackStyle_description": "selezione lo stile di riproduzione da usare per il player audio",
|
||||
"discordRichPresence_description": "abilita lo status del playback nello stato attività di {{discord}}. Le chiavi immagine sono: {{icon}}, {{playing}} e {{paused}} ",
|
||||
"mpvExecutablePath": "percorso eseguibile mpv",
|
||||
"audioDevice": "device audio",
|
||||
"hotkey_rate2": "voto 2 stelle",
|
||||
"playButtonBehavior_description": "imposta il comportamente di default del pulsante di riproduzione quando viene aggiunta una canzone alla coda",
|
||||
"minimumScrobblePercentage_description": "la minima percentuale di una canzone che deve essere riprodutta prima di eseguire lo scrobble",
|
||||
"exitToTray": "riduci a icona all'uscita",
|
||||
"hotkey_rate4": "voto 4 stelle",
|
||||
"enableRemote": "abilita controllo remoto server",
|
||||
"savePlayQueue": "salva coda di riproduzione",
|
||||
"minimumScrobbleSeconds_description": "la minima durata in secondi di una canzone che deve essere riprodutta prima di eseguire lo scrobble",
|
||||
"fontType_description": "Font built-in selezionana uno dei font forniti da Feishin. Font di sistema ti permette di selezionare ogni fonti fornito dal tuo sistema operativo. Custom ti permette di fornire il tuo font",
|
||||
"playButtonBehavior": "comportamento pulsante riproduzione",
|
||||
"volumeWheelStep": "step rotellina volume",
|
||||
"sidebarPlaylistList_description": "mostra o nascondi la lista delle playlist nella barra laterale",
|
||||
"accentColor": "colore d'accento",
|
||||
"accentColor_description": "imposta colore d'accento per l'applicazione",
|
||||
"playbackStyle_optionNormal": "normale",
|
||||
"windowBarStyle": "stile barra della finestra",
|
||||
"floatingQueueArea": "mostra l'area di passaggio della coda fluttante",
|
||||
"hotkey_toggleRepeat": "attiva/disattiva ripeti",
|
||||
"lyricOffset_description": "aumenta/dimuisce l'offset del testo di una specifica quantità di millisecondi",
|
||||
"sidebarConfiguration_description": "seleziona gli elementi e l'ordine in cui appaiono nella barra laterale",
|
||||
"fontType": "tipo font",
|
||||
"remotePort": "porta del server di controllo remoto",
|
||||
"applicationHotkeys": "tasti a scelta rapida applicazione",
|
||||
"hotkey_playbackNext": "traccia successiva",
|
||||
"useSystemTheme_description": "segui le preferenze del tema definite dal sistema",
|
||||
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
|
||||
"lyricFetch_description": "ottieni testi da varie sorgenti internet",
|
||||
"lyricFetchProvider_description": "seleziona i provider da dove prendere i testi. l'ordine dei provider è l'ordine in cui vengono fatte le richieste",
|
||||
"globalMediaHotkeys_description": "attiva/disattiva l'uso dei tasti media globali per controllare la riproduzione",
|
||||
"customFontPath": "percorso font personalizzato",
|
||||
"followLyric": "segui testo corrente",
|
||||
"crossfadeDuration": "durata dissolvenza",
|
||||
"discordIdleStatus": "visualizza lo stato attività in stato inattivo",
|
||||
"audioPlayer": "player audio",
|
||||
"hotkey_zoomOut": "rimpicciolisci",
|
||||
"hotkey_rate0": "rimuovi voto",
|
||||
"discordApplicationId": "application id {{discord}}",
|
||||
"applicationHotkeys_description": "configura tasti a scelta rapida dell'applicazione. attiva/disattiva la casella per impostare un tasto a scelta rapida globale (solo desktop)",
|
||||
"floatingQueueArea_description": "visualizza l'icona di passaggio sul lato destro dello schermo per mostrare la coda di riproduzione",
|
||||
"hotkey_volumeMute": "silenzia volume",
|
||||
"remoteUsername": "username server di controllo remoto",
|
||||
"sidebarPlaylistList": "lista playlist nella barra laterale",
|
||||
"minimizeToTray": "riduci a icona",
|
||||
"themeDark": "tema (scuro)",
|
||||
"customFontPath_description": "imposta il percorso al font personalizzato da usare per l'applicazione",
|
||||
"gaplessAudio_optionWeak": "debole (raccomandato)",
|
||||
"minimumScrobbleSeconds": "durata minima scrobble (secondi)",
|
||||
"hotkey_playbackStop": "ferma",
|
||||
"windowBarStyle_description": "seleziona lo stile della barra della finestra",
|
||||
"discordRichPresence": "stato attività {{discord}}",
|
||||
"font_description": "imposta il font da usare per l'applicazione",
|
||||
"savePlayQueue_description": "salva la coda di riproduzione quando l'applicazione viene chiusa e ripristina quando l'applicazione viene riaperta",
|
||||
"useSystemTheme": "usa il tema di sistema"
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "riavvia il server per applicare la nuova porta",
|
||||
"systemFontError": "si è verificato un errore nell'otternere i font di sistema",
|
||||
"playbackError": "si è verificato un errore nel provare a riprodurre il media",
|
||||
"endpointNotImplementedError": "l'endpoint {{endpoint} is not implemented for {{serverType}}",
|
||||
"remotePortError": "si è verificato un errore nel provare a impostare la porta del server remoto",
|
||||
"serverRequired": "server richiesto",
|
||||
"authenticationFailed": "autenticazione fallita",
|
||||
"apiRouteError": "impossibile indirizzare la richiesta",
|
||||
"genericError": "si è verificato un errore",
|
||||
"credentialsRequired": "credenziali richieste",
|
||||
"sessionExpiredError": "la tua sessione è scaduta",
|
||||
"remoteEnableError": "si è verificato un errore nel $t(common.enable) il server remoto",
|
||||
"localFontAccessDenied": "accesso non consentito ai font locali",
|
||||
"serverNotSelectedError": "nessun server selezionato",
|
||||
"remoteDisableError": "si è verificato un errore nel $t(common.disable) il server remoto",
|
||||
"mpvRequired": "MPV richiesto",
|
||||
"audioDeviceFetchError": "si è verificato un errore nel provare ad ottenre i device audio",
|
||||
"invalidServer": "server non valido",
|
||||
"loginRateError": "troppi tentativi di accesso, per favore riprova tra qualche secondo"
|
||||
},
|
||||
"filter": {
|
||||
"mostPlayed": "più riprodotti",
|
||||
"comment": "commento",
|
||||
"playCount": "numero di riproduzioni",
|
||||
"recentlyUpdated": "aggiornati recentemente",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"isCompilation": "è una compilation",
|
||||
"recentlyPlayed": "riprodotti recentemente",
|
||||
"isRated": "è valutato",
|
||||
"owner": "$t(common.owner)",
|
||||
"title": "titolo",
|
||||
"rating": "voto",
|
||||
"search": "cerca",
|
||||
"bitrate": "bitrate",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"recentlyAdded": "aggiunti recentemente",
|
||||
"note": "nota",
|
||||
"name": "nome",
|
||||
"dateAdded": "data aggiunta",
|
||||
"releaseDate": "data di rilascio",
|
||||
"albumCount": "numero $t(entity.album_other)",
|
||||
"communityRating": "voto della community",
|
||||
"path": "percorso",
|
||||
"favorited": "preferito",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"isRecentlyPlayed": "è stato recentemente riprodotto",
|
||||
"isFavorited": "è preferito",
|
||||
"bpm": "bpm",
|
||||
"releaseYear": "anno di rilascio",
|
||||
"id": "id",
|
||||
"disc": "disco",
|
||||
"biography": "biografia",
|
||||
"songCount": "conteggio canzoni",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"duration": "durata",
|
||||
"isPublic": "è pubblico",
|
||||
"random": "casuale",
|
||||
"lastPlayed": "ultima riproduzione",
|
||||
"toYear": "fino all'anno",
|
||||
"fromYear": "dall'anno",
|
||||
"criticRating": "voto della critica",
|
||||
"album": "$t(entity.album_one)",
|
||||
"trackNumber": "traccia"
|
||||
},
|
||||
"page": {
|
||||
"sidebar": {
|
||||
"nowPlaying": "in riproduzione",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"tracks": "$t(entity.track_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"home": "$t(common.home)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"albumArtists": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"showLyricMatch": "mostra corrispondenza testi",
|
||||
"dynamicBackground": "background dinamico",
|
||||
"synchronized": "sincronizzato",
|
||||
"followCurrentLyric": "segui testo corrente",
|
||||
"opacity": "opacità",
|
||||
"lyricSize": "dimensione testo",
|
||||
"showLyricProvider": "mostra provider testi",
|
||||
"unsynchronized": "non sinncronizzato",
|
||||
"lyricAlignment": "allineamento testo",
|
||||
"useImageAspectRatio": "usa le proporzioni dell'immagine",
|
||||
"lyricGap": "gap testo"
|
||||
},
|
||||
"upNext": "successivamente",
|
||||
"lyrics": "testi",
|
||||
"related": "correlati"
|
||||
},
|
||||
"appMenu": {
|
||||
"selectServer": "seleziona server",
|
||||
"version": "versione {{version}}",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"manageServers": "gestisci sever",
|
||||
"expandSidebar": "espandi barra laterale",
|
||||
"collapseSidebar": "collassa barra laterale",
|
||||
"openBrowserDevtools": "apri devtools browser",
|
||||
"quit": "$t(common.quit)",
|
||||
"goBack": "torna indietro",
|
||||
"goForward": "vai avanti"
|
||||
},
|
||||
"contextMenu": {
|
||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||
"addToFavorites": "$t(action.addToFavorites)",
|
||||
"setRating": "$t(action.setRating)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"createPlaylist": "$t(action.createPlaylist)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"addNext": "$t(player.addNext)",
|
||||
"deselectAll": "$t(action.deselectAll)",
|
||||
"addLast": "$t(player.addLast)",
|
||||
"addFavorite": "$t(action.addToFavorites)",
|
||||
"play": "$t(player.play)",
|
||||
"numberSelected": "{{count}} selezionati",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "più riprodotti",
|
||||
"newlyAdded": "nuovi rilasci aggiunti",
|
||||
"title": "$t(common.home)",
|
||||
"explore": "esplora dalla tua libreria",
|
||||
"recentlyPlayed": "riprodotti recentemente"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "di più da questo $t(entity.genre_one)",
|
||||
"moreFromGeneric": "di più da {{item}}"
|
||||
},
|
||||
"setting": {
|
||||
"playbackTab": "riproduzione",
|
||||
"generalTab": "generale",
|
||||
"hotkeysTab": "tasti a scelta rapida",
|
||||
"windowTab": "finestra"
|
||||
},
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"serverCommands": "comandi server",
|
||||
"goToPage": "vai alla pagina",
|
||||
"searchFor": "cerca per {{query}}"
|
||||
},
|
||||
"title": "comandi"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
"title": "elimina $t(entity.playlist_one)",
|
||||
"success": "$t(entity.playlist_one) eliminata correttamente",
|
||||
"input_confirm": "digita il nome della $t(entity.playlist_one) per confermare"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
"title": "crea $t(entity.playlist_one)",
|
||||
"input_public": "publico",
|
||||
"input_name": "$t(common.name)",
|
||||
"success": "$t(entity.playlist_one) creata con successo",
|
||||
"input_owner": "$t(common.owner)"
|
||||
},
|
||||
"addServer": {
|
||||
"title": "aggiungi server",
|
||||
"input_username": "nome utente",
|
||||
"input_url": "url",
|
||||
"input_password": "password",
|
||||
"input_legacyAuthentication": "abilita autenticazione legacy",
|
||||
"input_name": "nome server",
|
||||
"success": "server aggiunto con successo",
|
||||
"input_savePassword": "salva password",
|
||||
"ignoreSsl": "ignora ssl ($t(common.restartRequired))",
|
||||
"ignoreCors": "ignora cors ($t(common.restartRequired))",
|
||||
"error_savePassword": "si è verificato un errore quando si è provato a salvare la password"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"success": "aggiunto {{message}} $t(entity.song_other) a {{numOfPlaylists}} $t(entity.playlist_other)",
|
||||
"title": "aggiungi a $t(entity.playlist_one)",
|
||||
"input_skipDuplicates": "salta duplicati",
|
||||
"input_playlists": "$t(entity.playlist_other)"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "aggiorna server",
|
||||
"success": "server aggiornato con successo"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "soddisfa tutti",
|
||||
"input_optionMatchAny": "soddisfa qualsiasi"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"input_name": "$t(common.name)",
|
||||
"input_artist": "$t(entity.artist_one)",
|
||||
"title": "cerca testi"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "modifica $t(entity.playlist_one)"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"general": {
|
||||
"displayType": "mostra tipo",
|
||||
"gap": "$t(common.gap)",
|
||||
"tableColumns": "tabella colonne",
|
||||
"autoFitColumns": "adatta colonne automaticamente",
|
||||
"size": "$t(common.size)"
|
||||
},
|
||||
"view": {
|
||||
"table": "tabella"
|
||||
},
|
||||
"label": {
|
||||
"releaseDate": "data rilascio",
|
||||
"title": "$t(common.title)",
|
||||
"duration": "$t(common.duration)",
|
||||
"titleCombined": "$t(common.title) (combinati)",
|
||||
"dateAdded": "data aggiunta",
|
||||
"size": "$t(common.size)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"lastPlayed": "ultima riproduzione",
|
||||
"trackNumber": "numero traccia",
|
||||
"rowIndex": "indice riga",
|
||||
"rating": "$t(common.rating)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"note": "$t(common.note)",
|
||||
"biography": "$t(common.biography)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"playCount": "numero riproduzioni",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"discNumber": "numero disco",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"year": "$t(common.year)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"comment": "commento",
|
||||
"album": "album",
|
||||
"rating": "voto",
|
||||
"favorite": "preferito",
|
||||
"playCount": "riproduzioni",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"releaseYear": "anno",
|
||||
"lastPlayed": "ultima riproduzione",
|
||||
"biography": "biografia",
|
||||
"releaseDate": "data di rilascio",
|
||||
"bitrate": "bitrate",
|
||||
"title": "titolo",
|
||||
"bpm": "bpm",
|
||||
"dateAdded": "data aggiunta",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"trackNumber": "traccia",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"albumArtist": "artista album",
|
||||
"path": "percorso",
|
||||
"discNumber": "disco",
|
||||
"channels": "$t(common.channel_other)"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"genre_one": "genere",
|
||||
"genre_many": "generi",
|
||||
"genre_other": "generi",
|
||||
"playlistWithCount_one": "{{count}} playlist",
|
||||
"playlistWithCount_many": "{{count}} playlist",
|
||||
"playlistWithCount_other": "{{count}} playlist",
|
||||
"playlist_one": "playlist",
|
||||
"playlist_many": "playlist",
|
||||
"playlist_other": "playlist",
|
||||
"artist_one": "artista",
|
||||
"artist_many": "artisti",
|
||||
"artist_other": "artisti",
|
||||
"folderWithCount_one": "{{count}} cartella",
|
||||
"folderWithCount_many": "{{count}} cartelle",
|
||||
"folderWithCount_other": "{{count}} cartelle",
|
||||
"albumArtist_one": "artista album",
|
||||
"albumArtist_many": "artisti album",
|
||||
"albumArtist_other": "artisti album",
|
||||
"track_one": "traccia",
|
||||
"track_many": "tracce",
|
||||
"track_other": "tracce",
|
||||
"albumArtistCount_one": "{{count}} artista album",
|
||||
"albumArtistCount_many": "{{count}} artisti album",
|
||||
"albumArtistCount_other": "{{count}} artisti album",
|
||||
"albumWithCount_one": "{{count}} album",
|
||||
"albumWithCount_many": "{{count}} album",
|
||||
"albumWithCount_other": "{{count}} album",
|
||||
"favorite_one": "preferito",
|
||||
"favorite_many": "preferiti",
|
||||
"favorite_other": "preferiti",
|
||||
"artistWithCount_one": "{{count}} artista",
|
||||
"artistWithCount_many": "{{count}} artisti",
|
||||
"artistWithCount_other": "{{count}} artisti",
|
||||
"folder_one": "cartella",
|
||||
"folder_many": "cartelle",
|
||||
"folder_other": "cartelle",
|
||||
"smartPlaylist": "$t(entity.playlist_one) smart",
|
||||
"album_one": "album",
|
||||
"album_many": "album",
|
||||
"album_other": "album",
|
||||
"genreWithCount_one": "{{count}} genere",
|
||||
"genreWithCount_many": "{{count}} generi",
|
||||
"genreWithCount_other": "{{count}} generi",
|
||||
"trackWithCount_one": "{{count}} traccia",
|
||||
"trackWithCount_many": "{{count}} tracce",
|
||||
"trackWithCount_other": "{{count}} tracce"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
{
|
||||
"player": {
|
||||
"repeat_all": "全曲リピート",
|
||||
"stop": "停止",
|
||||
"repeat": "リピート",
|
||||
"queue_remove": "選択項目を削除",
|
||||
"playRandom": "ランダム再生",
|
||||
"skip": "スキップ",
|
||||
"previous": "前へ",
|
||||
"toggleFullscreenPlayer": "フルスクリーンプレーヤーの切り替え",
|
||||
"skip_back": "前へスキップ",
|
||||
"favorite": "お気に入り",
|
||||
"next": "次へ",
|
||||
"shuffle": "シャッフル",
|
||||
"playbackFetchNoResults": "曲が見つかりません",
|
||||
"playbackFetchInProgress": "曲を読み込み中…",
|
||||
"addNext": "次へ追加",
|
||||
"playbackSpeed": "再生速度",
|
||||
"playbackFetchCancel": "処理に時間がかかります… 通知を閉じるとキャンセルします",
|
||||
"play": "再生",
|
||||
"repeat_off": "リピート無効",
|
||||
"queue_clear": "キューをクリア",
|
||||
"muted": "ミュート中",
|
||||
"unfavorite": "お気に入り解除",
|
||||
"queue_moveToTop": "選択項目を先末尾に移動",
|
||||
"queue_moveToBottom": "選択項目を先頭に移動",
|
||||
"shuffle_off": "シャッフル無効",
|
||||
"addLast": "最後へ追加",
|
||||
"mute": "ミュート",
|
||||
"skip_forward": "次へスキップ",
|
||||
"pause": "一時停止"
|
||||
},
|
||||
"setting": {
|
||||
"crossfadeStyle_description": "オーディオプレーヤーが使用するクロスフェードのスタイルを選択します",
|
||||
"remotePort_description": "リモートコントロール サーバーのポートを設定します",
|
||||
"hotkey_skipBackward": "前にスキップ",
|
||||
"replayGainMode_description": "ファイルのメタデータに保存されている{{ReplayGain}}値に従って音量ゲインを調整します",
|
||||
"volumeWheelStep_description": "音量スライダーでマウスホイールをスクロールしたときに変化する音量を設定します",
|
||||
"audioDevice_description": "再生に使用するオーディオデバイスを選択します (Webプレーヤーのみ)",
|
||||
"theme_description": "アプリケーションに使用するテーマを設定します",
|
||||
"hotkey_playbackPause": "一時停止",
|
||||
"replayGainFallback": "{{ReplayGain}} フォールバック",
|
||||
"sidebarCollapsedNavigation_description": "折りたたみサイドバーのナビゲーションを表示/非表示にします",
|
||||
"mpvExecutablePath_help": "1行ごとに1アイテム",
|
||||
"hotkey_volumeUp": "音量を上げる",
|
||||
"skipDuration": "スキップの長さ",
|
||||
"discordIdleStatus_description": "プレイヤーがアイドル状態でもステータスを更新します",
|
||||
"showSkipButtons": "スキップボタンを表示",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"minimumScrobblePercentage": "最小 Scrobble 時間 (%)",
|
||||
"lyricFetch": "インターネットから歌詞を取得",
|
||||
"scrobble": "Scrobble",
|
||||
"skipDuration_description": "プレーヤーバーのスキップボタンでスキップする時間を設定します",
|
||||
"enableRemote_description": "リモートコントロール サーバーを有効化し、他のデバイスからアプリケーションを制御できるようにします",
|
||||
"fontType_optionSystem": "システムフォント",
|
||||
"mpvExecutablePath_description": "mpvの実行ファイルが存在するパスを設定します",
|
||||
"replayGainClipping_description": "自動的にゲインを下げて{{ReplayGain}}によるクリッピングを防ぎます",
|
||||
"replayGainPreamp": "{{ReplayGain}} プリアンプ (dB)",
|
||||
"hotkey_favoriteCurrentSong": "$t(common.currentSong) をお気に入り",
|
||||
"sampleRate": "サンプルレート",
|
||||
"crossfadeStyle": "クロスフェードスタイル",
|
||||
"sidePlayQueueStyle_optionAttached": "結合",
|
||||
"sidebarConfiguration": "サイドバー設定",
|
||||
"sampleRate_description": "サンプル周波数がメディア、選択とで異なる場合に使用する出力サンプルレートを選択します",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"replayGainClipping": "{{ReplayGain}} クリッピング",
|
||||
"hotkey_zoomIn": "拡大",
|
||||
"scrobble_description": "メデイアサーバーに再生をScrobbleさせます",
|
||||
"hotkey_browserForward": "ブラウザ 進む",
|
||||
"audioExclusiveMode_description": "専用の排他出力モードを有効にします。 このモードでは、システムの他の出力がロックされ、mpvだけがオーディオを出力できるようになります",
|
||||
"discordUpdateInterval": "{{discord}} Rich Presenceアップデート間隔",
|
||||
"themeLight": "テーマ (ライト)",
|
||||
"fontType_optionBuiltIn": "組み込みフォント",
|
||||
"hotkey_playbackPlayPause": "再生 / 一時停止",
|
||||
"hotkey_rate1": "1つ星で評価",
|
||||
"hotkey_skipForward": "次へスキップ",
|
||||
"disableLibraryUpdateOnStartup": "起動時の新バージョンチェックを無効にします",
|
||||
"discordApplicationId_description": "{{discord}} にRich Presenceステータスを表示するためのアプリケーションID (デフォルトは {{defaultId}} です)",
|
||||
"sidePlayQueueStyle": "サイド再生キュースタイル",
|
||||
"gaplessAudio": "ギャップレス再生",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"zoom": "ズーム率",
|
||||
"minimizeToTray_description": "最小化ボタンが押された際、システムトレイに格納します",
|
||||
"hotkey_playbackPlay": "再生",
|
||||
"hotkey_togglePreviousSongFavorite": "$t(common.previousSong) をお気に入り登録/解除",
|
||||
"hotkey_volumeDown": "音量を下げる",
|
||||
"hotkey_unfavoritePreviousSong": "$t(common.previousSong) をお気に入り解除",
|
||||
"audioPlayer_description": "再生に使用するオーディオプレーヤーを選択します",
|
||||
"globalMediaHotkeys": "グローバルメディアホットキー",
|
||||
"hotkey_globalSearch": "グローバル検索",
|
||||
"gaplessAudio_description": "mpv用にギャップレス再生を設定します",
|
||||
"remoteUsername_description": "リモートコントロール サーバーのユーザ名を設定します。 ユーザー名とパスワードの両方が空の場合、認証は無効になります",
|
||||
"disableAutomaticUpdates": "自動更新を無効化",
|
||||
"exitToTray_description": "アプリケーション終了ボタンが押された際、システムトレイに格納します",
|
||||
"followLyric_description": "現在の再生位置に歌詞をスクロールします",
|
||||
"hotkey_favoritePreviousSong": "$t(common.previousSong) をお気に入り",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"lyricOffset": "歌詞オフセット (ミリ秒)",
|
||||
"discordUpdateInterval_description": "更新間隔 (秒単位, 最小15秒)",
|
||||
"fontType_optionCustom": "カスタムフォント",
|
||||
"themeDark_description": "アプリケーションに使用するダークテーマを設定します",
|
||||
"audioExclusiveMode": "オーディオ排他モード",
|
||||
"remotePassword": "リモートコントロール サーバー パスワード",
|
||||
"lyricFetchProvider": "歌詞取得先",
|
||||
"language_description": "アプリケーションの言語を設定します ($t(common.restartRequired))",
|
||||
"playbackStyle_optionCrossFade": "クロスフェード",
|
||||
"hotkey_rate3": "3つ星で評価",
|
||||
"font": "フォント",
|
||||
"mpvExtraParameters": "mpv パラメーター",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"themeLight_description": "アプリケーションに使用するライトテーマを設定します",
|
||||
"hotkey_toggleFullScreenPlayer": "フルスクリーンプレーヤーの切り替え",
|
||||
"hotkey_localSearch": "ページ内検索",
|
||||
"hotkey_toggleQueue": "キューの切り替え",
|
||||
"zoom_description": "アプリケーションのズーム率を設定します",
|
||||
"remotePassword_description": "リモートコントロール サーバーのパスワードを設定します。 ログイン情報はデフォルトでセキュアな通信がされないため、個人情報と関係ないランダムなパスワードを利用してください",
|
||||
"hotkey_rate5": "5つ星で評価",
|
||||
"hotkey_playbackPrevious": "前のトラック",
|
||||
"showSkipButtons_description": "プレーヤーバーのスキップボタンを表示/非表示にします",
|
||||
"crossfadeDuration_description": "クロスフェード効果の時間を設定します",
|
||||
"language": "言語",
|
||||
"playbackStyle": "再生スタイル",
|
||||
"hotkey_toggleShuffle": "シャッフルの切り替え",
|
||||
"theme": "テーマ",
|
||||
"playbackStyle_description": "オーディオプレーヤーに使用する再生スタイルを選択します",
|
||||
"discordRichPresence_description": "{{discord}} のRich Presenceに再生ステータスを表示するようにします。画像キー: {{icon}}, {{playing}}, {{paused}} ",
|
||||
"mpvExecutablePath": "mpv 実行ファイルパス",
|
||||
"audioDevice": "オーディオデバイス",
|
||||
"hotkey_rate2": "2つ星で評価",
|
||||
"playButtonBehavior_description": "キューに曲を追加するときの再生ボタンのデフォルトの動作を設定します",
|
||||
"minimumScrobblePercentage_description": "Scrobbleされるために必要な最短の再生時間(%)",
|
||||
"exitToTray": "終了時にシステムトレイに格納",
|
||||
"hotkey_rate4": "4つ星で評価",
|
||||
"enableRemote": "リモートコントロール サーバーを有効化",
|
||||
"showSkipButton_description": "プレーヤーバーのスキップボタンを表示/非表示にします",
|
||||
"savePlayQueue": "再生キューを保存",
|
||||
"minimumScrobbleSeconds_description": "Scrobbleされるために必要な最短の再生時間(秒)",
|
||||
"skipPlaylistPage_description": "プレイリストに移動するときに、デフォルトページではなくプレイリストの曲リストページに移動します",
|
||||
"fontType_description": "組み込みフォントは、Feishin が提供するフォントから1つを選択します。 システムフォントは、OSにインストール済みの任意のフォントを選択できます。 カスタムフォントは,\nフォントファイルを自身で選択できます",
|
||||
"playButtonBehavior": "再生ボタンの動作",
|
||||
"volumeWheelStep": "音量ホイールステップ",
|
||||
"sidebarPlaylistList_description": "サイドバーでプレイリストのリストを表示/非表示にします",
|
||||
"accentColor": "アクセントカラー",
|
||||
"sidePlayQueueStyle_description": "サイド再生キューのスタイルを設定します",
|
||||
"accentColor_description": "アプリケーションが利用するアクセントカラーを設定します",
|
||||
"replayGainMode": "{{ReplayGain}} モード",
|
||||
"playbackStyle_optionNormal": "通常",
|
||||
"windowBarStyle": "ウィンドウバースタイル",
|
||||
"floatingQueueArea": "フローティング再生キューエリアの表示",
|
||||
"replayGainFallback_description": "ファイルに{{ReplayGain}}タグがない場合に適用するゲイン (dB単位)",
|
||||
"replayGainPreamp_description": "{{ReplayGain}}の値に適用されるプリアンプゲインを調整します",
|
||||
"hotkey_toggleRepeat": "リピートの切り替え",
|
||||
"lyricOffset_description": "歌詞のオフセットをミリ秒単位で指定します",
|
||||
"sidebarConfiguration_description": "サイドバーに表示されるアイテムと並び順を選択します",
|
||||
"fontType": "フォントタイプ",
|
||||
"remotePort": "リモートコントロール サーバー ポート",
|
||||
"applicationHotkeys": "アプリケーションホットキー",
|
||||
"hotkey_playbackNext": "次のトラック",
|
||||
"useSystemTheme_description": "システム設定のライト/ダークテーマに従います",
|
||||
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
|
||||
"lyricFetch_description": "様々なインターネットソースから歌詞を取得します",
|
||||
"lyricFetchProvider_description": "歌詞を取得するサービスを選択します。サービスの並び順に検索されます",
|
||||
"globalMediaHotkeys_description": "システムのメディアホットキーでの再生コントロールを有効/無効化します",
|
||||
"customFontPath": "カスタムフォントパス",
|
||||
"followLyric": "歌詞を再生位置に追従",
|
||||
"crossfadeDuration": "クロスフェードの長さ",
|
||||
"discordIdleStatus": "アイドル状態でRich Presenceステータスを表示",
|
||||
"sidePlayQueueStyle_optionDetached": "分離",
|
||||
"audioPlayer": "オーディオプレーヤー",
|
||||
"hotkey_zoomOut": "縮小",
|
||||
"hotkey_unfavoriteCurrentSong": "$t(common.currentSong) をお気に入り解除",
|
||||
"hotkey_rate0": "評価をクリア",
|
||||
"discordApplicationId": "{{discord}} アプリケーション ID",
|
||||
"applicationHotkeys_description": "アプリケーションのホットキーを設定します。 チェックボックスを切り替えて、グローバルホットキー(デスクトップのみ)として設定できます",
|
||||
"floatingQueueArea_description": "画面右側に、再生キューをフローティング表示するためのホバーアイコンが表示されます",
|
||||
"hotkey_volumeMute": "音量をミュート",
|
||||
"hotkey_toggleCurrentSongFavorite": "$t(common.currentSong) をお気に入り登録/解除",
|
||||
"remoteUsername": "リモートコントロール サーバー ユーザー名",
|
||||
"hotkey_browserBack": "ブラウザ 戻る",
|
||||
"showSkipButton": "スキップボタンを表示",
|
||||
"sidebarPlaylistList": "サイドバー プレイリスト リスト",
|
||||
"minimizeToTray": "最小化時にシステムトレイに格納",
|
||||
"skipPlaylistPage": "プレイリストページをスキップ",
|
||||
"themeDark": "テーマ (ダーク)",
|
||||
"sidebarCollapsedNavigation": "サイドバー (折りたたみ) ナビゲーション",
|
||||
"customFontPath_description": "アプリケーションが使用するカスタムフォントへのパスを設定します",
|
||||
"gaplessAudio_optionWeak": "弱 (推奨)",
|
||||
"minimumScrobbleSeconds": "最小 Scrobble 時間 (秒)",
|
||||
"hotkey_playbackStop": "停止",
|
||||
"windowBarStyle_description": "ウィンドウバーのスタイルを選択します",
|
||||
"discordRichPresence": "{{discord}} Rich Presence ステータス表示",
|
||||
"font_description": "アプリケーションに使用するフォントを設定します",
|
||||
"savePlayQueue_description": "アプリケーション終了時に再生キューを保存し、アプリケーション開始時に復元します",
|
||||
"useSystemTheme": "システムテーマを使用"
|
||||
},
|
||||
"action": {
|
||||
"editPlaylist": "$t(entity.playlist_one) を編集",
|
||||
"goToPage": "ページへ移動",
|
||||
"moveToTop": "先頭に移動",
|
||||
"clearQueue": "キューをクリア",
|
||||
"addToFavorites": "$t(entity.favorite_other) に追加",
|
||||
"addToPlaylist": "$t(entity.playlist_one) に追加",
|
||||
"createPlaylist": "$t(entity.playlist_one) を作成",
|
||||
"removeFromPlaylist": "$t(entity.playlist_one) から削除",
|
||||
"viewPlaylists": "$t(entity.playlist_other) を表示",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"deletePlaylist": "$t(entity.playlist_one) を削除",
|
||||
"removeFromQueue": "キューから削除",
|
||||
"deselectAll": "すべて選択解除",
|
||||
"moveToBottom": "末尾に移動",
|
||||
"setRating": "評価",
|
||||
"toggleSmartPlaylistEditor": "$t(entity.smartPlaylist) エディタの切り替え",
|
||||
"removeFromFavorites": "$t(entity.favorite_other) から削除"
|
||||
},
|
||||
"common": {
|
||||
"backward": "戻る",
|
||||
"increase": "増加",
|
||||
"rating": "評価",
|
||||
"bpm": "BPM",
|
||||
"refresh": "再読み込み",
|
||||
"unknown": "不明",
|
||||
"areYouSure": "実行しますか?",
|
||||
"edit": "編集",
|
||||
"favorite": "お気に入り",
|
||||
"left": "左側",
|
||||
"save": "保存",
|
||||
"right": "右側",
|
||||
"currentSong": "現在の $t(entity.track_one)",
|
||||
"collapse": "折りたたみ",
|
||||
"trackNumber": "トラック",
|
||||
"descending": "降順",
|
||||
"add": "追加",
|
||||
"gap": "ギャップ",
|
||||
"ascending": "昇順",
|
||||
"dismiss": "無視",
|
||||
"year": "年",
|
||||
"manage": "管理",
|
||||
"limit": "制限",
|
||||
"minimize": "最小化",
|
||||
"modified": "変更済み",
|
||||
"duration": "長さ",
|
||||
"name": "名前",
|
||||
"maximize": "最大化",
|
||||
"decrease": "減少",
|
||||
"ok": "OK",
|
||||
"description": "説明",
|
||||
"configure": "設定",
|
||||
"path": "パス",
|
||||
"center": "中央",
|
||||
"no": "いいえ",
|
||||
"owner": "所有者",
|
||||
"enable": "有効",
|
||||
"clear": "クリア",
|
||||
"forward": "進む",
|
||||
"delete": "削除",
|
||||
"cancel": "キャンセル",
|
||||
"forceRestartRequired": "変更を適用するために再起動が必要です… 通知を閉じると再起動します",
|
||||
"setting": "設定",
|
||||
"version": "バージョン",
|
||||
"title": "タイトル",
|
||||
"filter_other": "フィルタ",
|
||||
"filters": "フィルタ",
|
||||
"create": "作成",
|
||||
"bitrate": "ビットレート",
|
||||
"saveAndReplace": "保存して変更",
|
||||
"action_other": "アクション",
|
||||
"playerMustBePaused": "プレイヤーを一時停止する必要があります",
|
||||
"confirm": "確認",
|
||||
"resetToDefault": "デフォルトにリセット",
|
||||
"home": "ホーム",
|
||||
"comingSoon": "近日利用可能になる予定です…",
|
||||
"reset": "リセット",
|
||||
"channel_other": "チャンネル",
|
||||
"disable": "無効",
|
||||
"sortOrder": "順序",
|
||||
"none": "なし",
|
||||
"menu": "メニュー",
|
||||
"restartRequired": "再起動が必要です",
|
||||
"previousSong": "前の $t(entity.track_one)",
|
||||
"noResultsFromQuery": "条件にマッチするものがありません",
|
||||
"quit": "終了",
|
||||
"expand": "展開",
|
||||
"search": "検索",
|
||||
"saveAs": "名前を付けて保存",
|
||||
"disc": "ディスク",
|
||||
"yes": "はい",
|
||||
"random": "ランダム",
|
||||
"size": "サイズ",
|
||||
"biography": "バイオグラフィー",
|
||||
"note": "ノート"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"view": {
|
||||
"card": "カード",
|
||||
"table": "テーブル",
|
||||
"poster": "ポスター"
|
||||
},
|
||||
"general": {
|
||||
"displayType": "表示タイプ",
|
||||
"gap": "$t(common.gap)",
|
||||
"tableColumns": "テーブル カラム",
|
||||
"autoFitColumns": "カラム長を自動調整",
|
||||
"size": "$t(common.size)"
|
||||
},
|
||||
"label": {
|
||||
"releaseDate": "リリース日時",
|
||||
"title": "$t(common.title)",
|
||||
"duration": "$t(common.duration)",
|
||||
"titleCombined": "$t(common.title) (結合)",
|
||||
"dateAdded": "追加された日時",
|
||||
"size": "$t(common.size)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"lastPlayed": "最後に再生",
|
||||
"trackNumber": "トラック番号",
|
||||
"rowIndex": "行インデックス",
|
||||
"rating": "$t(common.rating)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"note": "$t(common.note)",
|
||||
"biography": "$t(common.biography)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"playCount": "再生回数",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"discNumber": "ディスク番号",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"year": "$t(common.year)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"comment": "コメント",
|
||||
"album": "アルバム",
|
||||
"rating": "評価",
|
||||
"favorite": "お気に入り",
|
||||
"playCount": "再生回数",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"releaseYear": "年",
|
||||
"lastPlayed": "最後に再生",
|
||||
"biography": "バイオグラフィー",
|
||||
"releaseDate": "リリース日時",
|
||||
"bitrate": "ビットレート",
|
||||
"title": "タイトル",
|
||||
"bpm": "BPM",
|
||||
"dateAdded": "追加された日時",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"trackNumber": "トラック",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"albumArtist": "アルバムアーティスト",
|
||||
"path": "パス",
|
||||
"discNumber": "ディスク",
|
||||
"channels": "$t(common.channel_other)"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "新たなポート設定を適用するためサーバーを再起動してください",
|
||||
"systemFontError": "システムフォントを取得する際にエラーが発生しました",
|
||||
"playbackError": "メディアの再生開始時にエラーが発生しました",
|
||||
"remotePortError": "リモートサーバーのポート設定時にエラーが発生しました",
|
||||
"serverRequired": "サーバーが必要です",
|
||||
"authenticationFailed": "認証に失敗しました",
|
||||
"apiRouteError": "リクエストをルーティングできません",
|
||||
"genericError": "エラーが発生しました",
|
||||
"credentialsRequired": "ログイン情報が必要です",
|
||||
"sessionExpiredError": "セッションの有効期限が切れました",
|
||||
"remoteEnableError": "リモートサーバーを $t(common.enable) にする際にエラーが発生しました",
|
||||
"localFontAccessDenied": "ローカルフォントへのアクセスが拒否されました",
|
||||
"serverNotSelectedError": "サーバーが選択されていません",
|
||||
"remoteDisableError": "リモートサーバーを $t(common.disable) にする際にエラーが発生しました",
|
||||
"mpvRequired": "MPVが必要です",
|
||||
"audioDeviceFetchError": "オーディオデバイスの取得時にエラーが発生しました",
|
||||
"invalidServer": "無効なサーバー",
|
||||
"loginRateError": "ログイン試行回数が多すぎます、数秒後に再試行してください"
|
||||
},
|
||||
"filter": {
|
||||
"mostPlayed": "最も多く再生",
|
||||
"playCount": "再生回数",
|
||||
"isCompilation": "コンピレーションアルバム",
|
||||
"recentlyPlayed": "最近の再生",
|
||||
"isRated": "評価済み",
|
||||
"title": "タイトル",
|
||||
"rating": "評価",
|
||||
"search": "検索",
|
||||
"bitrate": "ビットレート",
|
||||
"recentlyAdded": "最近の追加",
|
||||
"note": "ノート",
|
||||
"name": "名前",
|
||||
"dateAdded": "追加された日時",
|
||||
"releaseDate": "リリース日時",
|
||||
"communityRating": "コミュニティの評価",
|
||||
"path": "パス",
|
||||
"favorited": "お気に入り",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"isRecentlyPlayed": "最近再生済み",
|
||||
"isFavorited": "お気に入り済み",
|
||||
"bpm": "BPM",
|
||||
"releaseYear": "リリース年",
|
||||
"disc": "ディスク",
|
||||
"biography": "バイオグラフィー",
|
||||
"songCount": "曲数",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"duration": "長さ",
|
||||
"random": "ランダム",
|
||||
"lastPlayed": "最後に再生",
|
||||
"toYear": "年まで",
|
||||
"fromYear": "年から",
|
||||
"criticRating": "批評家の評価",
|
||||
"trackNumber": "トラック",
|
||||
"comment": "コメント",
|
||||
"recentlyUpdated": "新規更新",
|
||||
"isPublic": "共有済み"
|
||||
},
|
||||
"page": {
|
||||
"sidebar": {
|
||||
"nowPlaying": "再生中",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"tracks": "$t(entity.track_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"home": "$t(common.home)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"albumArtists": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"showLyricMatch": "歌詞のマッチを表示",
|
||||
"dynamicBackground": "ダイナミック背景",
|
||||
"synchronized": "同期",
|
||||
"followCurrentLyric": "歌詞を再生位置に追従",
|
||||
"opacity": "非透過率",
|
||||
"lyricSize": "歌詞のサイズ",
|
||||
"showLyricProvider": "歌詞の提供元を表示",
|
||||
"unsynchronized": "非同期",
|
||||
"lyricAlignment": "歌詞の位置",
|
||||
"useImageAspectRatio": "画像のアスペクト比を使用する",
|
||||
"lyricGap": "歌詞の間隔"
|
||||
},
|
||||
"upNext": "次へ",
|
||||
"lyrics": "歌詞",
|
||||
"related": "関連"
|
||||
},
|
||||
"appMenu": {
|
||||
"selectServer": "サーバを選択",
|
||||
"version": "バージョン {{version}}",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"manageServers": "サーバーの管理",
|
||||
"expandSidebar": "サイドバーを展開",
|
||||
"collapseSidebar": "サイドバーを折りたたむ",
|
||||
"openBrowserDevtools": "ブラウザの開発者ツールを開く",
|
||||
"quit": "$t(common.quit)",
|
||||
"goBack": "戻る",
|
||||
"goForward": "進む"
|
||||
},
|
||||
"contextMenu": {
|
||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||
"addToFavorites": "$t(action.addToFavorites)",
|
||||
"setRating": "$t(action.setRating)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"createPlaylist": "$t(action.createPlaylist)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"addNext": "$t(player.addNext)",
|
||||
"deselectAll": "$t(action.deselectAll)",
|
||||
"addLast": "$t(player.addLast)",
|
||||
"addFavorite": "$t(action.addToFavorites)",
|
||||
"play": "$t(player.play)",
|
||||
"numberSelected": "{{count}} 個 選択",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "最も多く再生",
|
||||
"newlyAdded": "新規追加リリース",
|
||||
"title": "$t(common.home)",
|
||||
"explore": "ライブラリから検索",
|
||||
"recentlyPlayed": "最近の再生"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "$t(entity.genre_one) の他の項目",
|
||||
"moreFromGeneric": "{{item}} の他の項目"
|
||||
},
|
||||
"setting": {
|
||||
"playbackTab": "再生",
|
||||
"generalTab": "一般",
|
||||
"hotkeysTab": "ホットキー",
|
||||
"windowTab": "ウィンドウ"
|
||||
},
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"serverCommands": "サーバーコマンド",
|
||||
"goToPage": "ページへ移動",
|
||||
"searchFor": "{{query}} を検索"
|
||||
},
|
||||
"title": "コマンド"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
"title": "$t(entity.playlist_one) を削除",
|
||||
"success": "$t(entity.playlist_one) が削除されました",
|
||||
"input_confirm": "確認のため $t(entity.playlist_one) の名前を入力してください"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
"title": "$t(entity.playlist_one) を作成",
|
||||
"input_public": "公開",
|
||||
"input_name": "$t(common.name)",
|
||||
"success": "$t(entity.playlist_one) を作成しました",
|
||||
"input_owner": "$t(common.owner)"
|
||||
},
|
||||
"addServer": {
|
||||
"title": "サーバーを追加",
|
||||
"input_username": "ユーザー名",
|
||||
"input_url": "URL",
|
||||
"input_password": "パスワード",
|
||||
"input_legacyAuthentication": "レガシー認証を有効化",
|
||||
"input_name": "サーバー名",
|
||||
"success": "サーバーが追加されました",
|
||||
"input_savePassword": "パスワードを保存",
|
||||
"ignoreSsl": "SSLを無視 ($t(common.restartRequired))",
|
||||
"ignoreCors": "CORSを無視 ($t(common.restartRequired))",
|
||||
"error_savePassword": "パスワードを保存する際にエラーが発生しました"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"success": "{{message}} $t(entity.song_other) を {{numOfPlaylists}} $t(entity.playlist_other) に追加しました",
|
||||
"title": "$t(entity.playlist_one) に追加",
|
||||
"input_skipDuplicates": "重複をスキップ",
|
||||
"input_playlists": "$t(entity.playlist_other)"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "サーバーをアップデート",
|
||||
"success": "サーバーがアップデートされました"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "すべて一致",
|
||||
"input_optionMatchAny": "一部一致"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"input_name": "$t(common.name)",
|
||||
"input_artist": "$t(entity.artist_one)",
|
||||
"title": "歌詞検索"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "$t(entity.playlist_one) を編集"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"genre_other": "ジャンル",
|
||||
"playlistWithCount_other": "{{count}} プレイリスト",
|
||||
"playlist_other": "プレイリスト",
|
||||
"artist_other": "アーティスト",
|
||||
"folderWithCount_other": "{{count}} フォルダ",
|
||||
"albumArtist_other": "アルバムアーティスト",
|
||||
"track_other": "トラック",
|
||||
"albumArtistCount_other": "{{count}} アルバムアーティスト",
|
||||
"albumWithCount_other": "{{count}} アルバム",
|
||||
"favorite_other": "お気に入り",
|
||||
"artistWithCount_other": "{{count}} アーティスト",
|
||||
"folder_other": "フォルダ",
|
||||
"smartPlaylist": "スマート $t(entity.playlist_one)",
|
||||
"album_other": "アルバム",
|
||||
"genreWithCount_other": "{{count}} ジャンル",
|
||||
"trackWithCount_other": "{{count}} トラック"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,632 @@
|
||||
{
|
||||
"action": {
|
||||
"editPlaylist": "edytuj $t(entity.playlist_one)",
|
||||
"goToPage": "idź do strony",
|
||||
"clearQueue": "wyczyść kolejkę",
|
||||
"addToFavorites": "dodaj do $t(entity.favorite_other)",
|
||||
"removeFromPlaylist": "usuń z $t(entity.playlist_one)",
|
||||
"viewPlaylists": "zobacz $t(entity.playlist_other)",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"removeFromQueue": "usuń z kolejki",
|
||||
"deselectAll": "odznacz wszystko",
|
||||
"toggleSmartPlaylistEditor": "przełącz edytor $t(entity.smartPlaylist)",
|
||||
"removeFromFavorites": "usuń z $t(entity.favorite_other)",
|
||||
"moveToTop": "przesuń na górę",
|
||||
"addToPlaylist": "dodaj do $t(entity.playlist_one)",
|
||||
"createPlaylist": "utwórz $t(entity.playlist_one)",
|
||||
"deletePlaylist": "usuń $t(entity.playlist_one)",
|
||||
"moveToBottom": "przesuń na dół",
|
||||
"setRating": "oceń"
|
||||
},
|
||||
"common": {
|
||||
"increase": "zwiększ",
|
||||
"rating": "ocena",
|
||||
"bpm": "bpm",
|
||||
"refresh": "odśwież",
|
||||
"unknown": "nieznany",
|
||||
"areYouSure": "czy jesteś pewien?",
|
||||
"edit": "edytuj",
|
||||
"favorite": "ulubiony",
|
||||
"save": "zapisz",
|
||||
"right": "prawo",
|
||||
"trackNumber": "utwór",
|
||||
"descending": "malejąco",
|
||||
"add": "dodaj",
|
||||
"ascending": "rosnąco",
|
||||
"dismiss": "anuluj",
|
||||
"year": "rok",
|
||||
"limit": "limit",
|
||||
"minimize": "zminimalizuj",
|
||||
"modified": "zmodyfikowany",
|
||||
"duration": "długość",
|
||||
"name": "nazwa",
|
||||
"maximize": "zmaksymalizuj",
|
||||
"ok": "ok",
|
||||
"description": "opis",
|
||||
"configure": "konfiguruj",
|
||||
"no": "nie",
|
||||
"owner": "właściciel",
|
||||
"enable": "włącz",
|
||||
"clear": "wyczyść",
|
||||
"forward": "do przodu",
|
||||
"delete": "usuń",
|
||||
"cancel": "cofnij",
|
||||
"forceRestartRequired": "zrestartuj aby zastosować zmiany... zamknij powiadomienie aby zrestartować",
|
||||
"setting": "ustawienie",
|
||||
"version": "wersja",
|
||||
"title": "tytuł",
|
||||
"filter_one": "filtr",
|
||||
"filter_few": "filtry",
|
||||
"filter_many": "filtrów",
|
||||
"filters": "filtry",
|
||||
"create": "stwórz",
|
||||
"bitrate": "bitrate",
|
||||
"saveAndReplace": "zapisz i zamień",
|
||||
"action_one": "akcja",
|
||||
"action_few": "akcje",
|
||||
"action_many": "akcji",
|
||||
"playerMustBePaused": "odtwarzacz musi być zapauzowany",
|
||||
"confirm": "potwierdź",
|
||||
"resetToDefault": "przywróć do domyślnych",
|
||||
"home": "główna",
|
||||
"comingSoon": "już wkrótce…",
|
||||
"reset": "zresetuj",
|
||||
"channel_one": "kanał",
|
||||
"channel_few": "kanałów",
|
||||
"channel_many": "kanałów",
|
||||
"disable": "wyłącz",
|
||||
"sortOrder": "kolejność",
|
||||
"none": "żaden",
|
||||
"menu": "menu",
|
||||
"restartRequired": "wymagany restart",
|
||||
"previousSong": "poprzedni $t(entity.track_one)",
|
||||
"noResultsFromQuery": "kolejka zwróciła brak wyników",
|
||||
"quit": "wyjdź",
|
||||
"expand": "rozszerz",
|
||||
"search": "szukaj",
|
||||
"saveAs": "zapisz jako",
|
||||
"disc": "płyta",
|
||||
"yes": "tak",
|
||||
"random": "losowy",
|
||||
"size": "wielkość",
|
||||
"biography": "biografia",
|
||||
"backward": "wstecz",
|
||||
"left": "lewo",
|
||||
"currentSong": "obecnie $t(entity.track_one)",
|
||||
"collapse": "zwiń",
|
||||
"gap": "luka",
|
||||
"manage": "zarządzaj",
|
||||
"decrease": "obniż",
|
||||
"path": "ścieżka",
|
||||
"center": "środkowy",
|
||||
"note": "notatka"
|
||||
},
|
||||
"entity": {
|
||||
"genre_one": "gatunek",
|
||||
"genre_few": "gatunków",
|
||||
"genre_many": "gatunków",
|
||||
"artist_one": "artysta",
|
||||
"artist_few": "artystów",
|
||||
"artist_many": "artystów",
|
||||
"albumArtist_one": "artysta albumu",
|
||||
"albumArtist_few": "artysta albumów",
|
||||
"albumArtist_many": "artysta albumów",
|
||||
"albumWithCount_one": "{{count}} album",
|
||||
"albumWithCount_few": "{{count}} albumów",
|
||||
"albumWithCount_many": "{{count}} albumów",
|
||||
"favorite_one": "ulubiony",
|
||||
"favorite_few": "ulubione",
|
||||
"favorite_many": "ulubione",
|
||||
"artistWithCount_one": "{{count}} artysta",
|
||||
"artistWithCount_few": "{{count}} artystów",
|
||||
"artistWithCount_many": "{{count}} artystów",
|
||||
"folder_one": "katalog",
|
||||
"folder_few": "katalogi",
|
||||
"folder_many": "katalogów",
|
||||
"album_one": "album",
|
||||
"album_few": "albumów",
|
||||
"album_many": "albumów",
|
||||
"playlistWithCount_one": "{{count}} lista odtwarzania",
|
||||
"playlistWithCount_few": "{{count}} listy odtwarzania",
|
||||
"playlistWithCount_many": "{{count}} list odtwarzania",
|
||||
"playlist_one": "lista odtwarzania",
|
||||
"playlist_few": "listy odtwarzania",
|
||||
"playlist_many": "list odtwarzania",
|
||||
"folderWithCount_one": "{{count}} katalog",
|
||||
"folderWithCount_few": "{{count}} katalogi",
|
||||
"folderWithCount_many": "{{count}} katalogów",
|
||||
"track_one": "utwór",
|
||||
"track_few": "utwory",
|
||||
"track_many": "utworów",
|
||||
"albumArtistCount_one": "{{count}} wykonawca albumu",
|
||||
"albumArtistCount_few": "{{count}} wykonawców albumu",
|
||||
"albumArtistCount_many": "{{count}} wykonawców albumu",
|
||||
"smartPlaylist": "inteligentna $t(entity.playlist_one)",
|
||||
"genreWithCount_one": "{{count}} gatunek",
|
||||
"genreWithCount_few": "{{count}} gatunki",
|
||||
"genreWithCount_many": "{{count}} gatunków",
|
||||
"trackWithCount_one": "{{count}} utwór",
|
||||
"trackWithCount_few": "{{count}} utwory",
|
||||
"trackWithCount_many": "{{count}} utworów"
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "uruchom ponownie serwer aby używać nowego portu",
|
||||
"systemFontError": "wystąpił błąd podczas próby pobrania czcionek systemowych",
|
||||
"playbackError": "wystąpił błąd podczas próby odtwarzania mediów",
|
||||
"endpointNotImplementedError": "punkt końcowy {{endpoint}} nie został zaimplementowany dla {{serverType}}",
|
||||
"remotePortError": "wystąpił problem podczas ustawiania portu dla zdalnego serwera",
|
||||
"serverRequired": "wymagany serwer",
|
||||
"authenticationFailed": "uwierzytelnianie nie powiodło się",
|
||||
"apiRouteError": "nie można wykonać żądania",
|
||||
"genericError": "wystąpił błąd",
|
||||
"credentialsRequired": "wymagane poświadczenia",
|
||||
"sessionExpiredError": "twoja sesja wygasła",
|
||||
"remoteEnableError": "wystąpił błąd podczas próby $t(common.enable) zdalnego serwera",
|
||||
"localFontAccessDenied": "dostęp do lokalnych czcionek odrzucony",
|
||||
"serverNotSelectedError": "nie zaznaczono serwera",
|
||||
"remoteDisableError": "wystąpił błąd podczas próby $t(common.disable) zdalnego serwera",
|
||||
"mpvRequired": "wymagane MPV",
|
||||
"audioDeviceFetchError": "wystąpił błąd podczas próby znalezienia urządzeń dźwiękowych",
|
||||
"invalidServer": "nieprawidłowy serwer",
|
||||
"loginRateError": "zbyt dużo prób logowania, poczekaj chwilę i spróbuj ponownie"
|
||||
},
|
||||
"filter": {
|
||||
"mostPlayed": "najczęściej odtwarzane",
|
||||
"playCount": "liczba odtworzeń",
|
||||
"isCompilation": "jest kompilacją",
|
||||
"recentlyPlayed": "ostatnio odtwarzane",
|
||||
"isRated": "jest ocenione",
|
||||
"title": "tytuł",
|
||||
"rating": "ocena",
|
||||
"search": "wyszukaj",
|
||||
"bitrate": "bitrate",
|
||||
"recentlyAdded": "ostatnio dodane",
|
||||
"note": "notatka",
|
||||
"name": "nazwa",
|
||||
"dateAdded": "dodano datę",
|
||||
"releaseDate": "data premiery",
|
||||
"communityRating": "ocena społeczności",
|
||||
"path": "ścieżka",
|
||||
"favorited": "ulubione",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"isRecentlyPlayed": "było niedawno odtwarzane",
|
||||
"isFavorited": "jest ulubione",
|
||||
"bpm": "bpm",
|
||||
"releaseYear": "rok wydania",
|
||||
"disc": "płyta",
|
||||
"biography": "biografia",
|
||||
"songCount": "liczba utworów",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"duration": "długość",
|
||||
"random": "losowy",
|
||||
"lastPlayed": "ostatnio odtwarzane",
|
||||
"toYear": "do roku",
|
||||
"fromYear": "od roku",
|
||||
"criticRating": "ocena krytyków",
|
||||
"trackNumber": "utwór",
|
||||
"comment": "komentarz",
|
||||
"recentlyUpdated": "ostatnio aktualizowane",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"owner": "$t(common.owner)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"albumCount": "liczba $t(entity.album_other)",
|
||||
"id": "id",
|
||||
"isPublic": "jest publiczny",
|
||||
"album": "$t(entity.album_one)"
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
"title": "usuń $t(entity.playlist_one)",
|
||||
"success": "$t(entity.playlist_one) usunięto pomyślnie",
|
||||
"input_confirm": "wpisz nazwę $t(entity.playlist_one) aby potwierdzić"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
"title": "utwórz $t(entity.playlist_one)",
|
||||
"input_public": "publiczny",
|
||||
"input_name": "$t(common.name)",
|
||||
"success": "$t(entity.playlist_one) utworzono pomyślnie",
|
||||
"input_owner": "$t(common.owner)"
|
||||
},
|
||||
"addServer": {
|
||||
"title": "dodaj serwer",
|
||||
"input_username": "nazwa użytkownika",
|
||||
"input_url": "adres",
|
||||
"input_password": "hasło",
|
||||
"input_legacyAuthentication": "umożliw starsze uwierzytelnianie",
|
||||
"input_name": "nazwa serwera",
|
||||
"success": "serwer dodany pomyślnie",
|
||||
"input_savePassword": "zapisz hasło",
|
||||
"ignoreSsl": "zignoruj ssl $t(common.restartRequired)",
|
||||
"ignoreCors": "zignoruj cors $t(common.restartRequired)",
|
||||
"error_savePassword": "wystąpił błąd podczas próby zapisania hasła"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"success": "dodano {{message}} $t(entity.song_other) do {{numOfPlaylists}} $t(entity.playlist_other)",
|
||||
"title": "dodano do $t(entity.playlist_one)",
|
||||
"input_skipDuplicates": "pomiń duplikaty",
|
||||
"input_playlists": "$t(entity.playlist_other)"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "uaktualnij serwer",
|
||||
"success": "serwer zaaktualizowany pomyślnie"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "dopasuj wszystkie",
|
||||
"input_optionMatchAny": "dopasuj dowolne"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"input_name": "$t(common.name)",
|
||||
"input_artist": "$t(entity.artist_one)",
|
||||
"title": "wyszukiwanie tekstów"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "edytuj $t(entity.playlist_one)"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"showLyricMatch": "pokaż dopasowanie tekstu",
|
||||
"dynamicBackground": "dynamiczne tło",
|
||||
"synchronized": "zsynchronizowane",
|
||||
"followCurrentLyric": "podążaj za aktualnym tekstem",
|
||||
"opacity": "przezroczystość",
|
||||
"lyricSize": "rozmiar tekstu",
|
||||
"showLyricProvider": "pokaż dostawce tekstu",
|
||||
"unsynchronized": "niezsynchronizowane",
|
||||
"lyricAlignment": "wyrównaj tekst",
|
||||
"useImageAspectRatio": "użyj współczynnika proporcji obrazu",
|
||||
"lyricGap": "odstępy tekstu"
|
||||
},
|
||||
"upNext": "następny",
|
||||
"lyrics": "tekst",
|
||||
"related": "powiązane"
|
||||
},
|
||||
"appMenu": {
|
||||
"selectServer": "wybierz serwer",
|
||||
"version": "wersja {{version}}",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"manageServers": "zarządzaj serwerami",
|
||||
"expandSidebar": "rozwiń pasek boczny",
|
||||
"collapseSidebar": "zwiń pasek boczny",
|
||||
"openBrowserDevtools": "otwórz narzędzia deweloperskie przeglądarki",
|
||||
"quit": "$t(common.quit)",
|
||||
"goBack": "do tyłu",
|
||||
"goForward": "do przodu"
|
||||
},
|
||||
"contextMenu": {
|
||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||
"addToFavorites": "$t(action.addToFavorites)",
|
||||
"setRating": "$t(action.setRating)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"createPlaylist": "$t(action.createPlaylist)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"addNext": "$t(player.addNext)",
|
||||
"deselectAll": "$t(action.deselectAll)",
|
||||
"addLast": "$t(player.addLast)",
|
||||
"addFavorite": "$t(action.addToFavorites)",
|
||||
"play": "$t(player.play)",
|
||||
"numberSelected": "zaznaczono {{count}}",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "więcej od $t(entity.genre_one)",
|
||||
"moreFromGeneric": "więcej od {{item}}"
|
||||
},
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)"
|
||||
},
|
||||
"sidebar": {
|
||||
"nowPlaying": "teraz odtwarzane",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"tracks": "$t(entity.track_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"home": "$t(common.home)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"albumArtists": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "najczęściej odtwarzane",
|
||||
"newlyAdded": "niedawno dodane",
|
||||
"title": "$t(common.home)",
|
||||
"explore": "przeglądaj z biblioteki",
|
||||
"recentlyPlayed": "ostatnio odtwarzane"
|
||||
},
|
||||
"setting": {
|
||||
"playbackTab": "odtworzenia",
|
||||
"generalTab": "ogólne",
|
||||
"hotkeysTab": "skróty klawiszowe",
|
||||
"windowTab": "okno"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"serverCommands": "komendy serwera",
|
||||
"goToPage": "przejdź do strony",
|
||||
"searchFor": "wyszukaj {{query}}"
|
||||
},
|
||||
"title": "komendy"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
"repeat_all": "powtarzaj wszystkie",
|
||||
"stop": "stop",
|
||||
"repeat": "powtarzaj",
|
||||
"queue_remove": "usuń zaznaczone",
|
||||
"playRandom": "odtwarzaj losowe",
|
||||
"skip": "pomiń",
|
||||
"previous": "poprzedni",
|
||||
"toggleFullscreenPlayer": "przełącz odtwarzacz pełnoekranowy",
|
||||
"skip_back": "przeskocz do tyłu",
|
||||
"favorite": "ulubione",
|
||||
"next": "następny",
|
||||
"shuffle": "losowa kolejność",
|
||||
"playbackFetchNoResults": "nie znaleziono utworów",
|
||||
"playbackFetchInProgress": "wczytywanie utworów…",
|
||||
"addNext": "dodaj następny",
|
||||
"playbackSpeed": "prędkość odtwarzania",
|
||||
"playbackFetchCancel": "to potrwa chwilę... zamknij powiadomienie aby anulować",
|
||||
"play": "odtwarzaj",
|
||||
"repeat_off": "powtarzanie wyłączone",
|
||||
"pause": "wstrzymaj",
|
||||
"queue_clear": "wyczyść kolejke",
|
||||
"muted": "wyciszone",
|
||||
"unfavorite": "usuń z ulubionych",
|
||||
"queue_moveToTop": "przesuń zaznaczone na dół",
|
||||
"queue_moveToBottom": "przesuń zaznaczone na górę",
|
||||
"shuffle_off": "losowa kolejność wyłączona",
|
||||
"addLast": "dodaj na końcu",
|
||||
"mute": "wycisz",
|
||||
"skip_forward": "przeskocz do przodu"
|
||||
},
|
||||
"setting": {
|
||||
"crossfadeStyle_description": "wybierz styl przenikania, który ma być używany do odtwarzania dźwięku",
|
||||
"hotkey_skipBackward": "przeskocz do tyłu",
|
||||
"audioDevice_description": "wybierz urządzenie dźwiękowe używane do odtwarzania (tylko odtwarzacz przeglądarkowy)",
|
||||
"hotkey_playbackPause": "wstrzymaj",
|
||||
"hotkey_volumeUp": "podgłoś",
|
||||
"discordIdleStatus_description": "kiedy włączony, aktualizuje stan kiedy odtwarzacz jest bezczynny",
|
||||
"lyricFetch": "pobierz teksty z internetu",
|
||||
"enableRemote_description": "umożliwia serwerowi zdalnego sterowania zezwalanie innym urządzeniom na sterowanie aplikacją",
|
||||
"fontType_optionSystem": "czcionka systemowa",
|
||||
"hotkey_favoriteCurrentSong": "ulubiona $t(common.currentSong)",
|
||||
"crossfadeStyle": "styl przenikania",
|
||||
"hotkey_zoomIn": "przybliż",
|
||||
"hotkey_browserForward": "przeglądarka w przód",
|
||||
"audioExclusiveMode_description": "włącz wyłączny tryb wyjścia. W tym trybie, system zwykle jest zablokowany i może odtwarzać tylko pliki mpv",
|
||||
"discordUpdateInterval": "{{discord}} interwał aktualizacji obszernej obecności",
|
||||
"fontType_optionBuiltIn": "wbudowana czcionka",
|
||||
"hotkey_playbackPlayPause": "odtwarzaj / wstrzymaj",
|
||||
"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}})",
|
||||
"gaplessAudio": "dźwięk bez przerw",
|
||||
"hotkey_playbackPlay": "odtwarzaj",
|
||||
"hotkey_togglePreviousSongFavorite": "dodaj $t(common.previousSong) do ulubionych",
|
||||
"hotkey_volumeDown": "przycisz",
|
||||
"hotkey_unfavoritePreviousSong": "usuń $t(common.previousSong) z ulubionych",
|
||||
"audioPlayer_description": "wybierz odtwarzacz dźwięku który ma być używany do odtwarzania",
|
||||
"globalMediaHotkeys": "globalne skróty klawiszowe multimediów",
|
||||
"hotkey_globalSearch": "globalne wyszukiwanie",
|
||||
"gaplessAudio_description": "ustaw dźwięk bez przerw dla mpv",
|
||||
"disableAutomaticUpdates": "wyłącz automatyczne aktualizacje",
|
||||
"exitToTray_description": "zamknij aplikację do zasobnika systemowego",
|
||||
"followLyric_description": "przewiń tekst do obecnego momentu",
|
||||
"hotkey_favoritePreviousSong": "ulubiona $t(common.previousSong)",
|
||||
"lyricOffset": "opóźnienie tekstu (ms)",
|
||||
"discordUpdateInterval_description": "czas w sekundach pomiędzy każdą aktualizacją (minimalnie 15 sekund)",
|
||||
"fontType_optionCustom": "czcionka niestandardowa",
|
||||
"audioExclusiveMode": "wyłączny tryb audio",
|
||||
"lyricFetchProvider": "dostawcy tekstów internetowych",
|
||||
"language_description": "ustaw język dla aplikacji $t(common.restartRequired)",
|
||||
"hotkey_rate3": "oceń na 3 gwiazdki",
|
||||
"font": "czcionka",
|
||||
"hotkey_toggleFullScreenPlayer": "przełącz tryb pełnoekranowy",
|
||||
"hotkey_localSearch": "wyszukiwanie na stronie",
|
||||
"hotkey_toggleQueue": "przełącz kolejkę",
|
||||
"hotkey_rate5": "oceń na 5 gwiazdek",
|
||||
"hotkey_playbackPrevious": "poprzedni utwór",
|
||||
"crossfadeDuration_description": "ustaw czas trwania efektu przenikania",
|
||||
"language": "język",
|
||||
"hotkey_toggleShuffle": "przełącz kolejność losową",
|
||||
"discordRichPresence_description": "włącz status odtwarzania w {{discord}} obszernie obecny. Klucze obrazów to {{icon}}, {{playing}} i {{paused}}. ",
|
||||
"audioDevice": "urządzenia dźwiękowe",
|
||||
"hotkey_rate2": "oceń na 2 gwiazdki",
|
||||
"exitToTray": "zamknij do zasobnika",
|
||||
"hotkey_rate4": "oceń na 4 gwiazdki",
|
||||
"enableRemote": "włącz zdalną kontrolę serwera",
|
||||
"fontType_description": "wbudowana czcionka pozwala na wybranie czcionki dostarczonej z Feishin. systemowa czcionka pozwala na wybranie czcionki dostarczonej przez system operacyjny. niestandardowa czcionka pozwala na wybranie własnej czcionki",
|
||||
"accentColor": "kolor akcentujący",
|
||||
"accentColor_description": "ustaw kolor akcentujący dla aplikacji",
|
||||
"floatingQueueArea": "pokaż pływającą kolejkę podczas najechania kursorem",
|
||||
"hotkey_toggleRepeat": "przełącz powtarzanie",
|
||||
"lyricOffset_description": "opóźnienie tekstu przez podaną liczbę milisekund",
|
||||
"fontType": "typ czcionki",
|
||||
"applicationHotkeys": "skróty klawiszowe aplikacji",
|
||||
"hotkey_playbackNext": "następny utwór",
|
||||
"lyricFetch_description": "pobierz teksty z rozmaitych źródeł internetowych",
|
||||
"lyricFetchProvider_description": "wybierz dostawców internetowych dla tekstów. zapytania będą wykonywane według podanej kolejności",
|
||||
"globalMediaHotkeys_description": "włącz lub wyłącz używanie systemowych skrótów klawiszowych do kontroli odtwarzania",
|
||||
"customFontPath": "niestandardowa ścieżka czcionki",
|
||||
"followLyric": "podążaj za tekstem",
|
||||
"crossfadeDuration": "czas trwania przenikania",
|
||||
"discordIdleStatus": "pokaż obszerne informacje w stanie bezczynności",
|
||||
"audioPlayer": "odtwarzacz dźwięku",
|
||||
"hotkey_zoomOut": "oddal",
|
||||
"hotkey_unfavoriteCurrentSong": "usuń $t(common.currentSong) z ulubionych",
|
||||
"hotkey_rate0": "wyczyść oceny",
|
||||
"discordApplicationId": "id aplikacji {{discord}}",
|
||||
"applicationHotkeys_description": "ustaw skróty klawiszowe aplikacji. przełącz pole wyboru aby ustawić skrót globalny (tylko komputery)",
|
||||
"floatingQueueArea_description": "wyświetl ikonę najechania kursorem po prawej stronie ekranu, aby wyświetlić kolejkę odtwarzania",
|
||||
"hotkey_volumeMute": "wycisz",
|
||||
"hotkey_toggleCurrentSongFavorite": "dodaj $t(common.currentSong) do ulubionych",
|
||||
"hotkey_browserBack": "przeglądarka wstecz",
|
||||
"minimizeToTray": "zminimalizuj do zasobnika",
|
||||
"customFontPath_description": "ustaw ścieżkę dla niestandardowych czcionek dla aplikacji",
|
||||
"gaplessAudio_optionWeak": "słabe (rekomendowane)",
|
||||
"hotkey_playbackStop": "zatrzymaj",
|
||||
"discordRichPresence": "{{discord}} obszernie obecny",
|
||||
"font_description": "ustaw czcionkę dla aplikacji",
|
||||
"mpvExecutablePath_help": "jedna na linnię",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"minimumScrobblePercentage": "minimalny czas trwania scrobble (procentowy)",
|
||||
"mpvExecutablePath_description": "ustaw ścieżkę dla plików wykonywalnych mpv",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"minimizeToTray_description": "zminimalizuj aplikację do zasobnika systemowego",
|
||||
"remotePassword": "hasło dla serwera zdalnej kontroli",
|
||||
"playbackStyle_optionCrossFade": "przenikanie",
|
||||
"mpvExtraParameters": "parametry mpv",
|
||||
"playbackStyle": "styl odtwarzania",
|
||||
"playbackStyle_description": "wybierz styl odtwarzania dla odtwarzacza dźwięku",
|
||||
"mpvExecutablePath": "ścieżka pliku wykonywalnego mpv",
|
||||
"playButtonBehavior_description": "ustaw domyślne zachowanie dla przycisku odtwarzania kiedy piosenka zostanie dodana do kolejki",
|
||||
"minimumScrobblePercentage_description": "minimalny czas odtwarzania piosenki który musi upłynąć aby uznać ją za scrobble",
|
||||
"minimumScrobbleSeconds_description": "minimalny czas odtwarzania piosenki w sekundach jaki musi upłynąć aby uznać ją za scrobble",
|
||||
"playButtonBehavior": "zachowanie przycisku odtwarzania",
|
||||
"playbackStyle_optionNormal": "normalny",
|
||||
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
|
||||
"minimumScrobbleSeconds": "minimalne scrobble (sekund)",
|
||||
"remotePort_description": "ustaw port dla serwera zdalnej kontroli",
|
||||
"replayGainMode_description": "dostosuj wzmocnienie dźwięku zgodnie z wartościami {{ReplayGain}} przechowywanymi w metadanych do pliku",
|
||||
"replayGainFallback": "rezerwowy {{ReplayGain}}",
|
||||
"sidebarCollapsedNavigation_description": "pokaż lub ukryj nawigację na zwiniętym pasku bocznym",
|
||||
"skipDuration": "czas trwania pominięcia",
|
||||
"showSkipButtons": "pokaż przyciski pomijania",
|
||||
"scrobble": "scrobble",
|
||||
"skipDuration_description": "ustaw czas pominięcia kiedy zostanie użyty przycisk pominięcia na pasku odtwarzania",
|
||||
"replayGainClipping_description": "Zapobiegaj wzmocnieniu spowodowanemu przez {{ReplayGain}} na automatyczne obniżanie wzmocnienia",
|
||||
"replayGainPreamp": "przedwzmacniacz {{ReplayGain}} (db)",
|
||||
"sampleRate": "częstotliwość próbkowania",
|
||||
"sidePlayQueueStyle_optionAttached": "przyłączony",
|
||||
"sidebarConfiguration": "konfiguracja paska bocznego",
|
||||
"sampleRate_description": "wybierz wyjściową częstotliwość próbkowania, która ma być używana, jeśli wybrana częstotliwość próbkowania różni się od częstotliwości bieżącego utworu",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"replayGainClipping": "wzmocnienie {{ReplayGain}}",
|
||||
"scrobble_description": "odtwarzanie scrobble na serwerze multimediów",
|
||||
"sidePlayQueueStyle": "boczny styl kolejki odtwarzania",
|
||||
"remoteUsername_description": "ustaw nazwę użytkownika dla serwera zdalnej kontroli. Jeśli nazwa użytkownika i hasło są puste, autoryzacja będzie wyłączona",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"remotePassword_description": "ustawia hasło dla serwera zdalnego sterowania. Te poświadczenia są domyślnie przesyłane w sposób niezabezpieczony, dlatego należy użyć unikalnego hasła na którym ci nie zależy",
|
||||
"showSkipButtons_description": "pokaż lub ukryj przyciski pomijania na pasku odtwarzacza",
|
||||
"showSkipButton_description": "pokaż lub ukryj przyciski pomijania na pasku odtwarzacza",
|
||||
"savePlayQueue": "zapisz kolejkę odtwarzania",
|
||||
"sidebarPlaylistList_description": "pokaż lub ukryj listę odtwarzania na pasku bocznym",
|
||||
"sidePlayQueueStyle_description": "ustaw boczny styl kolejki odtwarzania",
|
||||
"replayGainMode": "tryb {{ReplayGain}}",
|
||||
"replayGainFallback_description": "wzmocnienie w db do użycia w przypadku kiedy plik nie ma tagu {{ReplayGain}}",
|
||||
"replayGainPreamp_description": "dostosuj wzmocnienie przedwzmacniacza zastosowane do wartości {{ReplayGain}}",
|
||||
"sidebarConfiguration_description": "wybierz pozycje i ustaw je w kolejności w jakiej mają się pokazywać na pasku bocznym",
|
||||
"remotePort": "port dla serwera zdalnej kontroli",
|
||||
"sidePlayQueueStyle_optionDetached": "odłączony",
|
||||
"remoteUsername": "nazwa użytkownika serwera zdalnej kontroli",
|
||||
"showSkipButton": "pokaż przyciski pomijania",
|
||||
"sidebarPlaylistList": "lista odtwarzania na pasku bocznym",
|
||||
"sidebarCollapsedNavigation": "nawigacja na pasku bocznym (zwinięta)",
|
||||
"savePlayQueue_description": "zapisz kolejkę odtwarzania kiedy aplikacja jest zamykana i wznów ją kiedy aplikacja jest otwierana",
|
||||
"volumeWheelStep_description": "wartość zmiany glośności w czasie używania pokrętła myszy na pasku głośności",
|
||||
"theme_description": "ustaw motyw dla aplikacji",
|
||||
"themeLight": "motyw (jasny)",
|
||||
"zoom": "procentowe przybliżenie",
|
||||
"themeDark_description": "ustaw ciemny motyw do używania w aplikacji",
|
||||
"themeLight_description": "ustaw jasny motyw do używania w aplikacji",
|
||||
"zoom_description": "ustaw procentowe przybliżenie dla aplikacji",
|
||||
"theme": "motyw",
|
||||
"skipPlaylistPage_description": "przechodząc do listy odtwarzania, przejdź do strony listy odtwarzania zamiast do strony domyślnej",
|
||||
"volumeWheelStep": "krok pokrętła głośności",
|
||||
"windowBarStyle": "styl paska okna",
|
||||
"useSystemTheme_description": "podążaj za systemem z ustawieniami jasnego lub ciemnego motywu",
|
||||
"skipPlaylistPage": "pomiń stronę list odtwarzania",
|
||||
"themeDark": "motyw (ciemny)",
|
||||
"windowBarStyle_description": "wybierz styl paska okna",
|
||||
"useSystemTheme": "użyj motywu systemowego"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"view": {
|
||||
"card": "karta",
|
||||
"table": "tabela",
|
||||
"poster": "plakat"
|
||||
},
|
||||
"general": {
|
||||
"displayType": "typ wyświetlania",
|
||||
"gap": "$t(common.gap)",
|
||||
"tableColumns": "kolumny tabeli",
|
||||
"autoFitColumns": "automatyczne dopasowanie kolumn",
|
||||
"size": "$t(common.size)"
|
||||
},
|
||||
"label": {
|
||||
"releaseDate": "data premiery",
|
||||
"title": "$t(common.title)",
|
||||
"duration": "$t(common.duration)",
|
||||
"titleCombined": "$t(common.title) (połączony)",
|
||||
"dateAdded": "data dodania",
|
||||
"size": "$t(common.size)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"lastPlayed": "ostatnio odtwarzane",
|
||||
"trackNumber": "numer utworu",
|
||||
"rowIndex": "indeks wiersza",
|
||||
"rating": "$t(common.rating)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"note": "$t(common.note)",
|
||||
"biography": "$t(common.biography)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"playCount": "liczba odtworzeń",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"discNumber": "numer płyty",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"year": "$t(common.year)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"comment": "komentarz",
|
||||
"album": "album",
|
||||
"rating": "ocena",
|
||||
"favorite": "ulubione",
|
||||
"playCount": "odtwarzane",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"releaseYear": "rok",
|
||||
"lastPlayed": "ostatnio odtwarzane",
|
||||
"biography": "biografia",
|
||||
"releaseDate": "data premiery",
|
||||
"bitrate": "bitrate",
|
||||
"title": "tytuł",
|
||||
"bpm": "bpm",
|
||||
"dateAdded": "data dodania",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"trackNumber": "utwór",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"albumArtist": "artysta albumu",
|
||||
"path": "ścieżka",
|
||||
"discNumber": "płyta",
|
||||
"channels": "$t(common.channel_other)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"common": {
|
||||
"backward": "voltar",
|
||||
"areYouSure": "tem certeza?",
|
||||
"add": "adicionar",
|
||||
"ascending": "ascendente",
|
||||
"center": "centro",
|
||||
"cancel": "cancelar",
|
||||
"bitrate": "taxa de bits",
|
||||
"action_one": "ação",
|
||||
"action_many": "ações",
|
||||
"action_other": "(n == 0 || n == 1) ? ação : ações",
|
||||
"biography": "biografia"
|
||||
},
|
||||
"action": {
|
||||
"goToPage": "vá para página",
|
||||
"addToFavorites": "adicionar em $t(entity.favorite_other)",
|
||||
"viewPlaylists": "ver $t(entity.playlist_other)",
|
||||
"setRating": "definir classificação",
|
||||
"moveToTop": "mover para o topo",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"removeFromQueue": "remover da fila",
|
||||
"moveToBottom": "mover para baixo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"action": {
|
||||
"editPlaylist": "редактировать $t(entity.playlist_one)",
|
||||
"goToPage": "перейти на страницу",
|
||||
"moveToTop": "вверх",
|
||||
"clearQueue": "очистить очередь",
|
||||
"addToFavorites": "добавить в $t(entity.favorite_other)",
|
||||
"addToPlaylist": "добавить в $t(entity.playlist_one)",
|
||||
"createPlaylist": "создать $t(entity.playlist_one)",
|
||||
"removeFromPlaylist": "удалить из $t(entity.playlist_one)",
|
||||
"viewPlaylists": "просмотреть $t(entity.playlist_other)",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"deletePlaylist": "удалить $t(entity.playlist_one)",
|
||||
"removeFromQueue": "удалить из очереди",
|
||||
"deselectAll": "снять выделение",
|
||||
"moveToBottom": "вниз",
|
||||
"setRating": "оценить",
|
||||
"toggleSmartPlaylistEditor": "вкл/выкл $t(entity.smartPlaylist) редактор",
|
||||
"removeFromFavorites": "удалить из $t(entity.favorite_other)"
|
||||
},
|
||||
"common": {
|
||||
"backward": "назад",
|
||||
"increase": "увеличить",
|
||||
"rating": "рейтинг",
|
||||
"bpm": "ударов в мин.",
|
||||
"refresh": "обновить",
|
||||
"unknown": "неизвестно",
|
||||
"areYouSure": "вы уверены?",
|
||||
"edit": "изменить",
|
||||
"favorite": "любимый",
|
||||
"left": "лево",
|
||||
"save": "сохранить",
|
||||
"right": "право",
|
||||
"currentSong": "текущий $t(entity.track_one)",
|
||||
"collapse": "закрыть",
|
||||
"trackNumber": "трек",
|
||||
"descending": "убывающий",
|
||||
"add": "добавить",
|
||||
"gap": "промежуток",
|
||||
"ascending": "возрастающий",
|
||||
"dismiss": "отклонить",
|
||||
"year": "год",
|
||||
"manage": "управлять",
|
||||
"limit": "лимит",
|
||||
"minimize": "минимизировать",
|
||||
"modified": "изменено",
|
||||
"duration": "продолжительность",
|
||||
"name": "имя",
|
||||
"maximize": "максимизировать",
|
||||
"decrease": "уменьшить",
|
||||
"ok": "ок",
|
||||
"description": "описание",
|
||||
"configure": "настроить",
|
||||
"path": "путь",
|
||||
"center": "центр",
|
||||
"no": "нет",
|
||||
"owner": "владелец",
|
||||
"enable": "включить",
|
||||
"clear": "очистить",
|
||||
"forward": "вперёд",
|
||||
"delete": "удалить",
|
||||
"cancel": "отменить",
|
||||
"forceRestartRequired": "перезапустите приложение, чтобы применить изменения... закройте уведомление, чтобы перезапустить приложение",
|
||||
"setting": "настройка",
|
||||
"version": "версия",
|
||||
"title": "название",
|
||||
"filter_one": "фильтр",
|
||||
"filter_few": "фильтра",
|
||||
"filter_many": "фильтров",
|
||||
"filters": "фильтры",
|
||||
"create": "создать",
|
||||
"bitrate": "битрейт",
|
||||
"saveAndReplace": "сохранить и заменить",
|
||||
"action_one": "действие",
|
||||
"action_few": "действия",
|
||||
"action_many": "действий",
|
||||
"playerMustBePaused": "воспроизведение должно быть остановлено",
|
||||
"confirm": "подтвердить",
|
||||
"resetToDefault": "сбросить к настройкам по умолчанию",
|
||||
"home": "домой",
|
||||
"comingSoon": "скоро будет…",
|
||||
"reset": "сбросить",
|
||||
"channel_one": "канал",
|
||||
"channel_few": "канала",
|
||||
"channel_many": "каналов",
|
||||
"disable": "выключить",
|
||||
"sortOrder": "порядок",
|
||||
"menu": "меню",
|
||||
"restartRequired": "необходим перезапуск приложения",
|
||||
"previousSong": "предыдущий $t(entity.track_one)",
|
||||
"noResultsFromQuery": "нет результатов",
|
||||
"quit": "выйти",
|
||||
"expand": "расширить",
|
||||
"search": "поиск",
|
||||
"saveAs": "сохранить как",
|
||||
"disc": "диск",
|
||||
"yes": "да",
|
||||
"random": "случайный",
|
||||
"size": "размер",
|
||||
"biography": "биография",
|
||||
"note": "заметка",
|
||||
"none": "нет"
|
||||
},
|
||||
"entity": {
|
||||
"album_one": "альбом",
|
||||
"album_few": "альбома",
|
||||
"album_many": "альбомов",
|
||||
"genre_one": "жанр",
|
||||
"genre_few": "жанра",
|
||||
"genre_many": "жанров",
|
||||
"playlistWithCount_one": "{{count}} плейлист",
|
||||
"playlistWithCount_few": "{{count}} плейлиста",
|
||||
"playlistWithCount_many": "{{count}} плейлистов",
|
||||
"playlist_one": "плейлист",
|
||||
"playlist_few": "плейлиста",
|
||||
"playlist_many": "плейлистов",
|
||||
"artist_one": "автор",
|
||||
"artist_few": "автора",
|
||||
"artist_many": "авторов",
|
||||
"folderWithCount_one": "{{count}} папка",
|
||||
"folderWithCount_few": "{{count}} папки",
|
||||
"folderWithCount_many": "{{count}} папок",
|
||||
"albumArtist_one": "автор альбома",
|
||||
"albumArtist_few": "автора альбома",
|
||||
"albumArtist_many": "авторов альбома",
|
||||
"track_one": "трек",
|
||||
"track_few": "трека",
|
||||
"track_many": "треков",
|
||||
"albumArtistCount_one": "{{count}} автор альбома",
|
||||
"albumArtistCount_few": "{{count}} автора альбома",
|
||||
"albumArtistCount_many": "{{count}} авторов альбома",
|
||||
"albumWithCount_one": "{{count}} альбом",
|
||||
"albumWithCount_few": "{{count}} альбома",
|
||||
"albumWithCount_many": "{{count}} альбомов",
|
||||
"favorite_one": "любимый",
|
||||
"favorite_few": "любимых",
|
||||
"favorite_many": "любимые",
|
||||
"artistWithCount_one": "{{count}} автор",
|
||||
"artistWithCount_few": "{{count}} автора",
|
||||
"artistWithCount_many": "{{count}} авторов",
|
||||
"folder_one": "папка",
|
||||
"folder_few": "папки",
|
||||
"folder_many": "папок",
|
||||
"smartPlaylist": "умный $t(entity.playlist_one)",
|
||||
"genreWithCount_one": "{{count}} жанр",
|
||||
"genreWithCount_few": "{{count}} жанра",
|
||||
"genreWithCount_many": "{{count}} жанров",
|
||||
"trackWithCount_one": "{{count}} трек",
|
||||
"trackWithCount_few": "{{count}} трека",
|
||||
"trackWithCount_many": "{{count}} треков"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"view": {
|
||||
"card": "карта",
|
||||
"table": "таблица",
|
||||
"poster": "постер"
|
||||
},
|
||||
"general": {
|
||||
"displayType": "тип отображения",
|
||||
"gap": "$t(common.gap)",
|
||||
"tableColumns": "столбцы таблицы",
|
||||
"autoFitColumns": "автоматически расставить столбцы",
|
||||
"size": "$t(common.size)"
|
||||
},
|
||||
"label": {
|
||||
"releaseDate": "дата выхода",
|
||||
"title": "$t(common.title)",
|
||||
"duration": "$t(common.duration)",
|
||||
"titleCombined": "$t(common.title) (комбинированный)",
|
||||
"dateAdded": "дата добавления",
|
||||
"size": "$t(common.size)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"lastPlayed": "последний",
|
||||
"trackNumber": "номер трека",
|
||||
"rowIndex": "индекс ряда",
|
||||
"rating": "$t(common.rating)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"note": "$t(common.note)",
|
||||
"biography": "$t(common.biography)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"playCount": "количество воспроизведений",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"discNumber": "номер диска",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"year": "$t(common.year)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"rating": "рейтинг",
|
||||
"favorite": "любимый",
|
||||
"playCount": "воспроизведений",
|
||||
"releaseYear": "год",
|
||||
"lastPlayed": "последний",
|
||||
"releaseDate": "дата выхода",
|
||||
"title": "название",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"trackNumber": "трек",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"path": "путь",
|
||||
"discNumber": "диск"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "перезапустить сервер для применения нового порта",
|
||||
"systemFontError": "произошла ошибка при попытке получить системные шрифты",
|
||||
"playbackError": "произошла ошибка при попытке проиграть медиа",
|
||||
"endpointNotImplementedError": "запрос {{endpoint} is not implemented for {{serverType}}",
|
||||
"remotePortError": "произошла ошибка при попытке установить порт удаленного сервера",
|
||||
"serverRequired": "необходим сервер",
|
||||
"authenticationFailed": "аутентификация завершилась с ошибкой",
|
||||
"apiRouteError": "невозможно выполнить запрос",
|
||||
"genericError": "произошла ошибка",
|
||||
"credentialsRequired": "необходимы учётные данные",
|
||||
"sessionExpiredError": "ваш сеанс истек",
|
||||
"remoteEnableError": "ошибка произошла при попытке $t(common.enable) удаленного сервера",
|
||||
"localFontAccessDenied": "не получилось получить доступ к шрифтам",
|
||||
"serverNotSelectedError": "не выбран сервер",
|
||||
"remoteDisableError": "ошибка произошла при попытке $t(common.disable) удаленного сервера",
|
||||
"mpvRequired": "Необходим MPV",
|
||||
"audioDeviceFetchError": "произошла ошибка с аудиоустройством",
|
||||
"invalidServer": "недействительный сервер",
|
||||
"loginRateError": "слишком много попыток входа, пожалуйста, попробуйте еще раз через несколько секунд"
|
||||
},
|
||||
"filter": {
|
||||
"isCompilation": "сборник",
|
||||
"isRated": "оценён",
|
||||
"bitrate": "битрейт",
|
||||
"dateAdded": "дата добавления",
|
||||
"communityRating": "рейтинг сообщества",
|
||||
"favorited": "любимый",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"isFavorited": "любимый",
|
||||
"bpm": "ударов в мин.",
|
||||
"disc": "диск",
|
||||
"biography": "биография",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"duration": "продолжительность",
|
||||
"fromYear": "из года",
|
||||
"criticRating": "рейтинг критиков"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
{
|
||||
"action": {
|
||||
"editPlaylist": "编辑 $t(entity.playlist_one)",
|
||||
"moveToTop": "跳至顶部",
|
||||
"clearQueue": "清空播放队列",
|
||||
"addToFavorites": "添加到$t(entity.favorite_other)",
|
||||
"addToPlaylist": "添加到$t(entity.playlist_one)",
|
||||
"createPlaylist": "创建$t(entity.playlist_one)",
|
||||
"removeFromPlaylist": "从$t(entity.playlist_one)移除",
|
||||
"viewPlaylists": "查看$t(entity.playlist_other)",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"deletePlaylist": "删除$t(entity.playlist_one)",
|
||||
"removeFromQueue": "从播放队列中移除",
|
||||
"deselectAll": "取消全选",
|
||||
"moveToBottom": "跳至底部",
|
||||
"setRating": "评分",
|
||||
"toggleSmartPlaylistEditor": "切换$t(entity.smartPlaylist)编辑器",
|
||||
"removeFromFavorites": "从$t(entity.favorite_other)移除",
|
||||
"goToPage": "转到页面"
|
||||
},
|
||||
"common": {
|
||||
"increase": "增高",
|
||||
"rating": "评分",
|
||||
"bpm": "bpm",
|
||||
"refresh": "刷新",
|
||||
"unknown": "未知",
|
||||
"edit": "编辑",
|
||||
"favorite": "收藏",
|
||||
"left": "左",
|
||||
"save": "保存",
|
||||
"right": "右",
|
||||
"currentSong": "当前$t(entity.track_one)",
|
||||
"collapse": "折叠",
|
||||
"trackNumber": "音轨编号",
|
||||
"descending": "降序",
|
||||
"add": "添加",
|
||||
"ascending": "升序",
|
||||
"dismiss": "忽略",
|
||||
"year": "年份",
|
||||
"manage": "管理",
|
||||
"minimize": "最小化",
|
||||
"modified": "已修改",
|
||||
"name": "名称",
|
||||
"maximize": "最大化",
|
||||
"decrease": "降低",
|
||||
"description": "描述",
|
||||
"configure": "配置",
|
||||
"path": "路径",
|
||||
"center": "中央",
|
||||
"owner": "所有者",
|
||||
"enable": "启用",
|
||||
"clear": "清空",
|
||||
"forward": "前进",
|
||||
"delete": "删除",
|
||||
"cancel": "取消",
|
||||
"forceRestartRequired": "重启应用使更改生效…关闭通知即可重启",
|
||||
"setting": "设置",
|
||||
"version": "版本",
|
||||
"title": "标题",
|
||||
"filter_other": "筛选",
|
||||
"filters": "筛选",
|
||||
"create": "创建",
|
||||
"bitrate": "比特率",
|
||||
"saveAndReplace": "保存并替换",
|
||||
"action_other": "操作",
|
||||
"confirm": "确认",
|
||||
"resetToDefault": "重置为默认",
|
||||
"home": "主页",
|
||||
"comingSoon": "即将上线…",
|
||||
"reset": "重置",
|
||||
"disable": "禁用",
|
||||
"menu": "菜单",
|
||||
"restartRequired": "需要重启应用",
|
||||
"previousSong": "上一首$t(entity.track_one)",
|
||||
"noResultsFromQuery": "未查询到匹配结果",
|
||||
"quit": "退出",
|
||||
"expand": "展开",
|
||||
"search": "搜索",
|
||||
"saveAs": "保存为",
|
||||
"random": "随机",
|
||||
"biography": "简介",
|
||||
"sortOrder": "顺序",
|
||||
"backward": "返回",
|
||||
"gap": "空隙",
|
||||
"limit": "限制",
|
||||
"duration": "时长",
|
||||
"ok": "好",
|
||||
"no": "否",
|
||||
"playerMustBePaused": "播放器须被暂停",
|
||||
"channel_other": "频道",
|
||||
"none": "无",
|
||||
"disc": "盘",
|
||||
"yes": "是",
|
||||
"size": "大小",
|
||||
"areYouSure": "是否继续?",
|
||||
"note": "注释"
|
||||
},
|
||||
"entity": {
|
||||
"albumArtist_other": "专辑艺术家",
|
||||
"albumArtistCount_other": "{{count}} 位专辑艺术家",
|
||||
"albumWithCount_other": "{{count}} 张专辑",
|
||||
"album_other": "专辑",
|
||||
"genre_other": "流派",
|
||||
"playlistWithCount_other": "{{count}} 个播放列表",
|
||||
"playlist_other": "播放列表",
|
||||
"artist_other": "艺术家",
|
||||
"folderWithCount_other": "{{count}} 个文件夹",
|
||||
"track_other": "乐曲",
|
||||
"favorite_other": "收藏",
|
||||
"artistWithCount_other": "{{count}} 位艺术家",
|
||||
"folder_other": "文件夹",
|
||||
"smartPlaylist": "智能 $t(entity.playlist_one)",
|
||||
"genreWithCount_other": "{{count}} 种流派",
|
||||
"trackWithCount_other": "{{count}} 首乐曲"
|
||||
},
|
||||
"player": {
|
||||
"repeat_all": "全部循环",
|
||||
"stop": "停止",
|
||||
"repeat": "循环",
|
||||
"queue_remove": "移除所选",
|
||||
"playRandom": "随机播放",
|
||||
"skip": "跳过",
|
||||
"previous": "前一首",
|
||||
"toggleFullscreenPlayer": "全屏",
|
||||
"skip_back": "向后跳过",
|
||||
"favorite": "收藏",
|
||||
"next": "下一首",
|
||||
"shuffle": "随机播放",
|
||||
"playbackFetchNoResults": "未找到歌曲",
|
||||
"playbackFetchInProgress": "正在加载歌曲…",
|
||||
"addNext": "添加为播放列表下一首",
|
||||
"playbackFetchCancel": "请稍等…关闭通知以取消操作",
|
||||
"play": "播放",
|
||||
"repeat_off": "不循环",
|
||||
"queue_clear": "清空播放队列",
|
||||
"muted": "已静音",
|
||||
"unfavorite": "取消收藏",
|
||||
"queue_moveToTop": "使所选置底",
|
||||
"queue_moveToBottom": "使所选置顶",
|
||||
"shuffle_off": "未启用随机播放",
|
||||
"addLast": "添加到播放列表末尾",
|
||||
"mute": "静音",
|
||||
"skip_forward": "向前跳过",
|
||||
"playbackSpeed": "播放速度",
|
||||
"pause": "暂停"
|
||||
},
|
||||
"setting": {
|
||||
"crossfadeStyle_description": "选择用于音频播放器的淡入淡出风格",
|
||||
"hotkey_favoriteCurrentSong": "收藏 $t(common.currentSong)",
|
||||
"crossfadeStyle": "淡入淡出风格",
|
||||
"audioExclusiveMode_description": "启用独占输出模式。在此模式下,系统通常被锁定,只有 mpv 能够输出音频",
|
||||
"disableLibraryUpdateOnStartup": "禁用启动时查找新版本",
|
||||
"gaplessAudio": "无缝音频",
|
||||
"audioPlayer_description": "选择用于播放的音频播放器",
|
||||
"globalMediaHotkeys": "全局媒体快捷键",
|
||||
"gaplessAudio_description": "调整 mpv 无缝音频设置",
|
||||
"disableAutomaticUpdates": "禁用自动更新",
|
||||
"followLyric_description": "滚动歌词到当前播放位置",
|
||||
"audioExclusiveMode": "音频独占模式",
|
||||
"font": "字体",
|
||||
"crossfadeDuration_description": "设置淡入淡出持续时间",
|
||||
"audioDevice": "音频设备",
|
||||
"enableRemote": "启用远程控制服务器",
|
||||
"fontType": "字体类型",
|
||||
"applicationHotkeys": "应用快捷键",
|
||||
"globalMediaHotkeys_description": "启用或禁用系统媒体快捷键以控制播放",
|
||||
"customFontPath": "自定义字体路径",
|
||||
"followLyric": "跟随当前歌词",
|
||||
"crossfadeDuration": "淡入淡出持续时间",
|
||||
"audioPlayer": "音频播放器",
|
||||
"discordApplicationId": "{{discord}} 应用 id",
|
||||
"applicationHotkeys_description": "配置应用快捷键。勾选设为全局快捷键(仅桌面端)",
|
||||
"customFontPath_description": "设置应用使用的自定义字体路径",
|
||||
"gaplessAudio_optionWeak": "弱(推荐)",
|
||||
"font_description": "设置应用使用的字体",
|
||||
"audioDevice_description": "选择用于播放的音频设备(仅 web 播放器)",
|
||||
"enableRemote_description": "启用远程控制服务器,以允许其他设备控制此应用",
|
||||
"remotePort_description": "设置远程服务器端口",
|
||||
"hotkey_skipBackward": "向回跳过",
|
||||
"replayGainMode_description": "根据乐曲元数据中存储的{{ReplayGain}}值调整音量增益",
|
||||
"volumeWheelStep_description": "在音量滑块上滚动鼠标滚轮时要更改的音量大小",
|
||||
"theme_description": "设置应用的主题",
|
||||
"hotkey_playbackPause": "暂停",
|
||||
"replayGainFallback": "{{ReplayGain}}后备替代",
|
||||
"sidebarCollapsedNavigation_description": "在折叠的侧边栏中显示或隐藏导航",
|
||||
"hotkey_volumeUp": "音量增高",
|
||||
"skipDuration": "跳过时长",
|
||||
"showSkipButtons": "显示跳过按钮",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"minimumScrobblePercentage": "最小 scrobble 时长(百分比)",
|
||||
"lyricFetch": "从互联网获取歌词",
|
||||
"scrobble": "记录播放信息(Scrobble)",
|
||||
"skipDuration_description": "设置每次按下跳过按钮将会跳过的时长",
|
||||
"fontType_optionSystem": "系统字体",
|
||||
"mpvExecutablePath_description": "设置 mpv 二进制文件的路径",
|
||||
"sampleRate": "采样率",
|
||||
"sidePlayQueueStyle_optionAttached": "吸附",
|
||||
"sidebarConfiguration": "侧边栏设定",
|
||||
"sampleRate_description": "所选的采样率与当前媒体的频率不同时,用于输出的采样率",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"hotkey_zoomIn": "放大",
|
||||
"scrobble_description": "在你的社交媒体中记录播放信息",
|
||||
"hotkey_browserForward": "浏览器前进",
|
||||
"themeLight": "主题(浅色)",
|
||||
"fontType_optionBuiltIn": "内置字体",
|
||||
"hotkey_playbackPlayPause": "播放/暂停",
|
||||
"hotkey_rate1": "评为 1 星",
|
||||
"hotkey_skipForward": "向后跳过",
|
||||
"sidePlayQueueStyle": "侧边播放列表样式",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"zoom": "放大率",
|
||||
"minimizeToTray_description": "将应用程序最小化到系统托盘",
|
||||
"hotkey_playbackPlay": "播放",
|
||||
"hotkey_togglePreviousSongFavorite": "收藏 / 取消收藏$t(common.previousSong)",
|
||||
"hotkey_volumeDown": "音量降低",
|
||||
"hotkey_unfavoritePreviousSong": "取消收藏$t(common.previousSong)",
|
||||
"hotkey_globalSearch": "全局搜索",
|
||||
"remoteUsername_description": "设置远程控制服务器的用户名。如果用户名和密码都为空,则身份验证将被禁用",
|
||||
"exitToTray_description": "退出应用时最小化到系统托盘而非关闭",
|
||||
"hotkey_favoritePreviousSong": "收藏 $t(common.previousSong)",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"lyricOffset": "歌词偏移(毫秒)",
|
||||
"fontType_optionCustom": "自定义字体",
|
||||
"themeDark_description": "应用将使用深色主题",
|
||||
"remotePassword": "远程控制服务器密码",
|
||||
"lyricFetchProvider": "歌词源",
|
||||
"language_description": "设置应用的语言($t(common.restartRequired))",
|
||||
"playbackStyle_optionCrossFade": "交叉淡入淡出",
|
||||
"hotkey_rate3": "评为 3 星",
|
||||
"mpvExtraParameters": "mpv 参数",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"themeLight_description": "应用将使用浅色主题",
|
||||
"hotkey_toggleFullScreenPlayer": "全屏播放",
|
||||
"hotkey_localSearch": "页面内搜索",
|
||||
"hotkey_toggleQueue": "显示 / 隐藏播放队列",
|
||||
"zoom_description": "设置应用的放大率",
|
||||
"remotePassword_description": "设置远程控制服务器的密码。这些凭据默认以不安全的方式传输,因此您应该使用一个您不在意的唯一密码",
|
||||
"hotkey_rate5": "评为 5 星",
|
||||
"hotkey_playbackPrevious": "上一曲",
|
||||
"showSkipButtons_description": "在播放条显示/隐藏播放按钮",
|
||||
"language": "语言",
|
||||
"playbackStyle": "播放风格",
|
||||
"hotkey_toggleShuffle": "切换随机播放设定",
|
||||
"theme": "主题",
|
||||
"playbackStyle_description": "选择播放器的播放风格",
|
||||
"mpvExecutablePath": "mpv 二进制文件路径",
|
||||
"hotkey_rate2": "评为 2 星",
|
||||
"playButtonBehavior_description": "设置将歌曲添加到队列时播放按钮的默认行为",
|
||||
"minimumScrobblePercentage_description": "歌曲被记录为已播放(scrobble)所需的最小播放百分比",
|
||||
"exitToTray": "退出时最小化到托盘",
|
||||
"hotkey_rate4": "评为 4 星",
|
||||
"showSkipButton_description": "在播放条上显示/隐藏跳过按钮",
|
||||
"savePlayQueue": "保存播放列表",
|
||||
"minimumScrobbleSeconds_description": "歌曲被记录为已播放(scrobble)所需的最小播放时间",
|
||||
"skipPlaylistPage_description": "打开歌单时,直接查看歌曲列表而非查看默认页面",
|
||||
"fontType_description": "内置字体可以选择 Feishin 提供的字体之一。系统字体允许您选择操作系统提供的任何字体。自定义选项允许您使用自己的字体",
|
||||
"playButtonBehavior": "播放按钮行为",
|
||||
"volumeWheelStep": "音量滚轮步长",
|
||||
"sidebarPlaylistList_description": "显示或隐藏侧边栏歌单列表",
|
||||
"sidePlayQueueStyle_description": "设置侧边播放列表样式",
|
||||
"replayGainMode": "{{ReplayGain}}模式",
|
||||
"playbackStyle_optionNormal": "通常",
|
||||
"windowBarStyle": "窗口顶栏风格",
|
||||
"floatingQueueArea": "显示浮动队列悬停区域",
|
||||
"replayGainFallback_description": "乐曲没有{{ReplayGain}}标签时应用的增益(以分贝为单位)",
|
||||
"hotkey_toggleRepeat": "切换循环播放设定",
|
||||
"lyricOffset_description": "将歌词偏移指定的毫秒数",
|
||||
"sidebarConfiguration_description": "选择侧边栏包含的项目与顺序",
|
||||
"remotePort": "远程服务器端口",
|
||||
"hotkey_playbackNext": "下一曲",
|
||||
"useSystemTheme_description": "使用系统定义的浅色或深色主题",
|
||||
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
|
||||
"lyricFetch_description": "从多个互联网源获取歌词",
|
||||
"lyricFetchProvider_description": "选择歌词源。 歌词源顺序与查询顺序一致",
|
||||
"sidePlayQueueStyle_optionDetached": "不吸附",
|
||||
"hotkey_zoomOut": "缩小",
|
||||
"hotkey_unfavoriteCurrentSong": "取消收藏$t(common.currentSong)",
|
||||
"hotkey_rate0": "清除评分",
|
||||
"floatingQueueArea_description": "在屏幕右侧显示一个悬停图标,以查看播放队列",
|
||||
"hotkey_volumeMute": "静音",
|
||||
"hotkey_toggleCurrentSongFavorite": "收藏 / 取消收藏$t(common.currentSong)",
|
||||
"remoteUsername": "远程服务器用户名",
|
||||
"hotkey_browserBack": "浏览器后退",
|
||||
"showSkipButton": "显示跳过按钮",
|
||||
"sidebarPlaylistList": "侧边栏歌单列表",
|
||||
"minimizeToTray": "最小化到托盘",
|
||||
"skipPlaylistPage": "跳过歌单页面",
|
||||
"themeDark": "主题(深色)",
|
||||
"sidebarCollapsedNavigation": "侧边栏(已折叠)导航",
|
||||
"minimumScrobbleSeconds": "最小 scrobble 时间(秒)",
|
||||
"hotkey_playbackStop": "停止",
|
||||
"windowBarStyle_description": "选择窗口顶栏的风格",
|
||||
"savePlayQueue_description": "当应用程序关闭时保存播放队列,并在应用程序打开时恢复它",
|
||||
"useSystemTheme": "跟随系统",
|
||||
"mpvExecutablePath_help": "每行一个",
|
||||
"discordIdleStatus_description": "启用后将会在播放器闲置时更新状态",
|
||||
"replayGainClipping_description": "自动降低增益以防止{{ReplayGain}}造成削波",
|
||||
"replayGainPreamp": "{{ReplayGain}}前置放大(分贝)",
|
||||
"replayGainClipping": "{{ReplayGain}}削波",
|
||||
"discordUpdateInterval": "{{discord}} rich presence 更新间隔",
|
||||
"discordApplicationId_description": "{{discord}} rich presence 应用 id(默认为 {{defaultId}})",
|
||||
"discordUpdateInterval_description": "更新间隔秒数(至少 15 秒)",
|
||||
"discordRichPresence_description": "在 {{discord}} rich presence 中显示播放状态。图片键为:{{icon}}、{{playing}} 和 {{paused}} ",
|
||||
"accentColor": "强调色",
|
||||
"accentColor_description": "设置应用的强调色",
|
||||
"replayGainPreamp_description": "调整应用在{{ReplayGain}}值上的前置放大增益",
|
||||
"discordIdleStatus": "显示 rich presence 闲置状态",
|
||||
"discordRichPresence": "{{discord}} rich presence"
|
||||
},
|
||||
"error": {
|
||||
"remotePortWarning": "重启服务器使新端口生效",
|
||||
"systemFontError": "获取系统字体时出现错误",
|
||||
"playbackError": "无法播放媒体",
|
||||
"endpointNotImplementedError": "{{serverType}} 尚未实现端点 {{endpoint}}",
|
||||
"remotePortError": "设置远程服务器端口时发生错误",
|
||||
"serverRequired": "需要服务器",
|
||||
"authenticationFailed": "认证失败",
|
||||
"apiRouteError": "请求失败:无法路由",
|
||||
"genericError": "发生了错误",
|
||||
"credentialsRequired": "需要凭证",
|
||||
"sessionExpiredError": "会话已过期",
|
||||
"remoteEnableError": "$t(common.enable)远程服务器时出现错误",
|
||||
"localFontAccessDenied": "无法获取本地字体",
|
||||
"serverNotSelectedError": "未选择服务器",
|
||||
"remoteDisableError": "$t(common.disable)远程服务器时出现错误",
|
||||
"mpvRequired": "需要 MPV",
|
||||
"audioDeviceFetchError": "无法获取音频设备",
|
||||
"invalidServer": "无效的服务器",
|
||||
"loginRateError": "登录请求尝试次数过多,请稍后再试"
|
||||
},
|
||||
"filter": {
|
||||
"mostPlayed": "播放最多",
|
||||
"playCount": "播放次数",
|
||||
"recentlyPlayed": "最近播放",
|
||||
"title": "标题",
|
||||
"rating": "评分",
|
||||
"search": "搜索",
|
||||
"bitrate": "比特率",
|
||||
"recentlyAdded": "最近添加",
|
||||
"name": "名称",
|
||||
"dateAdded": "已添加日期",
|
||||
"releaseDate": "发布日期",
|
||||
"communityRating": "社区评分",
|
||||
"path": "路径",
|
||||
"favorited": "已收藏",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"releaseYear": "发布年份",
|
||||
"biography": "个人简介",
|
||||
"songCount": "曲目数",
|
||||
"random": "随机",
|
||||
"lastPlayed": "上次播放过",
|
||||
"toYear": "从年份",
|
||||
"fromYear": "从年份",
|
||||
"criticRating": "评论家评分",
|
||||
"trackNumber": "曲目",
|
||||
"bpm": "bpm",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"comment": "评论",
|
||||
"isCompilation": "为合辑",
|
||||
"isFavorited": "已收藏",
|
||||
"isPublic": "已公开",
|
||||
"recentlyUpdated": "最近更新",
|
||||
"isRated": "已评分",
|
||||
"isRecentlyPlayed": "最近播放过",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"owner": "$t(common.owner)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"note": "注释",
|
||||
"albumCount": "$t(entity.album_other)数",
|
||||
"id": "id",
|
||||
"disc": "盘",
|
||||
"duration": "时长",
|
||||
"album": "$t(entity.album_one)"
|
||||
},
|
||||
"page": {
|
||||
"sidebar": {
|
||||
"nowPlaying": "正在播放",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"tracks": "$t(entity.track_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"home": "$t(common.home)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"albumArtists": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"showLyricMatch": "显示匹配的歌词",
|
||||
"dynamicBackground": "动态背景",
|
||||
"synchronized": "已同步",
|
||||
"opacity": "透明度",
|
||||
"lyricSize": "歌词字体大小",
|
||||
"showLyricProvider": "显示歌词提供者",
|
||||
"unsynchronized": "未同步",
|
||||
"lyricAlignment": "歌词对齐",
|
||||
"useImageAspectRatio": "使用图片纵横比",
|
||||
"lyricGap": "歌词间距",
|
||||
"followCurrentLyric": "跟随当前歌词"
|
||||
},
|
||||
"lyrics": "歌词",
|
||||
"related": "相关",
|
||||
"upNext": "即将播放"
|
||||
},
|
||||
"appMenu": {
|
||||
"selectServer": "选择服务器",
|
||||
"version": "版本 {{version}}",
|
||||
"manageServers": "管理服务器",
|
||||
"expandSidebar": "展开侧边栏",
|
||||
"collapseSidebar": "折叠侧边栏",
|
||||
"openBrowserDevtools": "打开浏览器开发者工具",
|
||||
"goBack": "返回",
|
||||
"goForward": "前进",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"quit": "$t(common.quit)"
|
||||
},
|
||||
"home": {
|
||||
"mostPlayed": "最多播放",
|
||||
"newlyAdded": "最近添加的发布",
|
||||
"explore": "从库中搜索",
|
||||
"recentlyPlayed": "最近播放",
|
||||
"title": "$t(common.home)"
|
||||
},
|
||||
"albumDetail": {
|
||||
"moreFromArtist": "更多该$t(entity.genre_one)作品",
|
||||
"moreFromGeneric": "更多{{item}}作品"
|
||||
},
|
||||
"setting": {
|
||||
"playbackTab": "播放",
|
||||
"generalTab": "通用",
|
||||
"hotkeysTab": "快捷键",
|
||||
"windowTab": "窗口"
|
||||
},
|
||||
"globalSearch": {
|
||||
"commands": {
|
||||
"serverCommands": "服务器命令",
|
||||
"goToPage": "跳至页面",
|
||||
"searchFor": "搜索 {{query}}"
|
||||
},
|
||||
"title": "命令"
|
||||
},
|
||||
"contextMenu": {
|
||||
"setRating": "$t(action.setRating)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"play": "$t(player.play)",
|
||||
"numberSelected": "{{count}} 已选择",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)",
|
||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||
"addToFavorites": "$t(action.addToFavorites)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"createPlaylist": "$t(action.createPlaylist)",
|
||||
"addNext": "$t(player.addNext)",
|
||||
"deselectAll": "$t(action.deselectAll)",
|
||||
"addLast": "$t(player.addLast)",
|
||||
"addFavorite": "$t(action.addToFavorites)"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)"
|
||||
},
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
}
|
||||
},
|
||||
"form": {
|
||||
"deletePlaylist": {
|
||||
"title": "删除$t(entity.playlist_one)",
|
||||
"success": "$t(entity.playlist_one)已成功删除",
|
||||
"input_confirm": "输入$t(entity.playlist_one)的名称进行确认"
|
||||
},
|
||||
"addServer": {
|
||||
"title": "添加服务器",
|
||||
"input_username": "用户名",
|
||||
"input_password": "密码",
|
||||
"input_legacyAuthentication": "启用旧版认证方式",
|
||||
"input_name": "服务器名",
|
||||
"success": "服务器添加成功",
|
||||
"input_savePassword": "保存密码",
|
||||
"ignoreSsl": "忽略 ssl $t(common.restartRequired)",
|
||||
"ignoreCors": "忽略 cors $t(common.restartRequired)",
|
||||
"error_savePassword": "保存密码时出现错误",
|
||||
"input_url": "url"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"success": "添加 {{message}} $t(entity.song_other) 到 {{numOfPlaylists}} $t(entity.playlist_other)",
|
||||
"title": "添加到$t(entity.playlist_one)",
|
||||
"input_skipDuplicates": "跳过重复",
|
||||
"input_playlists": "$t(entity.playlist_other)"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"title": "创建$t(entity.playlist_one)",
|
||||
"input_public": "公开",
|
||||
"success": "已成功创建 $t(entity.playlist_one)",
|
||||
"input_description": "$t(common.description)",
|
||||
"input_name": "$t(common.name)",
|
||||
"input_owner": "$t(common.owner)"
|
||||
},
|
||||
"updateServer": {
|
||||
"title": "更新服务器",
|
||||
"success": "服务器已更新成功"
|
||||
},
|
||||
"queryEditor": {
|
||||
"input_optionMatchAll": "匹配全部",
|
||||
"input_optionMatchAny": "匹配任何"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"title": "编辑$t(entity.playlist_one)"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"title": "搜索歌词",
|
||||
"input_name": "$t(common.name)",
|
||||
"input_artist": "$t(entity.artist_one)"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"general": {
|
||||
"displayType": "显示风格",
|
||||
"gap": "$t(common.gap)",
|
||||
"tableColumns": "列",
|
||||
"autoFitColumns": "列宽自适应",
|
||||
"size": "$t(common.size)"
|
||||
},
|
||||
"view": {
|
||||
"table": "表格",
|
||||
"poster": "海报",
|
||||
"card": "卡片"
|
||||
},
|
||||
"label": {
|
||||
"releaseDate": "发布日期",
|
||||
"title": "$t(common.title)",
|
||||
"duration": "$t(common.duration)",
|
||||
"dateAdded": "添加日期",
|
||||
"size": "$t(common.size)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"lastPlayed": "最后播放",
|
||||
"trackNumber": "音轨编号",
|
||||
"rowIndex": "行号",
|
||||
"rating": "$t(common.rating)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"note": "$t(common.note)",
|
||||
"biography": "$t(common.biography)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"playCount": "播放数",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"actions": "$t(common.action_other)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"discNumber": "碟片编号",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"year": "$t(common.year)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"titleCombined": "$t(common.title)(合并)"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"comment": "评论",
|
||||
"album": "专辑",
|
||||
"rating": "评价",
|
||||
"favorite": "收藏",
|
||||
"playCount": "播放次数",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"releaseYear": "年份",
|
||||
"lastPlayed": "最后播放",
|
||||
"biography": "简介",
|
||||
"releaseDate": "发布日期",
|
||||
"bitrate": "比特率",
|
||||
"title": "标题",
|
||||
"bpm": "bpm",
|
||||
"dateAdded": "添加日期",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"trackNumber": "音轨编号",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"albumArtist": "专辑艺术家",
|
||||
"path": "路径",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"discNumber": "盘"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Client, SetActivity } from '@xhayper/discord-rpc';
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
const FEISHIN_DISCORD_APPLICATION_ID = '1165957668758900787';
|
||||
|
||||
let client: Client | null = null;
|
||||
|
||||
const createClient = (clientId?: string) => {
|
||||
client = new Client({
|
||||
clientId: clientId || FEISHIN_DISCORD_APPLICATION_ID,
|
||||
});
|
||||
|
||||
client.login();
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
const setActivity = (activity: SetActivity) => {
|
||||
if (client) {
|
||||
client.user?.setActivity({
|
||||
...activity,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const clearActivity = () => {
|
||||
if (client) {
|
||||
client.user?.clearActivity();
|
||||
}
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
if (client) {
|
||||
client?.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
ipcMain.handle('discord-rpc-initialize', (_event, clientId?: string) => {
|
||||
createClient(clientId);
|
||||
});
|
||||
|
||||
ipcMain.handle('discord-rpc-set-activity', (_event, activity: SetActivity) => {
|
||||
if (client) {
|
||||
setActivity(activity);
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('discord-rpc-clear-activity', () => {
|
||||
if (client) {
|
||||
clearActivity();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('discord-rpc-quit', () => {
|
||||
quit();
|
||||
});
|
||||
|
||||
export const discordRpc = {
|
||||
clearActivity,
|
||||
createClient,
|
||||
quit,
|
||||
setActivity,
|
||||
};
|
||||
@@ -2,3 +2,4 @@ import './lyrics';
|
||||
import './player';
|
||||
import './remote';
|
||||
import './settings';
|
||||
import './discord-rpc';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { load } from 'cheerio';
|
||||
import { orderSearchResults } from './shared';
|
||||
import {
|
||||
LyricSource,
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
} from '../../../../renderer/api/types';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const SEARCH_URL = 'https://genius.com/api/search/song';
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// 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';
|
||||
import { orderSearchResults } from './shared';
|
||||
|
||||
const FETCH_URL = 'https://lrclib.net/api/get';
|
||||
const SEEARCH_URL = 'https://lrclib.net/api/search';
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import console from 'console';
|
||||
import { ipcMain } from 'electron';
|
||||
import { getMainWindow, getMpvInstance } from '../../../main';
|
||||
import { getMpvInstance } from '../../../main';
|
||||
import { PlayerData } from '/@/renderer/store';
|
||||
|
||||
declare module 'node-mpv';
|
||||
|
||||
function wait(timeout: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve('resolved');
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
// function wait(timeout: number) {
|
||||
// return new Promise((resolve) => {
|
||||
// setTimeout(() => {
|
||||
// resolve('resolved');
|
||||
// }, timeout);
|
||||
// });
|
||||
// }
|
||||
|
||||
ipcMain.handle('player-is-running', async () => {
|
||||
return getMpvInstance()?.isRunning();
|
||||
@@ -101,6 +101,7 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to clear playlist', err);
|
||||
});
|
||||
|
||||
await getMpvInstance()
|
||||
?.pause()
|
||||
.catch((err) => {
|
||||
@@ -109,42 +110,25 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
||||
return;
|
||||
}
|
||||
|
||||
let complete = false;
|
||||
let tryAttempts = 0;
|
||||
try {
|
||||
if (data.queue.current) {
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.current.streamUrl, 'replace')
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load song', err);
|
||||
getMpvInstance()?.play();
|
||||
});
|
||||
|
||||
while (!complete) {
|
||||
if (tryAttempts > 3) {
|
||||
getMainWindow()?.webContents.send('renderer-player-error', 'Failed to load song');
|
||||
complete = true;
|
||||
} else {
|
||||
try {
|
||||
if (data.queue.current) {
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.current.streamUrl, 'replace')
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load song', err);
|
||||
});
|
||||
}
|
||||
|
||||
if (data.queue.next) {
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.next.streamUrl, 'append')
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load next song', err);
|
||||
});
|
||||
}
|
||||
|
||||
complete = true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
tryAttempts += 1;
|
||||
await wait(500);
|
||||
if (data.queue.next) {
|
||||
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (pause) {
|
||||
await getMpvInstance()?.pause();
|
||||
getMpvInstance()?.pause();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -186,6 +170,7 @@ ipcMain.on('player-auto-next', async (_event, data: PlayerData) => {
|
||||
?.playlistRemove(0)
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to remove song from playlist', err);
|
||||
getMpvInstance()?.pause();
|
||||
});
|
||||
|
||||
if (data.queue.next) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { deflate, gzip } from 'zlib';
|
||||
import axios from 'axios';
|
||||
import { app, ipcMain } from 'electron';
|
||||
import { Server as WsServer, WebSocketServer, WebSocket } from 'ws';
|
||||
import manifest from './manifest.json';
|
||||
import { ClientEvent, ServerEvent } from '../../../../remote/types';
|
||||
import { PlayerRepeat, SongUpdate } from '../../../../renderer/types';
|
||||
import { getMainWindow } from '../../../main';
|
||||
@@ -34,6 +35,7 @@ interface MimeType {
|
||||
|
||||
interface StatefulWebSocket extends WebSocket {
|
||||
alive: boolean;
|
||||
auth: boolean;
|
||||
}
|
||||
|
||||
let server: Server | undefined;
|
||||
@@ -52,7 +54,9 @@ type SendData = ServerEvent & {
|
||||
|
||||
function send({ client, event, data }: SendData): void {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify({ data, event }));
|
||||
if (client.alive && client.auth) {
|
||||
client.send(JSON.stringify({ data, event }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,17 +145,9 @@ async function serveFile(
|
||||
res: ServerResponse,
|
||||
): Promise<void> {
|
||||
const fileName = `${file}.${extension}`;
|
||||
let path: string;
|
||||
|
||||
if (extension === 'ico') {
|
||||
path = app.isPackaged
|
||||
? join(process.resourcesPath, 'assets', fileName)
|
||||
: join(__dirname, '../../../../../assets', fileName);
|
||||
} else {
|
||||
path = app.isPackaged
|
||||
? join(__dirname, '../remote', fileName)
|
||||
: join(__dirname, '../../../../../.erb/dll', fileName);
|
||||
}
|
||||
const path = app.isPackaged
|
||||
? join(__dirname, '../remote', fileName)
|
||||
: join(__dirname, '../../../../../.erb/dll', fileName);
|
||||
|
||||
let stats: Stats;
|
||||
|
||||
@@ -291,7 +287,7 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
break;
|
||||
}
|
||||
case '/favicon.ico': {
|
||||
await serveFile(req, 'icon', 'ico', res);
|
||||
await serveFile(req, 'favicon', 'ico', res);
|
||||
break;
|
||||
}
|
||||
case '/remote.css': {
|
||||
@@ -302,10 +298,26 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
await serveFile(req, 'remote', 'js', res);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
res.statusCode = 404;
|
||||
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('Not FOund');
|
||||
res.end(req.headers.authorization);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (req.url?.startsWith('/worker.js')) {
|
||||
await serveFile(req, 'worker', 'js', res);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.end('Not Found');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -318,14 +330,20 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
server.listen(config.port, resolve);
|
||||
wsServer = new WebSocketServer({ server });
|
||||
|
||||
wsServer.on('connection', (ws, req) => {
|
||||
if (!authorize(req)) {
|
||||
ws.close(4003);
|
||||
return;
|
||||
}
|
||||
|
||||
wsServer.on('connection', (ws) => {
|
||||
let authFail: number | undefined;
|
||||
ws.alive = true;
|
||||
|
||||
if (!settings.username && !settings.password) {
|
||||
ws.auth = true;
|
||||
} else {
|
||||
authFail = setTimeout(() => {
|
||||
if (!ws.auth) {
|
||||
ws.close();
|
||||
}
|
||||
}, 10000) as unknown as number;
|
||||
}
|
||||
|
||||
ws.on('error', console.error);
|
||||
|
||||
ws.on('message', (data) => {
|
||||
@@ -333,6 +351,25 @@ const enableServer = (config: RemoteConfig): Promise<void> => {
|
||||
const json = JSON.parse(data.toString()) as ClientEvent;
|
||||
const event = json.event;
|
||||
|
||||
if (!ws.auth) {
|
||||
if (event === 'authenticate') {
|
||||
const auth = json.header.split(' ')[1];
|
||||
const [login, password] = Buffer.from(auth, 'base64')
|
||||
.toString()
|
||||
.split(':');
|
||||
|
||||
if (login === settings.username && password === settings.password) {
|
||||
ws.auth = true;
|
||||
} else {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
clearTimeout(authFail);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (event) {
|
||||
case 'pause': {
|
||||
getMainWindow()?.webContents.send('renderer-player-pause');
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Feishin Remote",
|
||||
"short_name": "Feishin Remote",
|
||||
"start_url": "/",
|
||||
"background_color": "#000100",
|
||||
"theme_color": "#E7E7E7",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
}
|
||||
],
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import Store from 'electron-store';
|
||||
import { ipcMain, safeStorage } from 'electron';
|
||||
import Store from 'electron-store';
|
||||
|
||||
export const store = new Store();
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ ipcMain.on('update-song', (_event, args: SongUpdate) => {
|
||||
|
||||
mprisPlayer.metadata = {
|
||||
'mpris:artUrl': upsizedImageUrl,
|
||||
'mpris:length': song.duration ? Math.round((song.duration || 0) * 1e6) : null,
|
||||
'mpris:length': song.duration ? Math.round((song.duration || 0) * 1e3) : null,
|
||||
'mpris:trackid': song.id
|
||||
? mprisPlayer.objectPath(`track/${song.id?.replace('-', '')}`)
|
||||
: '',
|
||||
|
||||
@@ -11,11 +11,6 @@
|
||||
import { access, constants, readFile, writeFile } from 'fs';
|
||||
import path, { join } from 'path';
|
||||
import { deflate, inflate } from 'zlib';
|
||||
import electronLocalShortcut from 'electron-localshortcut';
|
||||
import log from 'electron-log';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import uniq from 'lodash/uniq';
|
||||
import MpvAPI from 'node-mpv';
|
||||
import {
|
||||
app,
|
||||
BrowserWindow,
|
||||
@@ -26,7 +21,14 @@ import {
|
||||
Menu,
|
||||
nativeImage,
|
||||
BrowserWindowConstructorOptions,
|
||||
protocol,
|
||||
net,
|
||||
} from 'electron';
|
||||
import electronLocalShortcut from 'electron-localshortcut';
|
||||
import log from 'electron-log';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import uniq from 'lodash/uniq';
|
||||
import MpvAPI from 'node-mpv';
|
||||
import { disableMediaKeys, enableMediaKeys } from './features/core/player/media-keys';
|
||||
import { store } from './features/core/settings/index';
|
||||
import MenuBuilder from './menu';
|
||||
@@ -43,6 +45,8 @@ export default class AppUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
protocol.registerSchemesAsPrivileged([{ privileges: { bypassCSP: true }, scheme: 'feishin' }]);
|
||||
|
||||
process.on('uncaughtException', (error: any) => {
|
||||
console.log('Error in main process', error);
|
||||
});
|
||||
@@ -129,7 +133,9 @@ const createTray = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
tray = isLinux() ? new Tray(getAssetPath('icon.png')) : new Tray(getAssetPath('icon.ico'));
|
||||
tray = isLinux()
|
||||
? new Tray(getAssetPath('icons/icon.png'))
|
||||
: new Tray(getAssetPath('icons/icon.ico'));
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
click: () => {
|
||||
@@ -212,7 +218,7 @@ const createWindow = async () => {
|
||||
autoHideMenuBar: true,
|
||||
frame: false,
|
||||
height: 900,
|
||||
icon: getAssetPath('icon.png'),
|
||||
icon: getAssetPath('icons/icon.png'),
|
||||
minHeight: 640,
|
||||
minWidth: 480,
|
||||
show: false,
|
||||
@@ -257,6 +263,11 @@ const createWindow = async () => {
|
||||
mainWindow?.close();
|
||||
});
|
||||
|
||||
ipcMain.on('window-quit', () => {
|
||||
mainWindow?.close();
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.on('app-restart', () => {
|
||||
// Fix for .AppImage
|
||||
if (process.env.APPIMAGE) {
|
||||
@@ -426,7 +437,7 @@ const prefetchPlaylistParams = [
|
||||
];
|
||||
|
||||
const DEFAULT_MPV_PARAMETERS = (extraParameters?: string[]) => {
|
||||
const parameters = ['--idle=yes'];
|
||||
const parameters = ['--idle=yes', '--no-config', '--load-scripts=no'];
|
||||
|
||||
if (!extraParameters?.some((param) => prefetchPlaylistParams.includes(param))) {
|
||||
parameters.push('--prefetch-playlist=yes');
|
||||
@@ -443,22 +454,28 @@ const createMpv = (data: { extraParameters?: string[]; properties?: Record<strin
|
||||
const params = uniq([...DEFAULT_MPV_PARAMETERS(extraParameters), ...(extraParameters || [])]);
|
||||
console.log('Setting mpv params: ', params);
|
||||
|
||||
const extra = isDevelopment ? '-dev' : '';
|
||||
|
||||
const mpv = new MpvAPI(
|
||||
{
|
||||
audio_only: true,
|
||||
auto_restart: false,
|
||||
binary: MPV_BINARY_PATH || '',
|
||||
socket: isWindows() ? `\\\\.\\pipe\\mpvserver${extra}` : `/tmp/node-mpv${extra}.sock`,
|
||||
time_update: 1,
|
||||
},
|
||||
params,
|
||||
);
|
||||
|
||||
console.log('Setting MPV properties: ', properties);
|
||||
mpv.setMultipleProperties(properties || {});
|
||||
|
||||
mpv.start().catch((error) => {
|
||||
console.log('MPV failed to start', error);
|
||||
});
|
||||
// eslint-disable-next-line promise/catch-or-return
|
||||
mpv.start()
|
||||
.catch((error) => {
|
||||
console.log('MPV failed to start', error);
|
||||
})
|
||||
.finally(() => {
|
||||
console.log('Setting MPV properties: ', properties);
|
||||
mpv.setMultipleProperties(properties || {});
|
||||
});
|
||||
|
||||
mpv.on('status', (status, ...rest) => {
|
||||
console.log('MPV Event: status', status.property, status.value, rest);
|
||||
@@ -640,8 +657,34 @@ app.on('window-all-closed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const FONT_HEADERS = [
|
||||
'font/collection',
|
||||
'font/otf',
|
||||
'font/sfnt',
|
||||
'font/ttf',
|
||||
'font/woff',
|
||||
'font/woff2',
|
||||
];
|
||||
|
||||
app.whenReady()
|
||||
.then(() => {
|
||||
protocol.handle('feishin', async (request) => {
|
||||
const filePath = `file://${request.url.slice('feishin://'.length)}`;
|
||||
const response = await net.fetch(filePath);
|
||||
const contentType = response.headers.get('content-type');
|
||||
|
||||
if (!contentType || !FONT_HEADERS.includes(contentType)) {
|
||||
getMainWindow()?.webContents.send('custom-font-error', filePath);
|
||||
|
||||
return new Response(null, {
|
||||
status: 403,
|
||||
statusText: 'Forbidden',
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
createWindow();
|
||||
createTray();
|
||||
app.on('activate', () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
@@ -10,6 +11,7 @@ import { utils } from './preload/utils';
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
browser,
|
||||
discordRpc,
|
||||
ipc,
|
||||
localSettings,
|
||||
lyrics,
|
||||
|
||||
@@ -3,16 +3,23 @@ import { ipcRenderer } from 'electron';
|
||||
const exit = () => {
|
||||
ipcRenderer.send('window-close');
|
||||
};
|
||||
|
||||
const maximize = () => {
|
||||
ipcRenderer.send('window-maximize');
|
||||
};
|
||||
|
||||
const minimize = () => {
|
||||
ipcRenderer.send('window-minimize');
|
||||
};
|
||||
|
||||
const unmaximize = () => {
|
||||
ipcRenderer.send('window-unmaximize');
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
ipcRenderer.send('window-quit');
|
||||
};
|
||||
|
||||
const devtools = () => {
|
||||
ipcRenderer.send('window-dev-tools');
|
||||
};
|
||||
@@ -22,5 +29,6 @@ export const browser = {
|
||||
exit,
|
||||
maximize,
|
||||
minimize,
|
||||
quit,
|
||||
unmaximize,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { SetActivity } from '@xhayper/discord-rpc';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
const initialize = (clientId: string) => {
|
||||
const client = ipcRenderer.invoke('discord-rpc-initialize', clientId);
|
||||
return client;
|
||||
};
|
||||
|
||||
const clearActivity = () => {
|
||||
ipcRenderer.invoke('discord-rpc-clear-activity');
|
||||
};
|
||||
|
||||
const setActivity = (activity: SetActivity) => {
|
||||
ipcRenderer.invoke('discord-rpc-set-activity', activity);
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
ipcRenderer.invoke('discord-rpc-quit');
|
||||
};
|
||||
|
||||
export const discordRpc = {
|
||||
clearActivity,
|
||||
initialize,
|
||||
quit,
|
||||
setActivity,
|
||||
};
|
||||
|
||||
export type DiscordRpc = typeof discordRpc;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IpcRendererEvent, ipcRenderer, webFrame } from 'electron';
|
||||
import Store from 'electron-store';
|
||||
import { ipcRenderer, webFrame } from 'electron';
|
||||
|
||||
const store = new Store();
|
||||
|
||||
@@ -39,9 +39,14 @@ const setZoomFactor = (zoomFactor: number) => {
|
||||
webFrame.setZoomFactor(zoomFactor / 100);
|
||||
};
|
||||
|
||||
const fontError = (cb: (event: IpcRendererEvent, file: string) => void) => {
|
||||
ipcRenderer.on('custom-font-error', cb);
|
||||
};
|
||||
|
||||
export const localSettings = {
|
||||
disableMediaKeys,
|
||||
enableMediaKeys,
|
||||
fontError,
|
||||
get,
|
||||
passwordGet,
|
||||
passwordRemove,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Ref, forwardRef } from 'react';
|
||||
import { MouseEvent, ReactNode, Ref, forwardRef } from 'react';
|
||||
import { Button, type ButtonProps as MantineButtonProps } from '@mantine/core';
|
||||
import { Tooltip } from '/@/renderer/components/tooltip';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface StyledButtonProps extends MantineButtonProps {
|
||||
$active?: boolean;
|
||||
children: React.ReactNode;
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
onMouseDown?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
children: ReactNode;
|
||||
onClick?: (e: MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
onMouseDown?: (e: MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
ref: Ref<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Group, Image, Rating, Text, Title } from '@mantine/core';
|
||||
import { Group, Image, Text, Title } from '@mantine/core';
|
||||
import { useInfo, useSend, useShowImage } from '/@/remote/store';
|
||||
import { RemoteButton } from '/@/remote/components/buttons/remote-button';
|
||||
import formatDuration from 'format-duration';
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { PlayerRepeat, PlayerStatus } from '/@/renderer/types';
|
||||
import { WrapperSlider } from '/@/remote/components/wrapped-slider';
|
||||
import { Tooltip } from '/@/renderer/components/tooltip';
|
||||
import { Rating } from '/@/renderer/components';
|
||||
|
||||
export const RemoteContainer = () => {
|
||||
const { repeat, shuffle, song, status, volume } = useInfo();
|
||||
@@ -45,7 +46,7 @@ export const RemoteContainer = () => {
|
||||
<Title order={2}>Artist: {song.artistName}</Title>
|
||||
</Group>
|
||||
<Group position="apart">
|
||||
<Title order={3}>Duration: {formatDuration(song.duration * 1000)}</Title>
|
||||
<Title order={3}>Duration: {formatDuration(song.duration)}</Title>
|
||||
{song.releaseDate && (
|
||||
<Title order={3}>
|
||||
Released: {new Date(song.releaseDate).toLocaleDateString()}
|
||||
|
||||
@@ -26,7 +26,6 @@ export const Shell = () => {
|
||||
<Grid.Col span="auto">
|
||||
<div>
|
||||
<Image
|
||||
bg="rgb(25, 25, 25)"
|
||||
fit="contain"
|
||||
height={60}
|
||||
src="/favicon.ico"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { SliderProps } from '@mantine/core';
|
||||
import styled from 'styled-components';
|
||||
import { PlayerbarSlider } from '/@/renderer/features/player/components/playerbar-slider';
|
||||
@@ -7,10 +7,10 @@ const SliderContainer = styled.div`
|
||||
display: flex;
|
||||
width: 95%;
|
||||
height: 20px;
|
||||
margin: 10px 0px;
|
||||
margin: 10px 0;
|
||||
`;
|
||||
|
||||
const SliderValueWrapper = styled.div<{ position: 'left' | 'right' }>`
|
||||
const SliderValueWrapper = styled.div<{ $position: 'left' | 'right' }>`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-self: flex-end;
|
||||
@@ -26,9 +26,9 @@ const SliderWrapper = styled.div`
|
||||
`;
|
||||
|
||||
export interface WrappedProps extends Omit<SliderProps, 'onChangeEnd'> {
|
||||
leftLabel?: JSX.Element;
|
||||
leftLabel?: ReactNode;
|
||||
onChangeEnd: (value: number) => void;
|
||||
rightLabel?: JSX.Element;
|
||||
rightLabel?: ReactNode;
|
||||
value: number;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: Wrappe
|
||||
|
||||
return (
|
||||
<SliderContainer>
|
||||
{leftLabel && <SliderValueWrapper position="left">{leftLabel}</SliderValueWrapper>}
|
||||
{leftLabel && <SliderValueWrapper $position="left">{leftLabel}</SliderValueWrapper>}
|
||||
<SliderWrapper>
|
||||
<PlayerbarSlider
|
||||
{...props}
|
||||
@@ -56,7 +56,7 @@ export const WrapperSlider = ({ leftLabel, rightLabel, value, ...props }: Wrappe
|
||||
}}
|
||||
/>
|
||||
</SliderWrapper>
|
||||
{rightLabel && <SliderValueWrapper position="right">{rightLabel}</SliderValueWrapper>}
|
||||
{rightLabel && <SliderValueWrapper $position="right">{rightLabel}</SliderValueWrapper>}
|
||||
</SliderContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
<meta http-equiv="Content-Security-Policy" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Feishin Remote</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
const version = encodeURIComponent("<%= version %>");
|
||||
const prod = encodeURIComponent("<%= prod %>");
|
||||
navigator.serviceWorker.register(`/worker.js?version=${version}&prod=${prod}`);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "Feishin Remote",
|
||||
"short_name": "Feishin Remote",
|
||||
"start_url": "/",
|
||||
"background_color": "#FFDCB5",
|
||||
"theme_color": "#1E003D",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
}
|
||||
],
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/// <reference lib="WebWorker" />
|
||||
|
||||
export type {};
|
||||
// eslint-disable-next-line no-undef
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const url = new URL(location.toString());
|
||||
const version = url.searchParams.get('version');
|
||||
const prod = url.searchParams.get('prod') === 'true';
|
||||
const cacheName = `Feishin-remote-${version}`;
|
||||
|
||||
const resourcesToCache = ['./', './remote.js', './favicon.ico'];
|
||||
|
||||
if (prod) {
|
||||
resourcesToCache.push('./remote.css');
|
||||
}
|
||||
|
||||
self.addEventListener('install', (e) => {
|
||||
e.waitUntil(
|
||||
caches.open(cacheName).then((cache) => {
|
||||
return cache.addAll(resourcesToCache);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (e) => {
|
||||
e.respondWith(
|
||||
caches.match(e.request).then((response) => {
|
||||
return response || fetch(e.request);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (e) => {
|
||||
e.waitUntil(
|
||||
caches.keys().then((keyList) => {
|
||||
return Promise.all(
|
||||
keyList.map((key) => {
|
||||
if (key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||
devtools(
|
||||
immer((set, get) => ({
|
||||
actions: {
|
||||
reconnect: () => {
|
||||
reconnect: async () => {
|
||||
const existing = get().socket;
|
||||
|
||||
if (existing) {
|
||||
@@ -99,6 +99,16 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||
existing.close(4001);
|
||||
}
|
||||
}
|
||||
|
||||
let authHeader: string | undefined;
|
||||
|
||||
try {
|
||||
const credentials = await fetch('/credentials');
|
||||
authHeader = await credentials.text();
|
||||
} catch (error) {
|
||||
console.error('Failed to get credentials');
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
const socket = new WebSocket(
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
@@ -148,6 +158,14 @@ export const useRemoteStore = create<SettingsSlice>()(
|
||||
});
|
||||
|
||||
socket.addEventListener('open', () => {
|
||||
if (authHeader) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
event: 'authenticate',
|
||||
header: authHeader,
|
||||
}),
|
||||
);
|
||||
}
|
||||
set({ connected: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -53,4 +53,14 @@ export interface ClientVolume {
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export type ClientEvent = ClientSimpleEvent | ClientFavorite | ClientRating | ClientVolume;
|
||||
export interface ClientAuth {
|
||||
event: 'authenticate';
|
||||
header: string;
|
||||
}
|
||||
|
||||
export type ClientEvent =
|
||||
| ClientAuth
|
||||
| ClientSimpleEvent
|
||||
| ClientFavorite
|
||||
| ClientRating
|
||||
| ClientVolume;
|
||||
|
||||
@@ -54,6 +54,7 @@ import { DeletePlaylistResponse, RandomSongListArgs } from './types';
|
||||
import { ndController } from '/@/renderer/api/navidrome/navidrome-controller';
|
||||
import { ssController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||
import { jfController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
export type ControllerEndpoint = Partial<{
|
||||
addToPlaylist: (args: AddToPlaylistArgs) => Promise<AddToPlaylistResponse>;
|
||||
@@ -128,7 +129,7 @@ const endpoints: ApiController = {
|
||||
getPlaylistList: jfController.getPlaylistList,
|
||||
getPlaylistSongList: jfController.getPlaylistSongList,
|
||||
getRandomSongList: jfController.getRandomSongList,
|
||||
getSongDetail: undefined,
|
||||
getSongDetail: jfController.getSongDetail,
|
||||
getSongList: jfController.getSongList,
|
||||
getTopSongs: jfController.getTopSongList,
|
||||
getUserList: undefined,
|
||||
@@ -212,7 +213,12 @@ const apiController = (endpoint: keyof ControllerEndpoint, type?: ServerType) =>
|
||||
const serverType = type || useAuthStore.getState().currentServer?.type;
|
||||
|
||||
if (!serverType) {
|
||||
toast.error({ message: 'No server selected', title: 'Unable to route request' });
|
||||
toast.error({
|
||||
message: i18n.t('error.serverNotSelectedError', {
|
||||
postProcess: 'sentenceCase',
|
||||
}) as string,
|
||||
title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string,
|
||||
});
|
||||
throw new Error(`No server selected`);
|
||||
}
|
||||
|
||||
@@ -221,10 +227,16 @@ const apiController = (endpoint: keyof ControllerEndpoint, type?: ServerType) =>
|
||||
if (typeof controllerFn !== 'function') {
|
||||
toast.error({
|
||||
message: `Endpoint ${endpoint} is not implemented for ${serverType}`,
|
||||
title: 'Unable to route request',
|
||||
title: i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' }) as string,
|
||||
});
|
||||
|
||||
throw new Error(`Endpoint ${endpoint} is not implemented for ${serverType}`);
|
||||
throw new Error(
|
||||
i18n.t('error.endpointNotImplementedError', {
|
||||
endpoint,
|
||||
postProcess: 'sentenceCase',
|
||||
serverType,
|
||||
}) as string,
|
||||
);
|
||||
}
|
||||
|
||||
return endpoints[serverType][endpoint];
|
||||
|
||||
@@ -547,6 +547,7 @@ export enum JFAlbumListSort {
|
||||
COMMUNITY_RATING = 'CommunityRating,SortName',
|
||||
CRITIC_RATING = 'CriticRating,SortName',
|
||||
NAME = 'SortName',
|
||||
PLAY_COUNT = 'PlayCount',
|
||||
RANDOM = 'Random,SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
RELEASE_DATE = 'ProductionYear,PremiereDate,SortName',
|
||||
|
||||
@@ -160,7 +160,7 @@ export const contract = c.router({
|
||||
},
|
||||
getSongDetail: {
|
||||
method: 'GET',
|
||||
path: 'song/:id',
|
||||
path: 'users/:userId/items/:id',
|
||||
responses: {
|
||||
200: jfType._response.song,
|
||||
400: jfType._response.error,
|
||||
@@ -272,6 +272,12 @@ axiosClient.interceptors.response.use(
|
||||
if (error.response && error.response.status === 401) {
|
||||
const currentServer = useAuthStore.getState().currentServer;
|
||||
|
||||
if (currentServer) {
|
||||
useAuthStore
|
||||
.getState()
|
||||
.actions.updateServer(currentServer.id, { credential: undefined });
|
||||
}
|
||||
|
||||
authenticationFailure(currentServer);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ import {
|
||||
LyricsArgs,
|
||||
LyricsResponse,
|
||||
genreListSortMap,
|
||||
SongDetailArgs,
|
||||
SongDetailResponse,
|
||||
} from '/@/renderer/api/types';
|
||||
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
||||
import { jfNormalize } from './jellyfin-normalize';
|
||||
@@ -54,11 +56,37 @@ import { jfType } from '/@/renderer/api/jellyfin/jellyfin-types';
|
||||
import packageJson from '../../../../package.json';
|
||||
import { z } from 'zod';
|
||||
import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types';
|
||||
import isElectron from 'is-electron';
|
||||
|
||||
const formatCommaDelimitedString = (value: string[]) => {
|
||||
return value.join(',');
|
||||
};
|
||||
|
||||
function getHostname(): string {
|
||||
if (isElectron()) {
|
||||
return 'Desktop Client';
|
||||
}
|
||||
const agent = navigator.userAgent;
|
||||
switch (true) {
|
||||
case agent.toLowerCase().indexOf('edge') > -1:
|
||||
return 'Microsoft Edge';
|
||||
case agent.toLowerCase().indexOf('edg/') > -1:
|
||||
return 'Edge Chromium'; // Match also / to avoid matching for the older Edge
|
||||
case agent.toLowerCase().indexOf('opr') > -1:
|
||||
return 'Opera';
|
||||
case agent.toLowerCase().indexOf('chrome') > -1:
|
||||
return 'Chrome';
|
||||
case agent.toLowerCase().indexOf('trident') > -1:
|
||||
return 'Internet Explorer';
|
||||
case agent.toLowerCase().indexOf('firefox') > -1:
|
||||
return 'Firefox';
|
||||
case agent.toLowerCase().indexOf('safari') > -1:
|
||||
return 'Safari';
|
||||
default:
|
||||
return 'PC';
|
||||
}
|
||||
}
|
||||
|
||||
const authenticate = async (
|
||||
url: string,
|
||||
body: {
|
||||
@@ -74,7 +102,9 @@ const authenticate = async (
|
||||
Username: body.username,
|
||||
},
|
||||
headers: {
|
||||
'x-emby-authorization': `MediaBrowser Client="Feishin", Device="PC", DeviceId="Feishin", Version="${packageJson.version}"`,
|
||||
'x-emby-authorization': `MediaBrowser Client="Feishin", Device="${getHostname()}", DeviceId="Feishin-${getHostname()}-${
|
||||
body.username
|
||||
}", Version="${packageJson.version}"`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -411,7 +441,9 @@ const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
|
||||
}
|
||||
|
||||
return {
|
||||
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
|
||||
items: res.body.Items.map((item) =>
|
||||
jfNormalize.song(item, apiClientProps.server, '', query.imageSize),
|
||||
),
|
||||
startIndex: query.startIndex,
|
||||
totalRecordCount: res.body.TotalRecordCount,
|
||||
};
|
||||
@@ -891,12 +923,29 @@ const getLyrics = async (args: LyricsArgs): Promise<LyricsResponse> => {
|
||||
}
|
||||
|
||||
if (res.body.Lyrics.length > 0 && res.body.Lyrics[0].Start === undefined) {
|
||||
return res.body.Lyrics[0].Text;
|
||||
return res.body.Lyrics.map((lyric) => lyric.Text).join('\n');
|
||||
}
|
||||
|
||||
return res.body.Lyrics.map((lyric) => [lyric.Start! / 1e4, lyric.Text]);
|
||||
};
|
||||
|
||||
const getSongDetail = async (args: SongDetailArgs): Promise<SongDetailResponse> => {
|
||||
const { query, apiClientProps } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getSongDetail({
|
||||
params: {
|
||||
id: query.id,
|
||||
userId: apiClientProps.server?.userId ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get song detail');
|
||||
}
|
||||
|
||||
return jfNormalize.song(res.body, apiClientProps.server, '');
|
||||
};
|
||||
|
||||
export const jfController = {
|
||||
addToPlaylist,
|
||||
authenticate,
|
||||
@@ -916,6 +965,7 @@ export const jfController = {
|
||||
getPlaylistList,
|
||||
getPlaylistSongList,
|
||||
getRandomSongList,
|
||||
getSongDetail,
|
||||
getSongList,
|
||||
getTopSongList,
|
||||
removeFromPlaylist,
|
||||
|
||||
@@ -150,7 +150,13 @@ const normalizeSong = (
|
||||
container: (item.MediaSources && item.MediaSources[0]?.Container) || null,
|
||||
createdAt: item.DateCreated,
|
||||
discNumber: (item.ParentIndexNumber && item.ParentIndexNumber) || 1,
|
||||
duration: item.RunTimeTicks / 10000000,
|
||||
discSubtitle: null,
|
||||
duration: item.RunTimeTicks / 10000,
|
||||
gain: item.LUFS
|
||||
? {
|
||||
track: -18 - item.LUFS,
|
||||
}
|
||||
: null,
|
||||
genres: item.GenreItems?.map((entry) => ({
|
||||
id: entry.Id,
|
||||
imageUrl: null,
|
||||
@@ -165,6 +171,7 @@ const normalizeSong = (
|
||||
lyrics: null,
|
||||
name: item.Name,
|
||||
path: (item.MediaSources && item.MediaSources[0]?.Path) || null,
|
||||
peak: null,
|
||||
playCount: (item.UserData && item.UserData.PlayCount) || 0,
|
||||
playlistItemId: item.PlaylistItemId,
|
||||
// releaseDate: (item.ProductionYear && new Date(item.ProductionYear, 0, 1).toISOString()) || null,
|
||||
@@ -208,7 +215,7 @@ const normalizeAlbum = (
|
||||
})),
|
||||
backdropImageUrl: null,
|
||||
createdAt: item.DateCreated,
|
||||
duration: item.RunTimeTicks / 10000000,
|
||||
duration: item.RunTimeTicks / 10000,
|
||||
genres: item.GenreItems?.map((entry) => ({
|
||||
id: entry.Id,
|
||||
imageUrl: null,
|
||||
|
||||
@@ -406,6 +406,7 @@ const song = z.object({
|
||||
ImageTags: imageTags,
|
||||
IndexNumber: z.number(),
|
||||
IsFolder: z.boolean(),
|
||||
LUFS: z.number().optional(),
|
||||
LocationType: z.string(),
|
||||
MediaSources: z.array(mediaSources),
|
||||
MediaType: z.string(),
|
||||
@@ -477,6 +478,7 @@ const albumListSort = {
|
||||
COMMUNITY_RATING: 'CommunityRating,SortName',
|
||||
CRITIC_RATING: 'CriticRating,SortName',
|
||||
NAME: 'SortName',
|
||||
PLAY_COUNT: 'PlayCount',
|
||||
RANDOM: 'Random,SortName',
|
||||
RECENTLY_ADDED: 'DateCreated,SortName',
|
||||
RELEASE_DATE: 'ProductionYear,PremiereDate,SortName',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { authenticationFailure, resultWithHeaders } from '/@/renderer/api/utils'
|
||||
import { useAuthStore } from '/@/renderer/store';
|
||||
import { ServerListItem } from '/@/renderer/types';
|
||||
import { toast } from '/@/renderer/components';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const localSettings = isElectron() ? window.electron.localSettings : null;
|
||||
|
||||
@@ -276,9 +277,12 @@ axiosClient.interceptors.response.use(
|
||||
|
||||
if (res.status === 429) {
|
||||
toast.error({
|
||||
message:
|
||||
'you have exceeded the number of allowed login requests. Please wait before logging, or consider tweaking AuthRequestLimit',
|
||||
title: 'Your session has expired.',
|
||||
message: i18n.t('error.loginRateError', {
|
||||
postProcess: 'sentenceCase',
|
||||
}) as string,
|
||||
title: i18n.t('error.sessionExpiredError', {
|
||||
postProcess: 'sentenceCase',
|
||||
}) as string,
|
||||
});
|
||||
|
||||
const serverId = currentServer.id;
|
||||
@@ -292,7 +296,11 @@ axiosClient.interceptors.response.use(
|
||||
throw TIMEOUT_ERROR;
|
||||
}
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to authenticate');
|
||||
throw new Error(
|
||||
i18n.t('error.authenticatedFailed', {
|
||||
postProcess: 'sentenceCase',
|
||||
}) as string,
|
||||
);
|
||||
}
|
||||
|
||||
const newCredential = res.data.token;
|
||||
|
||||
@@ -267,7 +267,9 @@ const getSongList = async (args: SongListArgs): Promise<SongListResponse> => {
|
||||
}
|
||||
|
||||
return {
|
||||
items: res.body.data.map((song) => ndNormalize.song(song, apiClientProps.server, '')),
|
||||
items: res.body.data.map((song) =>
|
||||
ndNormalize.song(song, apiClientProps.server, '', query.imageSize),
|
||||
),
|
||||
startIndex: query?.startIndex || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
|
||||
@@ -20,10 +20,6 @@ const getImageUrl = (args: { url: string | null }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (url?.match('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
@@ -74,7 +70,6 @@ const normalizeSong = (
|
||||
});
|
||||
|
||||
const imagePlaceholderUrl = null;
|
||||
|
||||
return {
|
||||
album: item.album,
|
||||
albumArtists: [{ id: item.artistId, imageUrl: null, name: item.artist }],
|
||||
@@ -89,7 +84,12 @@ const normalizeSong = (
|
||||
container: item.suffix,
|
||||
createdAt: item.createdAt.split('T')[0],
|
||||
discNumber: item.discNumber,
|
||||
duration: item.duration,
|
||||
discSubtitle: item.discSubtitle ? item.discSubtitle : null,
|
||||
duration: item.duration * 1000,
|
||||
gain:
|
||||
item.rgAlbumGain || item.rgTrackGain
|
||||
? { album: item.rgAlbumGain, track: item.rgTrackGain }
|
||||
: null,
|
||||
genres: item.genres?.map((genre) => ({
|
||||
id: genre.id,
|
||||
imageUrl: null,
|
||||
@@ -104,6 +104,10 @@ const normalizeSong = (
|
||||
lyrics: item.lyrics ? item.lyrics : null,
|
||||
name: item.title,
|
||||
path: item.path,
|
||||
peak:
|
||||
item.rgAlbumPeak || item.rgTrackPeak
|
||||
? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
|
||||
: null,
|
||||
playCount: item.playCount,
|
||||
playlistItemId,
|
||||
releaseDate: new Date(item.year, 0, 1).toISOString(),
|
||||
@@ -178,7 +182,16 @@ const normalizeAlbumArtist = (
|
||||
},
|
||||
server: ServerListItem | null,
|
||||
): AlbumArtist => {
|
||||
const imageUrl = getImageUrl({ url: item?.largeImageUrl || null });
|
||||
let imageUrl = getImageUrl({ url: item?.largeImageUrl || null });
|
||||
|
||||
if (!imageUrl) {
|
||||
imageUrl = getCoverArtUrl({
|
||||
baseUrl: server?.url,
|
||||
coverArtId: `ar-${item.id}`,
|
||||
credential: server?.credential,
|
||||
size: 100,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
albumCount: item.albumCount,
|
||||
@@ -202,7 +215,7 @@ const normalizeAlbumArtist = (
|
||||
similarArtists:
|
||||
item.similarArtists?.map((artist) => ({
|
||||
id: artist.id,
|
||||
imageUrl: getImageUrl({ url: artist?.artistImageUrl || null }),
|
||||
imageUrl: artist?.artistImageUrl || null,
|
||||
name: artist.name,
|
||||
})) || null,
|
||||
songCount: item.songCount,
|
||||
|
||||
@@ -181,22 +181,30 @@ const song = z.object({
|
||||
bitRate: z.number(),
|
||||
bookmarkPosition: z.number(),
|
||||
bpm: z.number().optional(),
|
||||
catalogNum: z.string().optional(),
|
||||
channels: z.number().optional(),
|
||||
comment: z.string().optional(),
|
||||
compilation: z.boolean(),
|
||||
createdAt: z.string(),
|
||||
discNumber: z.number(),
|
||||
discSubtitle: z.string().optional(),
|
||||
duration: z.number(),
|
||||
embedArtPath: z.string().optional(),
|
||||
externalInfoUpdatedAt: z.string().optional(),
|
||||
externalUrl: z.string().optional(),
|
||||
fullText: z.string(),
|
||||
genre: z.string(),
|
||||
genres: z.array(genre),
|
||||
hasCoverArt: z.boolean(),
|
||||
id: z.string(),
|
||||
imageFiles: z.string().optional(),
|
||||
largeImageUrl: z.string().optional(),
|
||||
lyrics: z.string().optional(),
|
||||
mbzAlbumArtistId: z.string().optional(),
|
||||
mbzAlbumId: z.string().optional(),
|
||||
mbzArtistId: z.string().optional(),
|
||||
mbzTrackId: z.string().optional(),
|
||||
mediumImageUrl: z.string().optional(),
|
||||
orderAlbumArtistName: z.string(),
|
||||
orderAlbumName: z.string(),
|
||||
orderArtistName: z.string(),
|
||||
@@ -205,7 +213,12 @@ const song = z.object({
|
||||
playCount: z.number(),
|
||||
playDate: z.string(),
|
||||
rating: z.number().optional(),
|
||||
rgAlbumGain: z.number().optional(),
|
||||
rgAlbumPeak: z.number().optional(),
|
||||
rgTrackGain: z.number().optional(),
|
||||
rgTrackPeak: z.number().optional(),
|
||||
size: z.number(),
|
||||
smallImageUrl: z.string().optional(),
|
||||
sortAlbumArtistName: z.string(),
|
||||
sortArtistName: z.string(),
|
||||
starred: z.boolean(),
|
||||
@@ -246,6 +259,7 @@ const songListParameters = paginationParameters.extend({
|
||||
album_id: z.array(z.string()).optional(),
|
||||
artist_id: z.array(z.string()).optional(),
|
||||
genre_id: z.string().optional(),
|
||||
path: z.string().optional(),
|
||||
starred: z.boolean().optional(),
|
||||
title: z.string().optional(),
|
||||
year: z.number().optional(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { z } from 'zod';
|
||||
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
|
||||
import { ServerListItem } from '/@/renderer/api/types';
|
||||
import { toast } from '/@/renderer/components/toast/index';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const c = initContract();
|
||||
|
||||
@@ -106,7 +107,7 @@ axiosClient.interceptors.response.use(
|
||||
if (data['subsonic-response'].error.code !== 0) {
|
||||
toast.error({
|
||||
message: data['subsonic-response'].error.message,
|
||||
title: 'Issue from Subsonic API',
|
||||
title: i18n.t('error.genericError', { postProcess: 'sentenceCase' }) as string,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ const normalizeSong = (
|
||||
container: item.contentType,
|
||||
createdAt: item.created,
|
||||
discNumber: item.discNumber || 1,
|
||||
duration: item.duration || 0,
|
||||
discSubtitle: null,
|
||||
duration: item.duration ? item.duration * 1000 : 0,
|
||||
gain: null,
|
||||
genres: item.genre
|
||||
? [
|
||||
{
|
||||
@@ -86,6 +88,7 @@ const normalizeSong = (
|
||||
lyrics: null,
|
||||
name: item.title,
|
||||
path: item.path,
|
||||
peak: null,
|
||||
playCount: item?.playCount || 0,
|
||||
releaseDate: null,
|
||||
releaseYear: item.year ? String(item.year) : null,
|
||||
|
||||
@@ -171,6 +171,11 @@ export type Album = {
|
||||
userRating: number | null;
|
||||
} & { songs?: Song[] };
|
||||
|
||||
export type GainInfo = {
|
||||
album?: number;
|
||||
track?: number;
|
||||
};
|
||||
|
||||
export type Song = {
|
||||
album: string | null;
|
||||
albumArtists: RelatedArtist[];
|
||||
@@ -185,7 +190,9 @@ export type Song = {
|
||||
container: string | null;
|
||||
createdAt: string;
|
||||
discNumber: number;
|
||||
discSubtitle: string | null;
|
||||
duration: number;
|
||||
gain: GainInfo | null;
|
||||
genres: Genre[];
|
||||
id: string;
|
||||
imagePlaceholderUrl: string | null;
|
||||
@@ -195,6 +202,7 @@ export type Song = {
|
||||
lyrics: string | null;
|
||||
name: string;
|
||||
path: string | null;
|
||||
peak: GainInfo | null;
|
||||
playCount: number;
|
||||
playlistItemId?: string;
|
||||
releaseDate: string | null;
|
||||
@@ -387,7 +395,7 @@ export const albumListSortMap: AlbumListSortMap = {
|
||||
duration: undefined,
|
||||
favorited: undefined,
|
||||
name: JFAlbumListSort.NAME,
|
||||
playCount: undefined,
|
||||
playCount: JFAlbumListSort.PLAY_COUNT,
|
||||
random: JFAlbumListSort.RANDOM,
|
||||
rating: undefined,
|
||||
recentlyAdded: JFAlbumListSort.RECENTLY_ADDED,
|
||||
@@ -473,6 +481,7 @@ export type SongListQuery = {
|
||||
};
|
||||
albumIds?: string[];
|
||||
artistIds?: string[];
|
||||
imageSize?: number;
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
searchTerm?: string;
|
||||
@@ -1121,3 +1130,12 @@ export enum LyricSource {
|
||||
}
|
||||
|
||||
export type LyricsOverride = Omit<FullLyricsMetadata, 'lyrics'> & { id: string };
|
||||
|
||||
// This type from https://wicg.github.io/local-font-access/#fontdata
|
||||
// NOTE: it is still experimental, so this should be updates as appropriate
|
||||
export type FontData = {
|
||||
family: string;
|
||||
fullName: string;
|
||||
postscriptName: string;
|
||||
style: string;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model';
|
||||
import { ModuleRegistry } from '@ag-grid-community/core';
|
||||
import { InfiniteRowModelModule } from '@ag-grid-community/infinite-row-model';
|
||||
@@ -23,8 +23,10 @@ import { PlayQueueHandlerContext } from '/@/renderer/features/player';
|
||||
import { AddToPlaylistContextModal } from '/@/renderer/features/playlists';
|
||||
import { getMpvProperties } from '/@/renderer/features/settings/components/playback/mpv-settings';
|
||||
import { PlayerState, usePlayerStore, useQueueControls } from '/@/renderer/store';
|
||||
import { PlaybackType, PlayerStatus } from '/@/renderer/types';
|
||||
import { FontType, PlaybackType, PlayerStatus } from '/@/renderer/types';
|
||||
import '@ag-grid-community/styles/ag-grid.css';
|
||||
import { useDiscordRpc } from '/@/renderer/features/discord-rpc/use-discord-rpc';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
ModuleRegistry.registerModules([ClientSideRowModelModule, InfiniteRowModelModule]);
|
||||
|
||||
@@ -37,17 +39,56 @@ const remote = isElectron() ? window.electron.remote : null;
|
||||
|
||||
export const App = () => {
|
||||
const theme = useTheme();
|
||||
const contentFont = useSettingsStore((state) => state.general.fontContent);
|
||||
const accent = useSettingsStore((store) => store.general.accent);
|
||||
const language = useSettingsStore((store) => store.general.language);
|
||||
const { builtIn, custom, system, type } = useSettingsStore((state) => state.font);
|
||||
const { type: playbackType } = usePlaybackSettings();
|
||||
const { bindings } = useHotkeySettings();
|
||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||
const { clearQueue, restoreQueue } = useQueueControls();
|
||||
const remoteSettings = useRemoteSettings();
|
||||
const textStyleRef = useRef<HTMLStyleElement>();
|
||||
useDiscordRpc();
|
||||
|
||||
useEffect(() => {
|
||||
if (type === FontType.SYSTEM && system) {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--content-font-family', 'dynamic-font');
|
||||
|
||||
if (!textStyleRef.current) {
|
||||
textStyleRef.current = document.createElement('style');
|
||||
document.body.appendChild(textStyleRef.current);
|
||||
}
|
||||
|
||||
textStyleRef.current.textContent = `
|
||||
@font-face {
|
||||
font-family: "dynamic-font";
|
||||
src: local("${system}");
|
||||
}`;
|
||||
} else if (type === FontType.CUSTOM && custom) {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--content-font-family', 'dynamic-font');
|
||||
|
||||
if (!textStyleRef.current) {
|
||||
textStyleRef.current = document.createElement('style');
|
||||
document.body.appendChild(textStyleRef.current);
|
||||
}
|
||||
|
||||
textStyleRef.current.textContent = `
|
||||
@font-face {
|
||||
font-family: "dynamic-font";
|
||||
src: url("feishin://${custom}");
|
||||
}`;
|
||||
} else {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--content-font-family', builtIn);
|
||||
}
|
||||
}, [builtIn, custom, system, type]);
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty('--content-font-family', contentFont);
|
||||
}, [contentFont]);
|
||||
root.style.setProperty('--primary-color', accent);
|
||||
}, [accent]);
|
||||
|
||||
const providerValue = useMemo(() => {
|
||||
return { handlePlayQueueAdd };
|
||||
@@ -62,7 +103,8 @@ export const App = () => {
|
||||
|
||||
if (!isRunning) {
|
||||
const extraParameters = useSettingsStore.getState().playback.mpvExtraParameters;
|
||||
const properties = {
|
||||
const properties: Record<string, any> = {
|
||||
speed: usePlayerStore.getState().current.speed,
|
||||
...getMpvProperties(useSettingsStore.getState().playback.mpvProperties),
|
||||
};
|
||||
|
||||
@@ -73,6 +115,7 @@ export const App = () => {
|
||||
|
||||
mpvPlayer?.volume(properties.volume);
|
||||
}
|
||||
mpvPlayer?.restoreQueue();
|
||||
};
|
||||
|
||||
if (isElectron() && playbackType === PlaybackType.LOCAL) {
|
||||
@@ -94,8 +137,6 @@ export const App = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (isElectron()) {
|
||||
mpvPlayer!.restoreQueue();
|
||||
|
||||
mpvPlayerListener!.rendererSaveQueue(() => {
|
||||
const { current, queue } = usePlayerStore.getState();
|
||||
const stateToSave: Partial<Pick<PlayerState, 'current' | 'queue'>> = {
|
||||
@@ -139,6 +180,12 @@ export const App = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (language) {
|
||||
i18n.changeLanguage(language);
|
||||
}
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<MantineProvider
|
||||
withGlobalStyles
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useImperativeHandle, forwardRef, useRef, useState, useCallback, useEffect } from 'react';
|
||||
import isElectron from 'is-electron';
|
||||
import type { ReactPlayerProps } from 'react-player';
|
||||
import ReactPlayer from 'react-player';
|
||||
import ReactPlayer from 'react-player/lazy';
|
||||
import type { Song } from '/@/renderer/api/types';
|
||||
import {
|
||||
crossfadeHandler,
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
import type { CrossfadeStyle } from '/@/renderer/types';
|
||||
import { PlaybackStyle, PlayerStatus } from '/@/renderer/types';
|
||||
import { useSpeed } from '/@/renderer/store';
|
||||
|
||||
interface AudioPlayerProps extends ReactPlayerProps {
|
||||
crossfadeDuration: number;
|
||||
@@ -33,6 +34,11 @@ const getDuration = (ref: any) => {
|
||||
return ref.current?.player?.player?.player?.duration;
|
||||
};
|
||||
|
||||
type WebAudio = {
|
||||
context: AudioContext;
|
||||
gain: GainNode;
|
||||
};
|
||||
|
||||
export const AudioPlayer = forwardRef(
|
||||
(
|
||||
{
|
||||
@@ -49,10 +55,87 @@ export const AudioPlayer = forwardRef(
|
||||
}: AudioPlayerProps,
|
||||
ref: any,
|
||||
) => {
|
||||
const player1Ref = useRef<any>(null);
|
||||
const player2Ref = useRef<any>(null);
|
||||
const player1Ref = useRef<ReactPlayer>(null);
|
||||
const player2Ref = useRef<ReactPlayer>(null);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const audioDeviceId = useSettingsStore((state) => state.playback.audioDeviceId);
|
||||
const playback = useSettingsStore((state) => state.playback.mpvProperties);
|
||||
const playbackSpeed = useSpeed();
|
||||
|
||||
const [webAudio, setWebAudio] = useState<WebAudio | null>(null);
|
||||
const [player1Source, setPlayer1Source] = useState<MediaElementAudioSourceNode | null>(
|
||||
null,
|
||||
);
|
||||
const [player2Source, setPlayer2Source] = useState<MediaElementAudioSourceNode | null>(
|
||||
null,
|
||||
);
|
||||
const calculateReplayGain = useCallback(
|
||||
(song: Song): number => {
|
||||
if (playback.replayGainMode === 'no') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let gain: number | undefined;
|
||||
let peak: number | undefined;
|
||||
|
||||
if (playback.replayGainMode === 'track') {
|
||||
gain = song.gain?.track ?? song.gain?.album;
|
||||
peak = song.peak?.track ?? song.peak?.album;
|
||||
} else {
|
||||
gain = song.gain?.album ?? song.gain?.track;
|
||||
peak = song.peak?.album ?? song.peak?.track;
|
||||
}
|
||||
|
||||
if (gain === undefined) {
|
||||
gain = playback.replayGainFallbackDB;
|
||||
|
||||
if (!gain) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (peak === undefined) {
|
||||
peak = 1;
|
||||
}
|
||||
|
||||
const preAmp = playback.replayGainPreampDB ?? 0;
|
||||
|
||||
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification§ion=19
|
||||
// Normalized to max gain
|
||||
const expectedGain = 10 ** ((gain + preAmp) / 20);
|
||||
|
||||
if (playback.replayGainClip) {
|
||||
return Math.min(expectedGain, 1 / peak);
|
||||
}
|
||||
return expectedGain;
|
||||
},
|
||||
[
|
||||
playback.replayGainClip,
|
||||
playback.replayGainFallbackDB,
|
||||
playback.replayGainMode,
|
||||
playback.replayGainPreampDB,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if ('AudioContext' in window) {
|
||||
const context = new AudioContext({
|
||||
latencyHint: 'playback',
|
||||
sampleRate: playback.audioSampleRateHz || undefined,
|
||||
});
|
||||
const gain = context.createGain();
|
||||
gain.connect(context.destination);
|
||||
|
||||
setWebAudio({ context, gain });
|
||||
|
||||
return () => {
|
||||
return context.close();
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
// Intentionally ignore the sample rate dependency, as it makes things really messy
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
get player1() {
|
||||
@@ -159,12 +242,74 @@ export const AudioPlayer = forwardRef(
|
||||
}
|
||||
}, [audioDeviceId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (webAudio && player1Source) {
|
||||
if (player1 === undefined) {
|
||||
player1Source.disconnect();
|
||||
setPlayer1Source(null);
|
||||
} else if (currentPlayer === 1) {
|
||||
webAudio.gain.gain.setValueAtTime(calculateReplayGain(player1), 0);
|
||||
}
|
||||
}
|
||||
}, [calculateReplayGain, currentPlayer, player1, player1Source, webAudio]);
|
||||
|
||||
useEffect(() => {
|
||||
if (webAudio && player2Source) {
|
||||
if (player2 === undefined) {
|
||||
player2Source.disconnect();
|
||||
setPlayer2Source(null);
|
||||
} else if (currentPlayer === 2) {
|
||||
webAudio.gain.gain.setValueAtTime(calculateReplayGain(player2), 0);
|
||||
}
|
||||
}
|
||||
}, [calculateReplayGain, currentPlayer, player2, player2Source, webAudio]);
|
||||
|
||||
const handlePlayer1Start = useCallback(
|
||||
async (player: ReactPlayer) => {
|
||||
if (!webAudio || player1Source) return;
|
||||
if (webAudio.context.state !== 'running') {
|
||||
await webAudio.context.resume();
|
||||
}
|
||||
|
||||
const internal = player.getInternalPlayer() as HTMLMediaElement | undefined;
|
||||
if (internal) {
|
||||
const { context, gain } = webAudio;
|
||||
const source = context.createMediaElementSource(internal);
|
||||
source.connect(gain);
|
||||
setPlayer1Source(source);
|
||||
}
|
||||
},
|
||||
[player1Source, webAudio],
|
||||
);
|
||||
|
||||
const handlePlayer2Start = useCallback(
|
||||
async (player: ReactPlayer) => {
|
||||
if (!webAudio || player2Source) return;
|
||||
if (webAudio.context.state !== 'running') {
|
||||
await webAudio.context.resume();
|
||||
}
|
||||
|
||||
const internal = player.getInternalPlayer() as HTMLMediaElement | undefined;
|
||||
if (internal) {
|
||||
const { context, gain } = webAudio;
|
||||
const source = context.createMediaElementSource(internal);
|
||||
source.connect(gain);
|
||||
setPlayer2Source(source);
|
||||
}
|
||||
},
|
||||
[player2Source, webAudio],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ReactPlayer
|
||||
ref={player1Ref}
|
||||
config={{
|
||||
file: { attributes: { crossOrigin: 'anonymous' }, forceAudio: true },
|
||||
}}
|
||||
height={0}
|
||||
muted={muted}
|
||||
playbackRate={playbackSpeed}
|
||||
playing={currentPlayer === 1 && status === PlayerStatus.PLAYING}
|
||||
progressInterval={isTransitioning ? 10 : 250}
|
||||
url={player1?.streamUrl}
|
||||
@@ -174,11 +319,16 @@ export const AudioPlayer = forwardRef(
|
||||
onProgress={
|
||||
playbackStyle === PlaybackStyle.GAPLESS ? handleGapless1 : handleCrossfade1
|
||||
}
|
||||
onReady={handlePlayer1Start}
|
||||
/>
|
||||
<ReactPlayer
|
||||
ref={player2Ref}
|
||||
config={{
|
||||
file: { attributes: { crossOrigin: 'anonymous' }, forceAudio: true },
|
||||
}}
|
||||
height={0}
|
||||
muted={muted}
|
||||
playbackRate={playbackSpeed}
|
||||
playing={currentPlayer === 2 && status === PlayerStatus.PLAYING}
|
||||
progressInterval={isTransitioning ? 10 : 250}
|
||||
url={player2?.streamUrl}
|
||||
@@ -188,6 +338,7 @@ export const AudioPlayer = forwardRef(
|
||||
onProgress={
|
||||
playbackStyle === PlaybackStyle.GAPLESS ? handleGapless2 : handleCrossfade2
|
||||
}
|
||||
onReady={handlePlayer2Start}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ export const gaplessHandler = (args: {
|
||||
|
||||
const durationPadding = isFlac ? 0.065 : 0.116;
|
||||
if (currentTime + durationPadding >= duration) {
|
||||
return nextPlayerRef.current.getInternalPlayer().play();
|
||||
return nextPlayerRef.current.getInternalPlayer()?.play();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -27,8 +27,8 @@ const StyledButton = styled(MantineButton)<StyledButtonProps>`
|
||||
transition: background 0.2s ease-in-out, color 0.2s ease-in-out, border 0.2s ease-in-out;
|
||||
|
||||
svg {
|
||||
transition: fill 0.2s ease-in-out;
|
||||
fill: ${(props) => `var(--btn-${props.variant}-fg)`};
|
||||
transition: fill 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@@ -39,8 +39,9 @@ const StyledButton = styled(MantineButton)<StyledButtonProps>`
|
||||
}
|
||||
|
||||
&:not([data-disabled])&:hover {
|
||||
color: ${(props) => `var(--btn-${props.variant}-fg-hover) !important`};
|
||||
background: ${(props) => `var(--btn-${props.variant}-bg-hover)`};
|
||||
color: ${(props) => `var(--btn-${props.variant}-fg) !important`};
|
||||
background: ${(props) => `var(--btn-${props.variant}-bg)`};
|
||||
filter: brightness(85%);
|
||||
border: ${(props) => `var(--btn-${props.variant}-border-hover)`};
|
||||
|
||||
svg {
|
||||
@@ -50,11 +51,8 @@ const StyledButton = styled(MantineButton)<StyledButtonProps>`
|
||||
|
||||
&:not([data-disabled])&:focus-visible {
|
||||
color: ${(props) => `var(--btn-${props.variant}-fg-hover)`};
|
||||
background: ${(props) => `var(--btn-${props.variant}-bg-hover)`};
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: none;
|
||||
background: ${(props) => `var(--btn-${props.variant}-bg)`};
|
||||
filter: brightness(85%);
|
||||
}
|
||||
|
||||
& .mantine-Button-centerLoader {
|
||||
@@ -65,7 +63,6 @@ const StyledButton = styled(MantineButton)<StyledButtonProps>`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-right: 0.5rem;
|
||||
transform: translateY(-0.1rem);
|
||||
}
|
||||
|
||||
.mantine-Button-rightIcon {
|
||||
|
||||
@@ -14,9 +14,9 @@ const CardWrapper = styled.div<{
|
||||
link?: boolean;
|
||||
}>`
|
||||
padding: 1rem;
|
||||
cursor: ${({ link }) => link && 'pointer'};
|
||||
background: var(--card-default-bg);
|
||||
border-radius: var(--card-default-radius);
|
||||
cursor: ${({ link }) => link && 'pointer'};
|
||||
transition: border 0.2s ease-in-out, background 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
@@ -61,17 +61,17 @@ const ImageSection = styled.div`
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
content: '';
|
||||
user-select: none;
|
||||
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
`;
|
||||
|
||||
const Image = styled(SimpleImg)`
|
||||
border-radius: var(--card-default-radius);
|
||||
box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, 20%);
|
||||
box-shadow: 2px 2px 10px 2px rgb(0 0 0 / 20%);
|
||||
`;
|
||||
|
||||
const ControlsContainer = styled.div`
|
||||
@@ -95,8 +95,8 @@ const Row = styled.div<{ $secondary?: boolean }>`
|
||||
padding: 0 0.2rem;
|
||||
overflow: hidden;
|
||||
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const PlayButton = styled.button<PlayButtonType>`
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
background-color: rgb(255 255 255);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
opacity: 0.8;
|
||||
@@ -41,8 +41,8 @@ const PlayButton = styled.button<PlayButtonType>`
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: rgb(0, 0, 0);
|
||||
stroke: rgb(0, 0, 0);
|
||||
fill: rgb(0 0 0);
|
||||
stroke: rgb(0 0 0);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { generatePath } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Album, AlbumArtist, Artist, Playlist } from '/@/renderer/api/types';
|
||||
import { Album, AlbumArtist, Artist, Playlist, Song } from '/@/renderer/api/types';
|
||||
import { Text } from '/@/renderer/components/text';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { CardRow } from '/@/renderer/types';
|
||||
@@ -14,8 +14,8 @@ const Row = styled.div<{ $secondary?: boolean }>`
|
||||
padding: 0 0.2rem;
|
||||
overflow: hidden;
|
||||
color: ${({ $secondary }) => ($secondary ? 'var(--main-fg-secondary)' : 'var(--main-fg)')};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
@@ -183,6 +183,60 @@ export const ALBUM_CARD_ROWS: { [key: string]: CardRow<Album> } = {
|
||||
},
|
||||
};
|
||||
|
||||
export const SONG_CARD_ROWS: { [key: string]: CardRow<Song> } = {
|
||||
album: {
|
||||
property: 'album',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'albumId', slugProperty: 'albumId' }],
|
||||
},
|
||||
},
|
||||
albumArtists: {
|
||||
arrayProperty: 'name',
|
||||
property: 'albumArtists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
},
|
||||
},
|
||||
artists: {
|
||||
arrayProperty: 'name',
|
||||
property: 'artists',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
property: 'createdAt',
|
||||
},
|
||||
duration: {
|
||||
property: 'duration',
|
||||
},
|
||||
lastPlayedAt: {
|
||||
property: 'lastPlayedAt',
|
||||
},
|
||||
name: {
|
||||
property: 'name',
|
||||
route: {
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'albumId', slugProperty: 'albumId' }],
|
||||
},
|
||||
},
|
||||
playCount: {
|
||||
property: 'playCount',
|
||||
},
|
||||
rating: {
|
||||
property: 'userRating',
|
||||
},
|
||||
releaseDate: {
|
||||
property: 'releaseDate',
|
||||
},
|
||||
releaseYear: {
|
||||
property: 'releaseYear',
|
||||
},
|
||||
};
|
||||
|
||||
export const ALBUMARTIST_CARD_ROWS: { [key: string]: CardRow<AlbumArtist> } = {
|
||||
albumCount: {
|
||||
property: 'albumCount',
|
||||
|
||||
@@ -33,8 +33,8 @@ const PosterCardContainer = styled.div<{ $isHidden?: boolean }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
|
||||
pointer-events: auto;
|
||||
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
|
||||
|
||||
.card-controls {
|
||||
opacity: 0;
|
||||
@@ -57,11 +57,11 @@ const ImageContainerStyles = css`
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 100%) 35%, rgba(0, 0, 0, 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
content: '';
|
||||
user-select: none;
|
||||
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, ReactNode, Ref } from 'react';
|
||||
import { ComponentPropsWithoutRef, forwardRef, ReactNode, Ref } from 'react';
|
||||
import { Box, Group, UnstyledButton, UnstyledButtonProps } from '@mantine/core';
|
||||
import { motion, Variants } from 'framer-motion';
|
||||
import styled from 'styled-components';
|
||||
@@ -20,7 +20,7 @@ const ContextMenuContainer = styled(motion.div)<Omit<ContextMenuProps, 'children
|
||||
max-width: ${({ maxWidth }) => maxWidth}px;
|
||||
background: var(--dropdown-menu-bg);
|
||||
border-radius: var(--dropdown-menu-border-radius);
|
||||
box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, 40%);
|
||||
box-shadow: 2px 2px 10px 2px rgb(0 0 0 / 40%);
|
||||
|
||||
button:first-child {
|
||||
border-top-left-radius: var(--dropdown-menu-border-radius);
|
||||
@@ -35,13 +35,13 @@ const ContextMenuContainer = styled(motion.div)<Omit<ContextMenuProps, 'children
|
||||
|
||||
export const StyledContextMenuButton = styled(UnstyledButton)`
|
||||
padding: var(--dropdown-menu-item-padding);
|
||||
color: var(--dropdown-menu-fg);
|
||||
font-weight: 500;
|
||||
font-family: var(--content-font-family);
|
||||
font-weight: 500;
|
||||
color: var(--dropdown-menu-fg);
|
||||
text-align: left;
|
||||
cursor: default;
|
||||
background: var(--dropdown-menu-bg);
|
||||
border: none;
|
||||
cursor: default;
|
||||
|
||||
& .mantine-Button-inner {
|
||||
justify-content: flex-start;
|
||||
@@ -65,7 +65,7 @@ export const ContextMenuButton = forwardRef(
|
||||
leftIcon,
|
||||
...props
|
||||
}: UnstyledButtonProps &
|
||||
React.ComponentPropsWithoutRef<'button'> & {
|
||||
ComponentPropsWithoutRef<'button'> & {
|
||||
leftIcon?: ReactNode;
|
||||
rightIcon?: ReactNode;
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import styled from 'styled-components';
|
||||
const StyledDialog = styled(MantineDialog)`
|
||||
&.mantine-Dialog-root {
|
||||
background-color: var(--modal-bg);
|
||||
box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, 40%);
|
||||
box-shadow: 2px 2px 10px 2px rgb(0 0 0 / 40%);
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ const StyledMenuLabel = styled(MantineMenu.Label)<MenuLabelProps>`
|
||||
const StyledMenuItem = styled(MantineMenu.Item)<MenuItemProps>`
|
||||
position: relative;
|
||||
padding: var(--dropdown-menu-item-padding);
|
||||
font-size: var(--dropdown-menu-item-font-size);
|
||||
font-family: var(--content-font-family);
|
||||
font-size: var(--dropdown-menu-item-font-size);
|
||||
|
||||
cursor: default;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
@@ -50,17 +52,15 @@ const StyledMenuItem = styled(MantineMenu.Item)<MenuItemProps>`
|
||||
& .mantine-Menu-itemRightSection {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
cursor: default;
|
||||
`;
|
||||
|
||||
const StyledMenuDropdown = styled(MantineMenu.Dropdown)`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: var(--dropdown-menu-bg);
|
||||
filter: drop-shadow(0 0 5px rgb(0 0 0 / 50%));
|
||||
border: var(--dropdown-menu-border);
|
||||
border-radius: var(--dropdown-menu-border-radius);
|
||||
filter: drop-shadow(0 0 5px rgb(0, 0, 0, 50%));
|
||||
|
||||
/* *:first-child {
|
||||
border-top-left-radius: var(--dropdown-menu-border-radius);
|
||||
@@ -74,8 +74,8 @@ const StyledMenuDropdown = styled(MantineMenu.Dropdown)`
|
||||
`;
|
||||
|
||||
const StyledMenuDivider = styled(MantineMenu.Divider)`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
export const DropdownMenu = ({ children, ...props }: MenuProps) => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useState } from 'react';
|
||||
import { Group, Image, Stack } from '@mantine/core';
|
||||
import type { Variants } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||
import { Link, generatePath } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
@@ -20,16 +21,16 @@ const Carousel = styled(motion.div)`
|
||||
min-height: 250px;
|
||||
padding: 2rem;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(180deg, var(--main-bg), rgba(25, 26, 28, 60%));
|
||||
background: linear-gradient(180deg, var(--main-bg), rgb(25 26 28 / 60%));
|
||||
border-radius: 1rem;
|
||||
`;
|
||||
|
||||
const Grid = styled.div`
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas: 'image info';
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: 200px minmax(0, 1fr);
|
||||
grid-auto-columns: 1fr;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
@@ -59,10 +60,10 @@ const BackgroundImage = styled.img`
|
||||
z-index: 0;
|
||||
width: 150%;
|
||||
height: 150%;
|
||||
user-select: none;
|
||||
filter: blur(24px);
|
||||
object-fit: cover;
|
||||
object-position: 0 30%;
|
||||
filter: blur(24px);
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const BackgroundImageOverlay = styled.div`
|
||||
@@ -72,7 +73,7 @@ const BackgroundImageOverlay = styled.div`
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(25, 26, 28, 30%), var(--main-bg));
|
||||
background: linear-gradient(180deg, rgb(25 26 28 / 30%), var(--main-bg));
|
||||
`;
|
||||
|
||||
const Wrapper = styled(Link)`
|
||||
@@ -109,6 +110,7 @@ interface FeatureCarouselProps {
|
||||
}
|
||||
|
||||
export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
||||
const { t } = useTranslation();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const [itemIndex, setItemIndex] = useState(0);
|
||||
const [direction, setDirection] = useState(0);
|
||||
@@ -224,7 +226,7 @@ export const FeatureCarousel = ({ data }: FeatureCarouselProps) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
Play
|
||||
{t('player.play', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
<Group spacing="sm">
|
||||
<Button
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { isValidElement, memo, ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
isValidElement,
|
||||
memo,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Group, Stack } from '@mantine/core';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from 'react-icons/ri';
|
||||
@@ -109,6 +118,10 @@ export const SwiperGridCarousel = ({
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
|
||||
useEffect(() => {
|
||||
swiperRef.current?.slideTo(0, 0);
|
||||
}, [data]);
|
||||
|
||||
const [pagination, setPagination] = useState({
|
||||
hasNextPage: (data?.length || 0) > Math.round(3),
|
||||
hasPreviousPage: false,
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Flex, FlexProps } from '@mantine/core';
|
||||
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { ReactNode, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useShouldPadTitlebar, useTheme } from '/@/renderer/hooks';
|
||||
import { useWindowSettings } from '/@/renderer/store/settings.store';
|
||||
import { Platform } from '/@/renderer/types';
|
||||
|
||||
const Container = styled(motion(Flex))<{
|
||||
height?: string;
|
||||
position?: string;
|
||||
$height?: string;
|
||||
$position?: string;
|
||||
}>`
|
||||
position: ${(props) => props.position || 'relative'};
|
||||
position: ${(props) => props.$position || 'relative'};
|
||||
z-index: 200;
|
||||
width: 100%;
|
||||
height: ${(props) => props.height || '65px'};
|
||||
height: ${(props) => props.$height || '65px'};
|
||||
background: var(--titlebar-bg);
|
||||
`;
|
||||
|
||||
@@ -27,8 +27,8 @@ const Header = styled(motion.div)<{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-right: ${(props) => (props.$padRight ? '140px' : '1rem')};
|
||||
user-select: ${(props) => (props.$isHidden ? 'none' : 'auto')};
|
||||
pointer-events: ${(props) => (props.$isHidden ? 'none' : 'auto')};
|
||||
user-select: ${(props) => (props.$isHidden ? 'none' : 'auto')};
|
||||
-webkit-app-region: ${(props) => props.$isDraggable && 'drag'};
|
||||
|
||||
button {
|
||||
@@ -40,13 +40,13 @@ const Header = styled(motion.div)<{
|
||||
}
|
||||
`;
|
||||
|
||||
const BackgroundImage = styled.div<{ background: string }>`
|
||||
const BackgroundImage = styled.div<{ $background: string }>`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${(props) => props.background || 'var(--titlebar-bg)'};
|
||||
background: ${(props) => props.$background || 'var(--titlebar-bg)'};
|
||||
`;
|
||||
|
||||
const BackgroundImageOverlay = styled.div<{ theme: 'light' | 'dark' }>`
|
||||
@@ -66,7 +66,7 @@ export interface PageHeaderProps
|
||||
extends Omit<FlexProps, 'onAnimationStart' | 'onDragStart' | 'onDragEnd' | 'onDrag'> {
|
||||
animated?: boolean;
|
||||
backgroundColor?: string;
|
||||
children?: React.ReactNode;
|
||||
children?: ReactNode;
|
||||
height?: string;
|
||||
isHidden?: boolean;
|
||||
position?: string;
|
||||
@@ -106,34 +106,36 @@ export const PageHeader = ({
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Container
|
||||
ref={ref}
|
||||
height={height}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<Header
|
||||
$isDraggable={windowBarStyle === Platform.WEB}
|
||||
$isHidden={isHidden}
|
||||
$padRight={padRight}
|
||||
<>
|
||||
<Container
|
||||
ref={ref}
|
||||
$height={height}
|
||||
$position={position}
|
||||
{...props}
|
||||
>
|
||||
<AnimatePresence initial={animated ?? false}>
|
||||
<TitleWrapper
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
initial="initial"
|
||||
variants={variants}
|
||||
>
|
||||
{children}
|
||||
</TitleWrapper>
|
||||
</AnimatePresence>
|
||||
</Header>
|
||||
{backgroundColor && (
|
||||
<>
|
||||
<BackgroundImage background={backgroundColor || 'var(--titlebar-bg)'} />
|
||||
<BackgroundImageOverlay theme={theme} />
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
<Header
|
||||
$isDraggable={windowBarStyle === Platform.WEB}
|
||||
$isHidden={isHidden}
|
||||
$padRight={padRight}
|
||||
>
|
||||
<AnimatePresence initial={animated ?? false}>
|
||||
<TitleWrapper
|
||||
animate="animate"
|
||||
exit="exit"
|
||||
initial="initial"
|
||||
variants={variants}
|
||||
>
|
||||
{children}
|
||||
</TitleWrapper>
|
||||
</AnimatePresence>
|
||||
</Header>
|
||||
{backgroundColor && (
|
||||
<>
|
||||
<BackgroundImage $background={backgroundColor || 'var(--titlebar-bg)'} />
|
||||
<BackgroundImageOverlay theme={theme as 'light' | 'dark'} />
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ReactNode } from 'react';
|
||||
import type { PaperProps as MantinePaperProps } from '@mantine/core';
|
||||
import { Paper as MantinePaper } from '@mantine/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export interface PaperProps extends MantinePaperProps {
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const StyledPaper = styled(MantinePaper)<PaperProps>`
|
||||
|
||||