diff --git a/.eslintrc.js b/.eslintrc.js index 841a32643b..44fd0a3046 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,6 @@ module.exports = { plugins: [ '@typescript-eslint', 'react', - 'promise', 'import', 'eslint-comments', 'sonarjs' @@ -20,7 +19,6 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', - // 'plugin:promise/recommended', 'plugin:import/errors', 'plugin:eslint-comments/recommended', 'plugin:compat/recommended', @@ -37,11 +35,18 @@ module.exports = { 'indent': ['error', 4, { 'SwitchCase': 1 }], 'jsx-quotes': ['error', 'prefer-single'], 'keyword-spacing': ['error'], - 'no-throw-literal': ['error'], 'max-statements-per-line': ['error'], 'max-params': ['error', 7], + 'new-cap': [ + 'error', + { + 'capIsNewExceptions': ['jQuery.Deferred'], + 'newIsCapExceptionPattern': '\\.default$' + } + ], 'no-duplicate-imports': ['error'], 'no-empty-function': ['error'], + 'no-extend-native': ['error'], 'no-floating-decimal': ['error'], 'no-multi-spaces': ['error'], 'no-multiple-empty-lines': ['error', { 'max': 1 }], @@ -54,6 +59,7 @@ module.exports = { 'no-sequences': ['error', { 'allowInParentheses': false }], 'no-shadow': ['off'], '@typescript-eslint/no-shadow': ['error'], + 'no-throw-literal': ['error'], 'no-trailing-spaces': ['error'], 'no-unused-expressions': ['off'], '@typescript-eslint/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], @@ -68,6 +74,7 @@ module.exports = { 'padded-blocks': ['error', 'never'], 'prefer-const': ['error', { 'destructuring': 'all' }], '@typescript-eslint/prefer-for-of': ['error'], + '@typescript-eslint/prefer-optional-chain': ['error'], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], 'radix': ['error'], '@typescript-eslint/semi': ['error'], @@ -206,10 +213,7 @@ module.exports = { // JavaScript source files { files: [ - './src/**/*.js', - './src/**/*.jsx', - './src/**/*.ts', - './src/**/*.tsx' + './src/**/*.{js,jsx,ts,tsx}' ], parserOptions: { project: ['./tsconfig.json'] @@ -253,15 +257,13 @@ module.exports = { 'Windows': 'readonly' }, rules: { - '@typescript-eslint/no-floating-promises': ['warn'], '@typescript-eslint/prefer-string-starts-ends-with': ['error'] } }, // TypeScript source files { files: [ - './src/**/*.ts', - './src/**/*.tsx' + './src/**/*.{ts,tsx}' ], extends: [ 'eslint:recommended', @@ -274,6 +276,7 @@ module.exports = { ], rules: { '@typescript-eslint/no-floating-promises': ['error'], + '@typescript-eslint/no-unused-vars': ['error'], 'sonarjs/cognitive-complexity': ['error'] } diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c6ad6da22c..6c8ffdbbf1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,13 +19,13 @@ jobs: language: [ 'javascript' ] steps: - name: Checkout repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Initialize CodeQL - uses: github/codeql-action/init@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6 + uses: github/codeql-action/init@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6 + uses: github/codeql-action/autobuild@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6 + uses: github/codeql-action/analyze@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3 diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 27bf335e19..f9b97849a7 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -12,13 +12,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify as seen - uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1 + uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2 with: token: ${{ secrets.JF_BOT_TOKEN }} comment-id: ${{ github.event.comment.id }} reactions: '+1' - name: Checkout the latest code - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: token: ${{ secrets.JF_BOT_TOKEN }} fetch-depth: 0 @@ -28,7 +28,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} - name: Comment on failure if: failure() - uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1 + uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2 with: token: ${{ secrets.JF_BOT_TOKEN }} issue-number: ${{ github.event.issue.number }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2f6e8a3b06..085e9d7893 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup node environment - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: node-version: 16 check-latest: true @@ -37,10 +37,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup node environment - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: node-version: 16 check-latest: true @@ -58,10 +58,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup node environment - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: node-version: 16 check-latest: true @@ -82,10 +82,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup node environment - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: node-version: 16 check-latest: true diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml index bd9975f12c..2d30664b6f 100644 --- a/.github/workflows/tsc.yml +++ b/.github/workflows/tsc.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup node environment - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 + uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 with: node-version: 16 check-latest: true diff --git a/package-lock.json b/package-lock.json index 64c7e6147c..a2ca04bbd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,16 +11,20 @@ "dependencies": { "@emotion/react": "11.11.0", "@emotion/styled": "11.11.0", - "@fontsource/noto-sans": "4.5.11", - "@fontsource/noto-sans-hk": "4.5.12", - "@fontsource/noto-sans-jp": "4.5.12", - "@fontsource/noto-sans-kr": "4.5.12", - "@fontsource/noto-sans-sc": "4.5.12", - "@fontsource/noto-sans-tc": "4.5.12", + "@fontsource/noto-sans": "5.0.4", + "@fontsource/noto-sans-hk": "5.0.4", + "@fontsource/noto-sans-jp": "5.0.4", + "@fontsource/noto-sans-kr": "5.0.4", + "@fontsource/noto-sans-sc": "5.0.4", + "@fontsource/noto-sans-tc": "5.0.4", "@jellyfin/sdk": "unstable", "@loadable/component": "5.15.3", "@mui/icons-material": "5.11.16", "@mui/material": "5.13.3", + "@mui/x-data-grid": "6.6.0", + "@react-hook/resize-observer": "1.2.6", + "@tanstack/react-query": "4.29.12", + "@tanstack/react-query-devtools": "4.29.12", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", @@ -55,8 +59,8 @@ "swiper": "9.3.2", "webcomponents.js": "0.7.24", "whatwg-fetch": "3.6.2", - "workbox-core": "6.5.4", - "workbox-precaching": "6.5.4" + "workbox-core": "6.6.0", + "workbox-precaching": "6.6.0" }, "devDependencies": { "@babel/core": "7.21.8", @@ -80,7 +84,7 @@ "confusing-browser-globals": "1.0.11", "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", - "css-loader": "6.7.4", + "css-loader": "6.8.1", "cssnano": "6.0.1", "es-check": "7.1.1", "eslint": "8.41.0", @@ -88,20 +92,19 @@ "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-sonarjs": "0.19.0", "expose-loader": "4.1.0", "html-loader": "4.2.0", - "html-webpack-plugin": "5.5.1", + "html-webpack-plugin": "5.5.3", "mini-css-extract-plugin": "2.7.6", "postcss": "8.4.24", - "postcss-loader": "7.3.1", + "postcss-loader": "7.3.3", "postcss-preset-env": "8.4.1", "postcss-scss": "4.0.6", "sass": "1.62.1", - "sass-loader": "13.3.0", + "sass-loader": "13.3.2", "source-map-loader": "4.0.1", "style-loader": "3.3.3", "stylelint": "15.6.2", @@ -109,13 +112,13 @@ "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "6.0.3", "stylelint-scss": "5.0.0", - "ts-loader": "9.4.3", + "ts-loader": "9.4.4", "typescript": "5.0.4", - "webpack": "5.84.1", - "webpack-cli": "5.1.1", - "webpack-dev-server": "4.15.0", + "webpack": "5.88.1", + "webpack-cli": "5.1.4", + "webpack-dev-server": "4.15.1", "webpack-merge": "5.9.0", - "workbox-webpack-plugin": "6.5.4", + "workbox-webpack-plugin": "6.6.0", "worker-loader": "3.0.8" }, "engines": { @@ -2850,34 +2853,34 @@ } }, "node_modules/@fontsource/noto-sans": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-4.5.11.tgz", - "integrity": "sha512-lBX7FCjIjSrQ+iMuXUuO+mbjbnUsJyZANg/04PgkeeAYe+cwnX81ibbgrAk1F56M6/btIrWQoDjxsK6Sz8KoVQ==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-5.0.4.tgz", + "integrity": "sha512-UFOkapwBv5MXfWT4Yralf3X+dyeqcHipp4stQ1hPzY4SgmZ/rnvJDmYiskAVHyucL52Kv/RHVCRJzrqYTE1e8g==" }, "node_modules/@fontsource/noto-sans-hk": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-4.5.12.tgz", - "integrity": "sha512-AWDAqUQpSXlraTZXCdwV9FOVuAVHmhed4bvqOjRqksfeMWZAAMiGOi1QfP4XWnGOzjLEZdV+4aNWc4RYw4suuQ==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-5.0.4.tgz", + "integrity": "sha512-AvHZQhw4EzdjmnOxzKZF/Trmm7WtR7pfQCuEA+6TBBIkGwama76JwIFR/zwvMiqNckY3AJrTxpH3lQPA/PclAw==" }, "node_modules/@fontsource/noto-sans-jp": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-4.5.12.tgz", - "integrity": "sha512-tdJAUEyuyyCRD4Ot4ZE8+3uyGeSJQD/soSHr7LoltEQr22IotDDWOlqQKF7CpTaJPwt4iOBoXWtAoSN0huVW6A==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-5.0.4.tgz", + "integrity": "sha512-RPkXsYv8ELml8LALba6ORKqx/Wyi97yw+8J5kZLyOYPHEKAvc5pAQQC5PV6UhWr/SlSJLLGoDwczHT/y2fJBsg==" }, "node_modules/@fontsource/noto-sans-kr": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-4.5.12.tgz", - "integrity": "sha512-6LBOzXw/V4guB/nATaiUjCcPxwJNGw+ky5DK7/wsgUZvfk8etH9IlKdvjFVidpeCuMVjOdlka2+MEw4hwEHaHA==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-5.0.4.tgz", + "integrity": "sha512-7HHWodiKUHOUtArboo2/Bma6pqkppo/hB05pT1/H0LJ2CZIWW9dS7TAp0qcPFidYMwyvfrs2SFG77LI42qdiMg==" }, "node_modules/@fontsource/noto-sans-sc": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.12.tgz", - "integrity": "sha512-KQ8Bn8KCGVS6RKrEw7fp65GJYpKyKEJ/TkePoKGxvNy7smSaHoKV7vrmpIiA46hkqQyYdENix/NKSprxs4Soyw==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-5.0.4.tgz", + "integrity": "sha512-OE7hJdAyyyLppiEBlNKsg/ef+CBt+jZYHofdXHkGWV4e7cREU5xgidL5RzOTOkcuLjPegEJPN0lW4voY3SSBjw==" }, "node_modules/@fontsource/noto-sans-tc": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.12.tgz", - "integrity": "sha512-c4OUY5Wq03ZLI4Ds9IEHEiPqpjmEXvDpANXufCTlk+bMb8MnweXPVWJSrSHydXnz7r+qstZl3HvWDl2RI5o/1A==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-5.0.4.tgz", + "integrity": "sha512-Y+c7nsyauKnfrNO/MsbmfOGTWfeEPEQt/r1jb55GHmYK1US1G7IxORZ3KN1g95mG87XOfmi3J/uGb9f8oBmxYA==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", @@ -2913,9 +2916,9 @@ "dev": true }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202305300501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202305300501.tgz", - "integrity": "sha512-xAiVZQFtnRkikiYcYSue75+socgwVY+NwY3PaRDTbjH90Guo4ptcLXmlgAFcUad+J3jpwpdAR9+fKmSomUFKRA==", + "version": "0.0.0-unstable.202307130502", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202307130502.tgz", + "integrity": "sha512-1+GXATaJLP5akFnUrpxYzoshLtTPZXJEdy/ozhY1g/DkULlz4LthLTaTJ2qImF0mb8Ayk7LNbh00n4ATk0JycA==", "peerDependencies": { "axios": "^1.3.4" } @@ -2978,6 +2981,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -3040,9 +3048,9 @@ } }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "optional": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3329,6 +3337,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/@mui/x-data-grid": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.6.0.tgz", + "integrity": "sha512-RCAdQM4D0RWLnFCtuv6pJEFqUtH3WmlTU2Av+p3Ir2Utw03FQzc81oOpOZNv/6SXv+rMa+i42pb6fW0U5Xz0AQ==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.13.1", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.4.1", + "@mui/system": "^5.4.1", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3373,6 +3406,35 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-hook/latest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", + "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/resize-observer": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-1.2.6.tgz", + "integrity": "sha512-DlBXtLSW0DqYYTW3Ft1/GQFZlTdKY5VAFIC4+km6IK5NiPPDFchGbEJm1j6pSgMqPRHbUQgHJX7RaR76ic1LWA==", + "dependencies": { + "@juggle/resize-observer": "^3.3.1", + "@react-hook/latest": "^1.0.2", + "@react-hook/passive-layout-effect": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/@remix-run/router": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.1.tgz", @@ -3472,6 +3534,75 @@ "string.prototype.matchall": "^4.0.6" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, + "node_modules/@tanstack/query-core": { + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz", + "integrity": "sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.12.tgz", + "integrity": "sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA==", + "dependencies": { + "@tanstack/query-core": "4.29.11", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.12.tgz", + "integrity": "sha512-ug4YGQhMhh6QI8/sWJhjXxuvdeehxf1cyxpTifGMH5qreQ5ECHT6vzqG/aKvADQDzqLBGrF0q4wTDnRRYvvtrA==", + "dependencies": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "4.29.12", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -3792,9 +3923,9 @@ } }, "node_modules/@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", "dev": true }, "node_modules/@types/unist": { @@ -3825,9 +3956,9 @@ } }, "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", "dev": true, "dependencies": { "@types/node": "*" @@ -3868,9 +3999,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4023,9 +4154,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4064,9 +4195,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4248,9 +4379,9 @@ } }, "node_modules/@webpack-cli/configtest": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz", - "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "engines": { "node": ">=14.15.0" @@ -4261,9 +4392,9 @@ } }, "node_modules/@webpack-cli/info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", - "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, "engines": { "node": ">=14.15.0" @@ -4274,9 +4405,9 @@ } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz", - "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "engines": { "node": ">=14.15.0" @@ -5880,6 +6011,20 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -6102,15 +6247,15 @@ } }, "node_modules/css-loader": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.4.tgz", - "integrity": "sha512-0Y5uHtK5BswfaGJ+jrO+4pPg1msFBc0pwPIE1VqfpmVn6YbDfYfXMj8rfd7nt+4goAhJueO+H/I40VWJfcP1mQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.1", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -6128,9 +6273,9 @@ } }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -6981,9 +7126,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", - "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -7582,18 +7727,6 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, "node_modules/eslint-plugin-react": { "version": "7.32.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", @@ -9489,9 +9622,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz", - "integrity": "sha512-cTUzZ1+NqjGEKjmVgZKLMdiFg3m9MdRXkZW2OEe69WYVi5ONLMmlnSZdXzGGMOq0C8jGDrL6EWyEDDUioHO/pA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", + "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -9713,9 +9846,9 @@ } }, "node_modules/idb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz", - "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "dev": true }, "node_modules/ignore": { @@ -10460,6 +10593,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-what": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.13.tgz", + "integrity": "sha512-Aoe8pT24sWzyoO0S2PTDyutGp9l7qYHyFtzYlC8hMLshyqV/minljBANT4f2hiS5OxnWvcKMiA5io+VaLMJ1oA==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -10736,15 +10880,6 @@ "node": ">=0.10.0" } }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/known-css-properties": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", @@ -11186,9 +11321,9 @@ } }, "node_modules/meow/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11628,9 +11763,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -12871,14 +13006,13 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.1.tgz", - "integrity": "sha512-uevGt8yy2gvruNvzy8jxgYSSnyqBcA7CnS6/57qoZnUMM51XgsTqxIpWZWdHyvIyo4ov0lCgnzIbhtWwVFI8lg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", + "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", "dev": true, "dependencies": { - "cosmiconfig": "^8.1.3", + "cosmiconfig": "^8.2.0", "jiti": "^1.18.2", - "klona": "^2.0.6", "semver": "^7.3.8" }, "engines": { @@ -12900,9 +13034,9 @@ "dev": true }, "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "dependencies": { "import-fresh": "^3.2.1", @@ -12973,9 +13107,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -14466,6 +14600,11 @@ "node": ">= 0.10" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -14521,6 +14660,11 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -14778,12 +14922,11 @@ } }, "node_modules/sass-loader": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.0.tgz", - "integrity": "sha512-LeWNswSEujsZnwdn9AuA+Q5wZEAFlU+eORQsDKp35OtGAfFxYxpfk/Ylon+TGGkazSqxi2EHDTqdr3di8r7nCg==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", + "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", "dev": true, "dependencies": { - "klona": "^2.0.6", "neo-async": "^2.6.2" }, "engines": { @@ -14873,9 +15016,9 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "devOptional": true, "bin": { "semver": "bin/semver.js" @@ -18396,6 +18539,17 @@ "node": ">=6" } }, + "node_modules/superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -18942,9 +19096,9 @@ } }, "node_modules/ts-loader": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz", - "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "dependencies": { "chalk": "^4.1.0", @@ -19019,9 +19173,9 @@ } }, "node_modules/ts-loader/node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -19430,6 +19584,14 @@ "node": ">=0.10.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -19556,9 +19718,9 @@ "dev": true }, "node_modules/webpack": { - "version": "5.84.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.84.1.tgz", - "integrity": "sha512-ZP4qaZ7vVn/K8WN/p990SGATmrL1qg4heP/MrVneczYtpDGJWlrgZv55vxaV2ul885Kz+25MP2kSXkPe3LZfmg==", + "version": "5.88.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -19570,7 +19732,7 @@ "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.1", + "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -19580,7 +19742,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", @@ -19603,15 +19765,15 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz", - "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.0", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.4", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", "commander": "^10.0.1", "cross-spawn": "^7.0.3", @@ -19692,9 +19854,9 @@ "dev": true }, "node_modules/webpack-dev-server": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz", - "integrity": "sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -19703,7 +19865,7 @@ "@types/serve-index": "^1.9.1", "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", + "@types/ws": "^8.5.5", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -19831,9 +19993,9 @@ "dev": true }, "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", - "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -20047,28 +20209,28 @@ } }, "node_modules/workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", "dev": true, "dependencies": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", "dev": true, "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", "dev": true, "dependencies": { "@apideck/better-ajv-errors": "^0.3.1", @@ -20093,21 +20255,21 @@ "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" }, "engines": { "node": ">=10.0.0" @@ -20126,126 +20288,127 @@ } }, "node_modules/workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", "dev": true, "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" }, "node_modules/workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", "dev": true, "dependencies": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", "dev": true, "dependencies": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", "dev": true, "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", "dev": true, "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", "dev": true, "dependencies": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "node_modules/workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", "dependencies": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", "dev": true, "dependencies": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" } }, "node_modules/workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==", "dev": true }, "node_modules/workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", "dev": true, "dependencies": { "fast-json-stable-stringify": "^2.1.0", "pretty-bytes": "^5.4.1", "upath": "^1.2.0", "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" + "workbox-build": "6.6.0" }, "engines": { "node": ">=10.0.0" @@ -20274,13 +20437,13 @@ } }, "node_modules/workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", "dev": true, "dependencies": { "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "node_modules/worker-loader": { @@ -22228,34 +22391,34 @@ "dev": true }, "@fontsource/noto-sans": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-4.5.11.tgz", - "integrity": "sha512-lBX7FCjIjSrQ+iMuXUuO+mbjbnUsJyZANg/04PgkeeAYe+cwnX81ibbgrAk1F56M6/btIrWQoDjxsK6Sz8KoVQ==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-5.0.4.tgz", + "integrity": "sha512-UFOkapwBv5MXfWT4Yralf3X+dyeqcHipp4stQ1hPzY4SgmZ/rnvJDmYiskAVHyucL52Kv/RHVCRJzrqYTE1e8g==" }, "@fontsource/noto-sans-hk": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-4.5.12.tgz", - "integrity": "sha512-AWDAqUQpSXlraTZXCdwV9FOVuAVHmhed4bvqOjRqksfeMWZAAMiGOi1QfP4XWnGOzjLEZdV+4aNWc4RYw4suuQ==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-5.0.4.tgz", + "integrity": "sha512-AvHZQhw4EzdjmnOxzKZF/Trmm7WtR7pfQCuEA+6TBBIkGwama76JwIFR/zwvMiqNckY3AJrTxpH3lQPA/PclAw==" }, "@fontsource/noto-sans-jp": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-4.5.12.tgz", - "integrity": "sha512-tdJAUEyuyyCRD4Ot4ZE8+3uyGeSJQD/soSHr7LoltEQr22IotDDWOlqQKF7CpTaJPwt4iOBoXWtAoSN0huVW6A==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-5.0.4.tgz", + "integrity": "sha512-RPkXsYv8ELml8LALba6ORKqx/Wyi97yw+8J5kZLyOYPHEKAvc5pAQQC5PV6UhWr/SlSJLLGoDwczHT/y2fJBsg==" }, "@fontsource/noto-sans-kr": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-4.5.12.tgz", - "integrity": "sha512-6LBOzXw/V4guB/nATaiUjCcPxwJNGw+ky5DK7/wsgUZvfk8etH9IlKdvjFVidpeCuMVjOdlka2+MEw4hwEHaHA==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-5.0.4.tgz", + "integrity": "sha512-7HHWodiKUHOUtArboo2/Bma6pqkppo/hB05pT1/H0LJ2CZIWW9dS7TAp0qcPFidYMwyvfrs2SFG77LI42qdiMg==" }, "@fontsource/noto-sans-sc": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.12.tgz", - "integrity": "sha512-KQ8Bn8KCGVS6RKrEw7fp65GJYpKyKEJ/TkePoKGxvNy7smSaHoKV7vrmpIiA46hkqQyYdENix/NKSprxs4Soyw==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-5.0.4.tgz", + "integrity": "sha512-OE7hJdAyyyLppiEBlNKsg/ef+CBt+jZYHofdXHkGWV4e7cREU5xgidL5RzOTOkcuLjPegEJPN0lW4voY3SSBjw==" }, "@fontsource/noto-sans-tc": { - "version": "4.5.12", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.12.tgz", - "integrity": "sha512-c4OUY5Wq03ZLI4Ds9IEHEiPqpjmEXvDpANXufCTlk+bMb8MnweXPVWJSrSHydXnz7r+qstZl3HvWDl2RI5o/1A==" + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-5.0.4.tgz", + "integrity": "sha512-Y+c7nsyauKnfrNO/MsbmfOGTWfeEPEQt/r1jb55GHmYK1US1G7IxORZ3KN1g95mG87XOfmi3J/uGb9f8oBmxYA==" }, "@humanwhocodes/config-array": { "version": "0.11.8", @@ -22281,9 +22444,9 @@ "dev": true }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202305300501", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202305300501.tgz", - "integrity": "sha512-xAiVZQFtnRkikiYcYSue75+socgwVY+NwY3PaRDTbjH90Guo4ptcLXmlgAFcUad+J3jpwpdAR9+fKmSomUFKRA==", + "version": "0.0.0-unstable.202307130502", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202307130502.tgz", + "integrity": "sha512-1+GXATaJLP5akFnUrpxYzoshLtTPZXJEdy/ozhY1g/DkULlz4LthLTaTJ2qImF0mb8Ayk7LNbh00n4ATk0JycA==", "requires": {} }, "@jridgewell/gen-mapping": { @@ -22335,6 +22498,11 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -22378,9 +22546,9 @@ } }, "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "optional": true, "requires": { "lru-cache": "^6.0.0" @@ -22526,6 +22694,18 @@ } } }, + "@mui/x-data-grid": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.6.0.tgz", + "integrity": "sha512-RCAdQM4D0RWLnFCtuv6pJEFqUtH3WmlTU2Av+p3Ir2Utw03FQzc81oOpOZNv/6SXv+rMa+i42pb6fW0U5Xz0AQ==", + "requires": { + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.13.1", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -22557,6 +22737,28 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, + "@react-hook/latest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@react-hook/latest/-/latest-1.0.3.tgz", + "integrity": "sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==", + "requires": {} + }, + "@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "requires": {} + }, + "@react-hook/resize-observer": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@react-hook/resize-observer/-/resize-observer-1.2.6.tgz", + "integrity": "sha512-DlBXtLSW0DqYYTW3Ft1/GQFZlTdKY5VAFIC4+km6IK5NiPPDFchGbEJm1j6pSgMqPRHbUQgHJX7RaR76ic1LWA==", + "requires": { + "@juggle/resize-observer": "^3.3.1", + "@react-hook/latest": "^1.0.2", + "@react-hook/passive-layout-effect": "^1.2.0" + } + }, "@remix-run/router": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.1.tgz", @@ -22627,6 +22829,38 @@ "string.prototype.matchall": "^4.0.6" } }, + "@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "requires": { + "remove-accents": "0.4.2" + } + }, + "@tanstack/query-core": { + "version": "4.29.11", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.11.tgz", + "integrity": "sha512-8C+hF6SFAb/TlFZyS9FItgNwrw4PMa7YeX+KQYe2ZAiEz6uzg6yIr+QBzPkUwZ/L0bXvGd1sufTm3wotoz+GwQ==" + }, + "@tanstack/react-query": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.12.tgz", + "integrity": "sha512-zhcN6+zF6cxprxhTHQajHGlvxgK8npnp9uLe9yaWhGc6sYcPWXzyO4raL4HomUzQOPzu3jLvkriJQ7BOrDM8vA==", + "requires": { + "@tanstack/query-core": "4.29.11", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/react-query-devtools": { + "version": "4.29.12", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.29.12.tgz", + "integrity": "sha512-ug4YGQhMhh6QI8/sWJhjXxuvdeehxf1cyxpTifGMH5qreQ5ECHT6vzqG/aKvADQDzqLBGrF0q4wTDnRRYvvtrA==", + "requires": { + "@tanstack/match-sorter-utils": "^8.7.0", + "superjson": "^1.10.0", + "use-sync-external-store": "^1.2.0" + } + }, "@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -22943,9 +23177,9 @@ } }, "@types/trusted-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", - "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", "dev": true }, "@types/unist": { @@ -22975,9 +23209,9 @@ } }, "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", + "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", "dev": true, "requires": { "@types/node": "*" @@ -23002,9 +23236,9 @@ }, "dependencies": { "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23088,9 +23322,9 @@ } }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23115,9 +23349,9 @@ }, "dependencies": { "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23288,23 +23522,23 @@ } }, "@webpack-cli/configtest": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz", - "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "requires": {} }, "@webpack-cli/info": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", - "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, "requires": {} }, "@webpack-cli/serve": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz", - "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "requires": {} }, @@ -24508,6 +24742,14 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, + "copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "requires": { + "is-what": "^4.1.8" + } + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -24649,15 +24891,15 @@ } }, "css-loader": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.4.tgz", - "integrity": "sha512-0Y5uHtK5BswfaGJ+jrO+4pPg1msFBc0pwPIE1VqfpmVn6YbDfYfXMj8rfd7nt+4goAhJueO+H/I40VWJfcP1mQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", "dev": true, "requires": { "icss-utils": "^5.1.0", "postcss": "^8.4.21", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.1", + "postcss-modules-local-by-default": "^4.0.3", "postcss-modules-scope": "^3.0.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", @@ -24665,9 +24907,9 @@ }, "dependencies": { "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -25307,9 +25549,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", - "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -25965,13 +26207,6 @@ } } }, - "eslint-plugin-promise": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", - "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", - "dev": true, - "requires": {} - }, "eslint-plugin-react": { "version": "7.32.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz", @@ -27230,9 +27465,9 @@ "dev": true }, "html-webpack-plugin": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz", - "integrity": "sha512-cTUzZ1+NqjGEKjmVgZKLMdiFg3m9MdRXkZW2OEe69WYVi5ONLMmlnSZdXzGGMOq0C8jGDrL6EWyEDDUioHO/pA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", + "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", "dev": true, "requires": { "@types/html-minifier-terser": "^6.0.0", @@ -27388,9 +27623,9 @@ "requires": {} }, "idb": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.0.2.tgz", - "integrity": "sha512-jjKrT1EnyZewQ/gCBb/eyiYrhGzws2FeY92Yx8qT9S9GeQAmo4JFVIiWRIfKW/6Ob9A+UDAOW9j9jn58fy2HIg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "dev": true }, "ignore": { @@ -27907,6 +28142,11 @@ "get-intrinsic": "^1.1.1" } }, + "is-what": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.13.tgz", + "integrity": "sha512-Aoe8pT24sWzyoO0S2PTDyutGp9l7qYHyFtzYlC8hMLshyqV/minljBANT4f2hiS5OxnWvcKMiA5io+VaLMJ1oA==" + }, "is-whitespace-character": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", @@ -28126,12 +28366,6 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true - }, "known-css-properties": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz", @@ -28488,9 +28722,9 @@ } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -28821,9 +29055,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -29657,14 +29891,13 @@ } }, "postcss-loader": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.1.tgz", - "integrity": "sha512-uevGt8yy2gvruNvzy8jxgYSSnyqBcA7CnS6/57qoZnUMM51XgsTqxIpWZWdHyvIyo4ov0lCgnzIbhtWwVFI8lg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", + "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", "dev": true, "requires": { - "cosmiconfig": "^8.1.3", + "cosmiconfig": "^8.2.0", "jiti": "^1.18.2", - "klona": "^2.0.6", "semver": "^7.3.8" }, "dependencies": { @@ -29675,9 +29908,9 @@ "dev": true }, "cosmiconfig": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz", - "integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "dev": true, "requires": { "import-fresh": "^3.2.1", @@ -29724,9 +29957,9 @@ "dev": true }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -30779,6 +31012,11 @@ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -30822,6 +31060,11 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -31013,12 +31256,11 @@ } }, "sass-loader": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.0.tgz", - "integrity": "sha512-LeWNswSEujsZnwdn9AuA+Q5wZEAFlU+eORQsDKp35OtGAfFxYxpfk/Ylon+TGGkazSqxi2EHDTqdr3di8r7nCg==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", + "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", "dev": true, "requires": { - "klona": "^2.0.6", "neo-async": "^2.6.2" } }, @@ -31064,9 +31306,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "devOptional": true }, "send": { @@ -33876,6 +34118,14 @@ } } }, + "superjson": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.12.3.tgz", + "integrity": "sha512-0j+U70KUtP8+roVPbwfqkyQI7lBt7ETnuA7KXbTDX3mCKiD/4fXs2ldKSMdt0MCfpTwiMxo20yFU3vu6ewETpQ==", + "requires": { + "copy-anything": "^3.0.2" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -34263,9 +34513,9 @@ "dev": true }, "ts-loader": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz", - "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==", + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -34315,9 +34565,9 @@ "dev": true }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -34626,6 +34876,12 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -34731,9 +34987,9 @@ "dev": true }, "webpack": { - "version": "5.84.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.84.1.tgz", - "integrity": "sha512-ZP4qaZ7vVn/K8WN/p990SGATmrL1qg4heP/MrVneczYtpDGJWlrgZv55vxaV2ul885Kz+25MP2kSXkPe3LZfmg==", + "version": "5.88.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", @@ -34745,7 +35001,7 @@ "acorn-import-assertions": "^1.9.0", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.1", + "enhanced-resolve": "^5.15.0", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -34755,7 +35011,7 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", @@ -34794,9 +35050,9 @@ "dev": true }, "schema-utils": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", - "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "requires": { "@types/json-schema": "^7.0.8", @@ -34807,15 +35063,15 @@ } }, "webpack-cli": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz", - "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.0", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.4", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", "colorette": "^2.0.14", "commander": "^10.0.1", "cross-spawn": "^7.0.3", @@ -34863,9 +35119,9 @@ } }, "webpack-dev-server": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.0.tgz", - "integrity": "sha512-HmNB5QeSl1KpulTBQ8UT4FPrByYyaLxpJoQ0+s7EvUrMc16m0ZS1sgb1XGqzmgCPk0c9y+aaXxn11tbLzuM7NQ==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dev": true, "requires": { "@types/bonjour": "^3.5.9", @@ -34874,7 +35130,7 @@ "@types/serve-index": "^1.9.1", "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", + "@types/ws": "^8.5.5", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -35097,28 +35353,28 @@ "dev": true }, "workbox-background-sync": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz", - "integrity": "sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", "dev": true, "requires": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-broadcast-update": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.5.4.tgz", - "integrity": "sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", "dev": true, "requires": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-build": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.5.4.tgz", - "integrity": "sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", "dev": true, "requires": { "@apideck/better-ajv-errors": "^0.3.1", @@ -35143,21 +35399,21 @@ "strip-comments": "^2.0.1", "tempy": "^0.6.0", "upath": "^1.2.0", - "workbox-background-sync": "6.5.4", - "workbox-broadcast-update": "6.5.4", - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-google-analytics": "6.5.4", - "workbox-navigation-preload": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-range-requests": "6.5.4", - "workbox-recipes": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4", - "workbox-streams": "6.5.4", - "workbox-sw": "6.5.4", - "workbox-window": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" }, "dependencies": { "source-map": { @@ -35172,126 +35428,126 @@ } }, "workbox-cacheable-response": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.5.4.tgz", - "integrity": "sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", "dev": true, "requires": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-core": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.5.4.tgz", - "integrity": "sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==" + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" }, "workbox-expiration": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.5.4.tgz", - "integrity": "sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", "dev": true, "requires": { "idb": "^7.0.1", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-google-analytics": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.5.4.tgz", - "integrity": "sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", "dev": true, "requires": { - "workbox-background-sync": "6.5.4", - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "workbox-navigation-preload": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.5.4.tgz", - "integrity": "sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", "dev": true, "requires": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-precaching": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.5.4.tgz", - "integrity": "sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "workbox-range-requests": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.5.4.tgz", - "integrity": "sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", "dev": true, "requires": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-recipes": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.5.4.tgz", - "integrity": "sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", "dev": true, "requires": { - "workbox-cacheable-response": "6.5.4", - "workbox-core": "6.5.4", - "workbox-expiration": "6.5.4", - "workbox-precaching": "6.5.4", - "workbox-routing": "6.5.4", - "workbox-strategies": "6.5.4" + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" } }, "workbox-routing": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.5.4.tgz", - "integrity": "sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", "requires": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-strategies": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.5.4.tgz", - "integrity": "sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", "requires": { - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "workbox-streams": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.5.4.tgz", - "integrity": "sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", "dev": true, "requires": { - "workbox-core": "6.5.4", - "workbox-routing": "6.5.4" + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" } }, "workbox-sw": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.5.4.tgz", - "integrity": "sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==", "dev": true }, "workbox-webpack-plugin": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.5.4.tgz", - "integrity": "sha512-LmWm/zoaahe0EGmMTrSLUi+BjyR3cdGEfU3fS6PN1zKFYbqAKuQ+Oy/27e4VSXsyIwAw8+QDfk1XHNGtZu9nQg==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", "dev": true, "requires": { "fast-json-stable-stringify": "^2.1.0", "pretty-bytes": "^5.4.1", "upath": "^1.2.0", "webpack-sources": "^1.4.3", - "workbox-build": "6.5.4" + "workbox-build": "6.6.0" }, "dependencies": { "source-map": { @@ -35313,13 +35569,13 @@ } }, "workbox-window": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.5.4.tgz", - "integrity": "sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", "dev": true, "requires": { "@types/trusted-types": "^2.0.2", - "workbox-core": "6.5.4" + "workbox-core": "6.6.0" } }, "worker-loader": { diff --git a/package.json b/package.json index 0a3d6f7e62..b192e0df56 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "confusing-browser-globals": "1.0.11", "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", - "css-loader": "6.7.4", + "css-loader": "6.8.1", "cssnano": "6.0.1", "es-check": "7.1.1", "eslint": "8.41.0", @@ -34,20 +34,19 @@ "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", - "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-sonarjs": "0.19.0", "expose-loader": "4.1.0", "html-loader": "4.2.0", - "html-webpack-plugin": "5.5.1", + "html-webpack-plugin": "5.5.3", "mini-css-extract-plugin": "2.7.6", "postcss": "8.4.24", - "postcss-loader": "7.3.1", + "postcss-loader": "7.3.3", "postcss-preset-env": "8.4.1", "postcss-scss": "4.0.6", "sass": "1.62.1", - "sass-loader": "13.3.0", + "sass-loader": "13.3.2", "source-map-loader": "4.0.1", "style-loader": "3.3.3", "stylelint": "15.6.2", @@ -55,28 +54,32 @@ "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "6.0.3", "stylelint-scss": "5.0.0", - "ts-loader": "9.4.3", + "ts-loader": "9.4.4", "typescript": "5.0.4", - "webpack": "5.84.1", - "webpack-cli": "5.1.1", - "webpack-dev-server": "4.15.0", + "webpack": "5.88.1", + "webpack-cli": "5.1.4", + "webpack-dev-server": "4.15.1", "webpack-merge": "5.9.0", - "workbox-webpack-plugin": "6.5.4", + "workbox-webpack-plugin": "6.6.0", "worker-loader": "3.0.8" }, "dependencies": { "@emotion/react": "11.11.0", "@emotion/styled": "11.11.0", - "@fontsource/noto-sans": "4.5.11", - "@fontsource/noto-sans-hk": "4.5.12", - "@fontsource/noto-sans-jp": "4.5.12", - "@fontsource/noto-sans-kr": "4.5.12", - "@fontsource/noto-sans-sc": "4.5.12", - "@fontsource/noto-sans-tc": "4.5.12", + "@fontsource/noto-sans": "5.0.4", + "@fontsource/noto-sans-hk": "5.0.4", + "@fontsource/noto-sans-jp": "5.0.4", + "@fontsource/noto-sans-kr": "5.0.4", + "@fontsource/noto-sans-sc": "5.0.4", + "@fontsource/noto-sans-tc": "5.0.4", "@jellyfin/sdk": "unstable", "@loadable/component": "5.15.3", "@mui/icons-material": "5.11.16", "@mui/material": "5.13.3", + "@mui/x-data-grid": "6.6.0", + "@react-hook/resize-observer": "1.2.6", + "@tanstack/react-query": "4.29.12", + "@tanstack/react-query-devtools": "4.29.12", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", @@ -111,8 +114,8 @@ "swiper": "9.3.2", "webcomponents.js": "0.7.24", "whatwg-fetch": "3.6.2", - "workbox-core": "6.5.4", - "workbox-precaching": "6.5.4" + "workbox-core": "6.6.0", + "workbox-precaching": "6.6.0" }, "browserslist": [ "last 2 Firefox versions", diff --git a/src/RootApp.tsx b/src/RootApp.tsx index a44e25d565..62223f7236 100644 --- a/src/RootApp.tsx +++ b/src/RootApp.tsx @@ -1,6 +1,8 @@ import loadable from '@loadable/component'; import { History } from '@remix-run/router'; import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import StableApp from './apps/stable/App'; import { HistoryRouter } from './components/router/HistoryRouter'; @@ -9,21 +11,26 @@ import { WebConfigProvider } from './hooks/useWebConfig'; const ExperimentalApp = loadable(() => import('./apps/experimental/App')); +const queryClient = new QueryClient(); + const RootApp = ({ history }: { history: History }) => { const layoutMode = localStorage.getItem('layout'); return ( - - - - { - layoutMode === 'experimental' ? - : - - } - - - + + + + + { + layoutMode === 'experimental' ? + : + + } + + + + + ); }; diff --git a/src/apps/experimental/App.tsx b/src/apps/experimental/App.tsx index 0013ef0430..0c0778b74e 100644 --- a/src/apps/experimental/App.tsx +++ b/src/apps/experimental/App.tsx @@ -36,6 +36,9 @@ const ExperimentalApp = () => { {LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute)} + + {/* Redirects for old paths */} + } /> ); diff --git a/src/apps/experimental/components/AppToolbar/UserMenuButton.tsx b/src/apps/experimental/components/AppToolbar/UserMenuButton.tsx index 85db4d1f29..89f18669e9 100644 --- a/src/apps/experimental/components/AppToolbar/UserMenuButton.tsx +++ b/src/apps/experimental/components/AppToolbar/UserMenuButton.tsx @@ -1,6 +1,4 @@ -import Avatar from '@mui/material/Avatar'; import IconButton from '@mui/material/IconButton'; -import { useTheme } from '@mui/material/styles'; import Tooltip from '@mui/material/Tooltip'; import React, { useCallback, useState } from 'react'; @@ -8,10 +6,10 @@ import { useApi } from 'hooks/useApi'; import globalize from 'scripts/globalize'; import AppUserMenu, { ID } from './menus/AppUserMenu'; +import UserAvatar from 'components/UserAvatar'; const UserMenuButton = () => { - const theme = useTheme(); - const { api, user } = useApi(); + const { user } = useApi(); const [ userMenuAnchorEl, setUserMenuAnchorEl ] = useState(null); const isUserMenuOpen = Boolean(userMenuAnchorEl); @@ -37,18 +35,7 @@ const UserMenuButton = () => { color='inherit' sx={{ padding: 0 }} > - + diff --git a/src/apps/experimental/components/AppToolbar/index.tsx b/src/apps/experimental/components/AppToolbar/index.tsx index 8d87961511..0317ae92a3 100644 --- a/src/apps/experimental/components/AppToolbar/index.tsx +++ b/src/apps/experimental/components/AppToolbar/index.tsx @@ -1,3 +1,4 @@ +import ArrowBack from '@mui/icons-material/ArrowBack'; import MenuIcon from '@mui/icons-material/Menu'; import SearchIcon from '@mui/icons-material/Search'; import Box from '@mui/material/Box'; @@ -9,6 +10,7 @@ import React, { FC } from 'react'; import { Link, useLocation } from 'react-router-dom'; import appIcon from 'assets/img/icon-transparent.png'; +import { appRouter } from 'components/router/appRouter'; import { useApi } from 'hooks/useApi'; import globalize from 'scripts/globalize'; @@ -23,6 +25,13 @@ interface AppToolbarProps { onDrawerButtonClick: (event: React.MouseEvent) => void } +const onBackButtonClick = () => { + appRouter.back() + .catch(err => { + console.error('[AppToolbar] error calling appRouter.back', err); + }); +}; + const AppToolbar: FC = ({ isDrawerOpen, onDrawerButtonClick @@ -32,6 +41,7 @@ const AppToolbar: FC = ({ const location = useLocation(); const isDrawerAvailable = isDrawerPath(location.pathname); + const isBackButtonAvailable = appRouter.canGoBack(); return ( = ({ edge='start' color='inherit' aria-label={globalize.translate(isDrawerOpen ? 'MenuClose' : 'MenuOpen')} - sx={{ mr: 2 }} onClick={onDrawerButtonClick} > @@ -58,12 +67,28 @@ const AppToolbar: FC = ({ )} + {isBackButtonAvailable && ( + + + + + + )} + = ({ ])} + {/* ADMIN LINKS */} + {user?.Policy?.IsAdministrator && ([ + , + + + + + + + , + + + + + + + ])} + ; + +/** + * Link component to use in mui's data-grid action column due to a current bug with passing props to custom link components. + * @see https://github.com/mui/mui-x/issues/4654 + */ +const GridActionsCellLink = ({ to, ...props }: GridActionsCellLinkProps) => ( + + + +); + +export default GridActionsCellLink; diff --git a/src/apps/experimental/components/activityTable/LogLevelChip.tsx b/src/apps/experimental/components/activityTable/LogLevelChip.tsx new file mode 100644 index 0000000000..8053f770c1 --- /dev/null +++ b/src/apps/experimental/components/activityTable/LogLevelChip.tsx @@ -0,0 +1,34 @@ +import { LogLevel } from '@jellyfin/sdk/lib/generated-client/models/log-level'; +import Chip from '@mui/material/Chip'; +import React from 'react'; + +import globalize from 'scripts/globalize'; + +const LogLevelChip = ({ level }: { level: LogLevel }) => { + let color: 'info' | 'warning' | 'error' | undefined = undefined; + switch (level) { + case LogLevel.Information: + color = 'info'; + break; + case LogLevel.Warning: + color = 'warning'; + break; + case LogLevel.Error: + case LogLevel.Critical: + color = 'error'; + break; + } + + const levelText = globalize.translate(`LogLevel.${level}`); + + return ( + + ); +}; + +export default LogLevelChip; diff --git a/src/apps/experimental/components/activityTable/OverviewCell.tsx b/src/apps/experimental/components/activityTable/OverviewCell.tsx new file mode 100644 index 0000000000..69702de5f6 --- /dev/null +++ b/src/apps/experimental/components/activityTable/OverviewCell.tsx @@ -0,0 +1,64 @@ +import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry'; +import Info from '@mui/icons-material/Info'; +import Box from '@mui/material/Box'; +import ClickAwayListener from '@mui/material/ClickAwayListener'; +import IconButton from '@mui/material/IconButton'; +import Tooltip from '@mui/material/Tooltip'; +import React, { FC, useCallback, useState } from 'react'; + +const OverviewCell: FC = ({ Overview, ShortOverview }) => { + const displayValue = ShortOverview ?? Overview; + const [ open, setOpen ] = useState(false); + + const onTooltipClose = useCallback(() => { + setOpen(false); + }, []); + + const onTooltipOpen = useCallback(() => { + setOpen(true); + }, []); + + if (!displayValue) return null; + + return ( + + + {displayValue} + + {ShortOverview && Overview && ( + + + + + + + + )} + + ); +}; + +export default OverviewCell; diff --git a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx b/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx index ae1e7d4b85..cb3cbf33ce 100644 --- a/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx +++ b/src/apps/experimental/components/drawers/dashboard/DevicesDrawerSection.tsx @@ -40,7 +40,7 @@ const DevicesDrawerSection = () => { - + diff --git a/src/apps/experimental/components/library/GenresItemsContainer.tsx b/src/apps/experimental/components/library/GenresItemsContainer.tsx new file mode 100644 index 0000000000..41fba412d1 --- /dev/null +++ b/src/apps/experimental/components/library/GenresItemsContainer.tsx @@ -0,0 +1,51 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import React, { FC } from 'react'; +import { useGetGenres } from 'hooks/useFetchItems'; +import globalize from 'scripts/globalize'; +import Loading from 'components/loading/LoadingComponent'; +import GenresSectionContainer from './GenresSectionContainer'; +import { CollectionType } from 'types/collectionType'; + +interface GenresItemsContainerProps { + parentId?: string | null; + collectionType?: CollectionType; + itemType: BaseItemKind; +} + +const GenresItemsContainer: FC = ({ + parentId, + collectionType, + itemType +}) => { + const { isLoading, data: genresResult } = useGetGenres( + itemType, + parentId + ); + + if (isLoading) { + return ; + } + + return ( + <> + {!genresResult?.Items?.length ? ( +
+

{globalize.translate('MessageNothingHere')}

+

{globalize.translate('MessageNoGenresAvailable')}

+
+ ) : ( + genresResult?.Items?.map((genre) => ( + + )) + )} + + ); +}; + +export default GenresItemsContainer; diff --git a/src/apps/experimental/components/library/GenresSectionContainer.tsx b/src/apps/experimental/components/library/GenresSectionContainer.tsx new file mode 100644 index 0000000000..74f57782b1 --- /dev/null +++ b/src/apps/experimental/components/library/GenresSectionContainer.tsx @@ -0,0 +1,79 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import escapeHTML from 'escape-html'; +import React, { FC } from 'react'; + +import { useGetItems } from 'hooks/useFetchItems'; +import Loading from 'components/loading/LoadingComponent'; +import { appRouter } from 'components/router/appRouter'; +import SectionContainer from './SectionContainer'; +import { CollectionType } from 'types/collectionType'; + +interface GenresSectionContainerProps { + parentId?: string | null; + collectionType?: CollectionType; + itemType: BaseItemKind; + genre: BaseItemDto; +} + +const GenresSectionContainer: FC = ({ + parentId, + collectionType, + itemType, + genre +}) => { + const getParametersOptions = () => { + return { + sortBy: [ItemSortBy.Random], + sortOrder: [SortOrder.Ascending], + includeItemTypes: [itemType], + recursive: true, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + imageTypeLimit: 1, + enableImageTypes: [ImageType.Primary], + limit: 25, + genreIds: genre.Id ? [genre.Id] : undefined, + enableTotalRecordCount: false, + parentId: parentId ?? undefined + }; + }; + + const { isLoading, data: itemsResult } = useGetItems(getParametersOptions()); + + const getRouteUrl = (item: BaseItemDto) => { + return appRouter.getRouteUrl(item, { + context: collectionType, + parentId: parentId + }); + }; + + if (isLoading) { + return ; + } + + return ; +}; + +export default GenresSectionContainer; diff --git a/src/apps/experimental/components/library/RecommendationContainer.tsx b/src/apps/experimental/components/library/RecommendationContainer.tsx new file mode 100644 index 0000000000..4ec8102f57 --- /dev/null +++ b/src/apps/experimental/components/library/RecommendationContainer.tsx @@ -0,0 +1,66 @@ +import { RecommendationDto, RecommendationType } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC } from 'react'; + +import globalize from 'scripts/globalize'; +import escapeHTML from 'escape-html'; +import SectionContainer from './SectionContainer'; + +interface RecommendationContainerProps { + recommendation?: RecommendationDto; +} + +const RecommendationContainer: FC = ({ + recommendation = {} +}) => { + let title = ''; + + switch (recommendation.RecommendationType) { + case RecommendationType.SimilarToRecentlyPlayed: + title = globalize.translate( + 'RecommendationBecauseYouWatched', + recommendation.BaselineItemName + ); + break; + + case RecommendationType.SimilarToLikedItem: + title = globalize.translate( + 'RecommendationBecauseYouLike', + recommendation.BaselineItemName + ); + break; + + case RecommendationType.HasDirectorFromRecentlyPlayed: + case RecommendationType.HasLikedDirector: + title = globalize.translate( + 'RecommendationDirectedBy', + recommendation.BaselineItemName + ); + break; + + case RecommendationType.HasActorFromRecentlyPlayed: + case RecommendationType.HasLikedActor: + title = globalize.translate( + 'RecommendationStarring', + recommendation.BaselineItemName + ); + break; + } + + return ( + + ); +}; + +export default RecommendationContainer; diff --git a/src/apps/experimental/components/library/SectionContainer.tsx b/src/apps/experimental/components/library/SectionContainer.tsx new file mode 100644 index 0000000000..325c950a03 --- /dev/null +++ b/src/apps/experimental/components/library/SectionContainer.tsx @@ -0,0 +1,73 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useEffect, useRef } from 'react'; + +import cardBuilder from 'components/cardbuilder/cardBuilder'; +import ItemsContainerElement from 'elements/ItemsContainerElement'; +import Scroller from 'elements/emby-scroller/Scroller'; +import LinkButton from 'elements/emby-button/LinkButton'; +import imageLoader from 'components/images/imageLoader'; + +import { CardOptions } from 'types/cardOptions'; + +interface SectionContainerProps { + url?: string; + sectionTitle: string; + items: BaseItemDto[]; + cardOptions: CardOptions; +} + +const SectionContainer: FC = ({ + sectionTitle, + url, + items, + cardOptions +}) => { + const element = useRef(null); + + useEffect(() => { + const itemsContainer = element.current?.querySelector('.itemsContainer'); + cardBuilder.buildCards(items, { + itemsContainer: itemsContainer, + parentContainer: element.current, + + ...cardOptions + }); + + imageLoader.lazyChildren(itemsContainer); + }, [cardOptions, items]); + + return ( +
+
+ {url && items.length > 5 ? ( + +

+ {sectionTitle} +

+ +
+ ) : ( +

+ {sectionTitle} +

+ )} +
+ + + + +
+ ); +}; + +export default SectionContainer; diff --git a/src/apps/experimental/components/library/SuggestionsItemsContainer.tsx b/src/apps/experimental/components/library/SuggestionsItemsContainer.tsx new file mode 100644 index 0000000000..d985592a8e --- /dev/null +++ b/src/apps/experimental/components/library/SuggestionsItemsContainer.tsx @@ -0,0 +1,206 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import React, { FC } from 'react'; +import * as userSettings from 'scripts/settings/userSettings'; +import SuggestionsSectionContainer from './SuggestionsSectionContainer'; +import { Sections, SectionsView, SectionsViewType } from 'types/suggestionsSections'; + +const getSuggestionsSections = (): Sections[] => { + return [ + { + name: 'HeaderContinueWatching', + viewType: SectionsViewType.ResumeItems, + type: 'Movie', + view: SectionsView.ContinueWatchingMovies, + parametersOptions: { + includeItemTypes: [BaseItemKind.Movie] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + preferThumb: true, + shape: 'overflowBackdrop', + showYear: true + } + }, + { + name: 'HeaderLatestMovies', + viewType: SectionsViewType.LatestMedia, + type: 'Movie', + view: SectionsView.LatestMovies, + parametersOptions: { + includeItemTypes: [BaseItemKind.Movie] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowPortrait', + showYear: true + } + }, + { + name: 'HeaderContinueWatching', + viewType: SectionsViewType.ResumeItems, + type: 'Episode', + view: SectionsView.ContinueWatchingEpisode, + parametersOptions: { + includeItemTypes: [BaseItemKind.Episode] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowBackdrop', + preferThumb: true, + inheritThumb: + !userSettings.useEpisodeImagesInNextUpAndResume(undefined), + showYear: true + } + }, + { + name: 'HeaderLatestEpisodes', + viewType: SectionsViewType.LatestMedia, + type: 'Episode', + view: SectionsView.LatestEpisode, + parametersOptions: { + includeItemTypes: [BaseItemKind.Episode] + }, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowBackdrop', + preferThumb: true, + showSeriesYear: true, + showParentTitle: true, + overlayText: false, + showUnplayedIndicator: false, + showChildCountIndicator: true, + lazy: true, + lines: 2 + } + }, + { + name: 'NextUp', + viewType: SectionsViewType.NextUp, + type: 'nextup', + view: SectionsView.NextUp, + cardOptions: { + scalable: true, + overlayPlayButton: true, + showTitle: true, + centerText: true, + cardLayout: false, + shape: 'overflowBackdrop', + preferThumb: true, + inheritThumb: + !userSettings.useEpisodeImagesInNextUpAndResume(undefined), + showParentTitle: true, + overlayText: false + } + }, + { + name: 'HeaderLatestMusic', + viewType: SectionsViewType.LatestMedia, + type: 'Audio', + view: SectionsView.LatestMusic, + parametersOptions: { + includeItemTypes: [BaseItemKind.Audio] + }, + cardOptions: { + showUnplayedIndicator: false, + shape: 'overflowSquare', + showTitle: true, + showParentTitle: true, + lazy: true, + centerText: true, + overlayPlayButton: true, + cardLayout: false, + coverImage: true + } + }, + { + name: 'HeaderRecentlyPlayed', + type: 'Audio', + view: SectionsView.RecentlyPlayedMusic, + parametersOptions: { + sortBy: [ItemSortBy.DatePlayed], + sortOrder: [SortOrder.Descending], + includeItemTypes: [BaseItemKind.Audio] + }, + cardOptions: { + showUnplayedIndicator: false, + shape: 'overflowSquare', + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + cardLayout: false, + coverImage: true + } + }, + { + name: 'HeaderFrequentlyPlayed', + type: 'Audio', + view: SectionsView.FrequentlyPlayedMusic, + parametersOptions: { + sortBy: [ItemSortBy.PlayCount], + sortOrder: [SortOrder.Descending], + includeItemTypes: [BaseItemKind.Audio] + }, + cardOptions: { + showUnplayedIndicator: false, + shape: 'overflowSquare', + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + cardLayout: false, + coverImage: true + } + } + ]; +}; + +interface SuggestionsItemsContainerProps { + parentId?: string | null; + sectionsView: SectionsView[]; +} + +const SuggestionsItemsContainer: FC = ({ + parentId, + sectionsView +}) => { + const suggestionsSections = getSuggestionsSections(); + + return ( + <> + {suggestionsSections + .filter((section) => sectionsView.includes(section.view)) + .map((section) => ( + + ))} + + ); +}; + +export default SuggestionsItemsContainer; diff --git a/src/apps/experimental/components/library/SuggestionsSectionContainer.tsx b/src/apps/experimental/components/library/SuggestionsSectionContainer.tsx new file mode 100644 index 0000000000..4c52d712e1 --- /dev/null +++ b/src/apps/experimental/components/library/SuggestionsSectionContainer.tsx @@ -0,0 +1,49 @@ +import React, { FC } from 'react'; +import { useGetItemsBySectionType } from 'hooks/useFetchItems'; +import globalize from 'scripts/globalize'; + +import Loading from 'components/loading/LoadingComponent'; +import { appRouter } from 'components/router/appRouter'; +import SectionContainer from './SectionContainer'; + +import { Sections } from 'types/suggestionsSections'; + +interface SuggestionsSectionContainerProps { + parentId?: string | null; + section: Sections; +} + +const SuggestionsSectionContainer: FC = ({ + parentId, + section +}) => { + const getRouteUrl = () => { + return appRouter.getRouteUrl('list', { + serverId: window.ApiClient.serverId(), + itemTypes: section.type, + parentId: parentId + }); + }; + + const { isLoading, data: items } = useGetItemsBySectionType( + section, + parentId + ); + + if (isLoading) { + return ; + } + + return ( + + ); +}; + +export default SuggestionsSectionContainer; diff --git a/src/apps/experimental/components/library/filter/FilterButton.tsx b/src/apps/experimental/components/library/filter/FilterButton.tsx new file mode 100644 index 0000000000..ce9be77f0f --- /dev/null +++ b/src/apps/experimental/components/library/filter/FilterButton.tsx @@ -0,0 +1,457 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import React, { FC, useCallback } from 'react'; +import ArrowForwardIosSharpIcon from '@mui/icons-material/ArrowForwardIosSharp'; +import Box from '@mui/material/Box'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import Popover from '@mui/material/Popover'; +import MuiAccordion, { AccordionProps } from '@mui/material/Accordion'; +import MuiAccordionDetails from '@mui/material/AccordionDetails'; +import MuiAccordionSummary, { + AccordionSummaryProps +} from '@mui/material/AccordionSummary'; +import IconButton from '@mui/material/IconButton'; +import { styled } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; + +import { useGetQueryFiltersLegacy, useGetStudios } from 'hooks/useFetchItems'; +import globalize from 'scripts/globalize'; + +import FiltersFeatures from './FiltersFeatures'; +import FiltersGenres from './FiltersGenres'; +import FiltersOfficialRatings from './FiltersOfficialRatings'; +import FiltersEpisodesStatus from './FiltersEpisodesStatus'; +import FiltersSeriesStatus from './FiltersSeriesStatus'; +import FiltersStatus from './FiltersStatus'; +import FiltersStudios from './FiltersStudios'; +import FiltersTags from './FiltersTags'; +import FiltersVideoTypes from './FiltersVideoTypes'; +import FiltersYears from './FiltersYears'; + +import { LibraryViewSettings } from 'types/library'; +import { LibraryTab } from 'types/libraryTab'; + +const Accordion = styled((props: AccordionProps) => ( + +))(({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + '&:not(:last-child)': { + borderBottom: 0 + }, + '&:before': { + display: 'none' + } +})); + +const AccordionSummary = styled((props: AccordionSummaryProps) => ( + } + {...props} + /> +))(({ theme }) => ({ + backgroundColor: + theme.palette.mode === 'dark' ? + 'rgba(255, 255, 255, .05)' : + 'rgba(0, 0, 0, .03)', + '& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': { + transform: 'rotate(90deg)' + }, + '& .MuiAccordionSummary-content': { + marginLeft: theme.spacing(1) + } +})); + +const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ + padding: theme.spacing(2), + borderTop: '1px solid rgba(0, 0, 0, .125)' +})); + +interface FilterButtonProps { + parentId: string | null | undefined; + itemType: BaseItemKind; + viewType: LibraryTab; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch< + React.SetStateAction + >; +} + +const FilterButton: FC = ({ + parentId, + itemType, + viewType, + libraryViewSettings, + setLibraryViewSettings +}) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const [expanded, setExpanded] = React.useState(false); + const open = Boolean(anchorEl); + const id = open ? 'filter-popover' : undefined; + + const { data } = useGetQueryFiltersLegacy(parentId, itemType); + const { data: studios } = useGetStudios(parentId, itemType); + + const handleChange = + (panel: string) => + (event: React.SyntheticEvent, newExpanded: boolean) => { + setExpanded(newExpanded ? panel : false); + }; + + const handleClick = useCallback((event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }, []); + + const handleClose = useCallback(() => { + setAnchorEl(null); + }, []); + + const isFiltersLegacyEnabled = () => { + return ( + viewType === LibraryTab.Movies + || viewType === LibraryTab.Series + || viewType === LibraryTab.Albums + || viewType === LibraryTab.AlbumArtists + || viewType === LibraryTab.Artists + || viewType === LibraryTab.Songs + || viewType === LibraryTab.Episodes + ); + }; + + const isFiltersStudiosEnabled = () => { + return ( + viewType === LibraryTab.Movies + || viewType === LibraryTab.Series + ); + }; + + const isFiltersFeaturesEnabled = () => { + return ( + viewType === LibraryTab.Movies + || viewType === LibraryTab.Series + || viewType === LibraryTab.Episodes + ); + }; + + const isFiltersVideoTypesEnabled = () => { + return ( + viewType === LibraryTab.Movies + || viewType === LibraryTab.Episodes + ); + }; + + const isFiltersSeriesStatusEnabled = () => { + return viewType === LibraryTab.Series; + }; + + const isFiltersEpisodesStatusEnabled = () => { + return viewType === LibraryTab.Episodes; + }; + + return ( + + + + + + + + + {globalize.translate('Filters')} + + + + + + + {isFiltersSeriesStatusEnabled() && ( + <> + + + + {globalize.translate('HeaderSeriesStatus')} + + + + + + + + )} + {isFiltersEpisodesStatusEnabled() && ( + <> + + + + {globalize.translate('HeaderEpisodesStatus')} + + + + + + + + )} + {isFiltersFeaturesEnabled() && ( + <> + + + + {globalize.translate('Features')} + + + + + + + + )} + + {isFiltersVideoTypesEnabled() && ( + <> + + + + {globalize.translate('HeaderVideoType')} + + + + + + + + )} + + {isFiltersLegacyEnabled() && ( + <> + {data?.Genres && data?.Genres?.length > 0 && ( + + + + {globalize.translate('Genres')} + + + + + + + )} + + {data?.OfficialRatings + && data?.OfficialRatings?.length > 0 && ( + + + + {globalize.translate( + 'HeaderParentalRatings' + )} + + + + + + + )} + + {data?.Tags && data?.Tags.length > 0 && ( + + + + {globalize.translate('Tags')} + + + + + + + )} + + {data?.Years && data?.Years?.length > 0 && ( + + + + {globalize.translate('HeaderYears')} + + + + + + + )} + + )} + {isFiltersStudiosEnabled() && ( + <> + + + + {globalize.translate('Studios')} + + + + + + + + )} + + + ); +}; + +export default FilterButton; diff --git a/src/apps/experimental/components/library/filter/FiltersEpisodesStatus.tsx b/src/apps/experimental/components/library/filter/FiltersEpisodesStatus.tsx new file mode 100644 index 0000000000..8360b2b5a8 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersEpisodesStatus.tsx @@ -0,0 +1,75 @@ +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import globalize from 'scripts/globalize'; +import { LibraryViewSettings } from 'types/library'; + +const episodesStatusOptions = [ + { label: 'OptionSpecialEpisode', value: 'ParentIndexNumber' }, + { label: 'OptionMissingEpisode', value: 'IsMissing' }, + { label: 'OptionUnairedEpisode', value: 'IsUnaired' } +]; + +interface FiltersEpisodesStatusProps { + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersEpisodesStatus: FC = ({ + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersEpisodesStatusChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = String(event.target.value); + const existingValue = libraryViewSettings?.Filters?.EpisodesStatus; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: string) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, EpisodesStatus: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + EpisodesStatus: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.EpisodesStatus] + ); + + return ( + + {episodesStatusOptions.map((filter) => ( + + } + label={globalize.translate(filter.label)} + /> + ))} + + ); +}; + +export default FiltersEpisodesStatus; diff --git a/src/apps/experimental/components/library/filter/FiltersFeatures.tsx b/src/apps/experimental/components/library/filter/FiltersFeatures.tsx new file mode 100644 index 0000000000..5140350460 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersFeatures.tsx @@ -0,0 +1,81 @@ +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import globalize from 'scripts/globalize'; +import { LibraryViewSettings } from 'types/library'; + +const featuresOptions = [ + { label: 'Subtitles', value: 'HasSubtitles' }, + { label: 'Trailers', value: 'HasTrailer' }, + { label: 'Extras', value: 'HasSpecialFeature' }, + { label: 'ThemeSongs', value: 'HasThemeSong' }, + { label: 'ThemeVideos', value: 'HasThemeVideo' } +]; + +interface FiltersFeaturesProps { + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch< + React.SetStateAction + >; +} + +const FiltersFeatures: FC = ({ + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersFeaturesChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = String(event.target.value); + const existingValue = + libraryViewSettings?.Filters?.Features; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: string) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, Features: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + Features: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.Features] + ); + + return ( + + {featuresOptions + .map((filter) => ( + + } + label={globalize.translate(filter.label)} + /> + ))} + + ); +}; + +export default FiltersFeatures; diff --git a/src/apps/experimental/components/library/filter/FiltersGenres.tsx b/src/apps/experimental/components/library/filter/FiltersGenres.tsx new file mode 100644 index 0000000000..8d58aab63f --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersGenres.tsx @@ -0,0 +1,71 @@ +import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { LibraryViewSettings } from 'types/library'; + +interface FiltersGenresProps { + filters?: QueryFiltersLegacy; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersGenres: FC = ({ + filters, + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersGenresChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = String(event.target.value); + const existingValue = libraryViewSettings?.Filters?.Genres; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: string) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, Genres: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + Genres: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.Genres] + ); + + return ( + + {filters?.Genres?.map((filter) => ( + + } + label={filter} + /> + ))} + + ); +}; + +export default FiltersGenres; diff --git a/src/apps/experimental/components/library/filter/FiltersOfficialRatings.tsx b/src/apps/experimental/components/library/filter/FiltersOfficialRatings.tsx new file mode 100644 index 0000000000..1dc793f07b --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersOfficialRatings.tsx @@ -0,0 +1,71 @@ +import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { LibraryViewSettings } from 'types/library'; + +interface FiltersOfficialRatingsProps { + filters?: QueryFiltersLegacy; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersOfficialRatings: FC = ({ + filters, + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersOfficialRatingsChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = String(event.target.value); + const existingValue = libraryViewSettings?.Filters?.OfficialRatings; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: string) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, OfficialRatings: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + OfficialRatings: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.OfficialRatings] + ); + + return ( + + {filters?.OfficialRatings?.map((filter) => ( + + } + label={filter} + /> + ))} + + ); +}; + +export default FiltersOfficialRatings; diff --git a/src/apps/experimental/components/library/filter/FiltersSeriesStatus.tsx b/src/apps/experimental/components/library/filter/FiltersSeriesStatus.tsx new file mode 100644 index 0000000000..3420c8c7a0 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersSeriesStatus.tsx @@ -0,0 +1,74 @@ +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import globalize from 'scripts/globalize'; +import { LibraryViewSettings } from 'types/library'; +import { SeriesStatus } from '@jellyfin/sdk/lib/generated-client'; + +const statusFiltersOptions = [ + { label: 'Continuing', value: SeriesStatus.Continuing }, + { label: 'Ended', value: SeriesStatus.Ended }, + { label: 'Unreleased', value: SeriesStatus.Unreleased } +]; + +interface FiltersSeriesStatusProps { + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersSeriesStatus: FC = ({ + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersSeriesStatusChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = event.target.value as SeriesStatus; + const existingValue = libraryViewSettings?.Filters?.SeriesStatus; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: SeriesStatus) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, SeriesStatus: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + SeriesStatus: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.SeriesStatus] + ); + + return ( + + {statusFiltersOptions.map((filter) => ( + + } + label={globalize.translate(filter.label)} + /> + ))} + + ); +}; + +export default FiltersSeriesStatus; diff --git a/src/apps/experimental/components/library/filter/FiltersStatus.tsx b/src/apps/experimental/components/library/filter/FiltersStatus.tsx new file mode 100644 index 0000000000..72e3139199 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersStatus.tsx @@ -0,0 +1,97 @@ +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import globalize from 'scripts/globalize'; +import { LibraryViewSettings } from 'types/library'; +import { ItemFilter } from '@jellyfin/sdk/lib/generated-client'; +import { LibraryTab } from 'types/libraryTab'; + +const statusFiltersOptions = [ + { label: 'Played', value: ItemFilter.IsPlayed }, + { label: 'Unplayed', value: ItemFilter.IsUnplayed }, + { label: 'Favorite', value: ItemFilter.IsFavorite }, + { label: 'ContinueWatching', value: ItemFilter.IsResumable } +]; + +interface FiltersStatusProps { + viewType: LibraryTab; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersStatus: FC = ({ + viewType, + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersStatusChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = event.target.value as ItemFilter; + const existingValue = libraryViewSettings?.Filters?.Status; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: ItemFilter) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, Status: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + Status: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.Status] + ); + + const getVisibleFiltersStatus = () => { + const visibleFiltersStatus: ItemFilter[] = [ItemFilter.IsFavorite]; + + if ( + viewType !== LibraryTab.Albums + && viewType !== LibraryTab.Artists + && viewType !== LibraryTab.AlbumArtists + && viewType !== LibraryTab.Songs + ) { + visibleFiltersStatus.push(ItemFilter.IsUnplayed); + visibleFiltersStatus.push(ItemFilter.IsPlayed); + visibleFiltersStatus.push(ItemFilter.IsResumable); + } + + return visibleFiltersStatus; + }; + + return ( + + {statusFiltersOptions + .filter((filter) => getVisibleFiltersStatus().includes(filter.value)) + .map((filter) => ( + + } + label={globalize.translate(filter.label)} + /> + ))} + + ); +}; + +export default FiltersStatus; diff --git a/src/apps/experimental/components/library/filter/FiltersStudios.tsx b/src/apps/experimental/components/library/filter/FiltersStudios.tsx new file mode 100644 index 0000000000..0e8e2defc3 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersStudios.tsx @@ -0,0 +1,71 @@ +import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { LibraryViewSettings } from 'types/library'; + +interface FiltersStudiosProps { + filters?: BaseItemDtoQueryResult; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersStudios: FC = ({ + filters, + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersStudiosChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = String(event.target.value); + const existingValue = libraryViewSettings?.Filters?.StudioIds; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: string) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, StudioIds: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + StudioIds: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.StudioIds] + ); + + return ( + + {filters?.Items?.map((filter) => ( + + } + label={filter.Name} + /> + ))} + + ); +}; + +export default FiltersStudios; diff --git a/src/apps/experimental/components/library/filter/FiltersTags.tsx b/src/apps/experimental/components/library/filter/FiltersTags.tsx new file mode 100644 index 0000000000..f626eb03ae --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersTags.tsx @@ -0,0 +1,71 @@ +import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { LibraryViewSettings } from 'types/library'; + +interface FiltersTagsProps { + filters?: QueryFiltersLegacy; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersTags: FC = ({ + filters, + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersTagsChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = String(event.target.value); + const existingValue = libraryViewSettings?.Filters?.Tags; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: string) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, Tags: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + Tags: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.Tags] + ); + + return ( + + {filters?.Tags?.map((filter) => ( + + } + label={filter} + /> + ))} + + ); +}; + +export default FiltersTags; diff --git a/src/apps/experimental/components/library/filter/FiltersVideoTypes.tsx b/src/apps/experimental/components/library/filter/FiltersVideoTypes.tsx new file mode 100644 index 0000000000..3aa99cb074 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersVideoTypes.tsx @@ -0,0 +1,131 @@ +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { LibraryViewSettings } from 'types/library'; +import { VideoType } from '@jellyfin/sdk/lib/generated-client'; +import globalize from 'scripts/globalize'; + +const videoTypesOptions = [ + { label: 'DVD', value: VideoType.Dvd }, + { label: 'Blu-ray', value: VideoType.BluRay }, + { label: 'ISO', value: VideoType.Iso } +]; + +interface FiltersVideoTypesProps { + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersVideoTypes: FC = ({ + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersVideoTypesChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = event.target.value as VideoType; + const existingValue = libraryViewSettings?.Filters?.VideoTypes; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: VideoType) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, VideoTypes: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + VideoTypes: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.VideoTypes] + ); + + const handleChange = useCallback( + (event: React.ChangeEvent) => { + const name = event.target.name; + + setLibraryViewSettings((prevState) => ({ + ...prevState, + [name]: event.target.checked + })); + }, + [setLibraryViewSettings] + ); + + return ( + + + } + label={globalize.translate('SD')} + /> + + } + label={globalize.translate('HD')} + /> + + } + label={globalize.translate('4K')} + /> + + } + label={globalize.translate('3D')} + /> + {videoTypesOptions + .map((filter) => ( + + } + label={filter.label} + /> + ))} + + ); +}; + +export default FiltersVideoTypes; diff --git a/src/apps/experimental/components/library/filter/FiltersYears.tsx b/src/apps/experimental/components/library/filter/FiltersYears.tsx new file mode 100644 index 0000000000..aeb8673e83 --- /dev/null +++ b/src/apps/experimental/components/library/filter/FiltersYears.tsx @@ -0,0 +1,71 @@ +import type { QueryFiltersLegacy } from '@jellyfin/sdk/lib/generated-client'; +import React, { FC, useCallback } from 'react'; +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Checkbox from '@mui/material/Checkbox'; +import { LibraryViewSettings } from 'types/library'; + +interface FiltersYearsProps { + filters?: QueryFiltersLegacy; + libraryViewSettings: LibraryViewSettings; + setLibraryViewSettings: React.Dispatch>; +} + +const FiltersYears: FC = ({ + filters, + libraryViewSettings, + setLibraryViewSettings +}) => { + const onFiltersYearsChange = useCallback( + (event: React.ChangeEvent) => { + event.preventDefault(); + const value = Number(event.target.value); + const existingValue = libraryViewSettings?.Filters?.Years; + + if (existingValue?.includes(value)) { + const newValue = existingValue?.filter( + (prevState: number) => prevState !== value + ); + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { ...prevState.Filters, Years: newValue } + })); + } else { + setLibraryViewSettings((prevState) => ({ + ...prevState, + StartIndex: 0, + Filters: { + ...prevState.Filters, + Years: [...(existingValue ?? []), value] + } + })); + } + }, + [setLibraryViewSettings, libraryViewSettings?.Filters?.Years] + ); + + return ( + + {filters?.Years?.map((filter) => ( + + } + label={filter} + /> + ))} + + ); +}; + +export default FiltersYears; diff --git a/src/apps/experimental/routes/asyncRoutes/admin.ts b/src/apps/experimental/routes/asyncRoutes/admin.ts index 72bcc6f32b..7e8c0eca16 100644 --- a/src/apps/experimental/routes/asyncRoutes/admin.ts +++ b/src/apps/experimental/routes/asyncRoutes/admin.ts @@ -1,6 +1,7 @@ -import { AsyncRoute } from '../../../../components/router/AsyncRoute'; +import { AsyncRoute, AsyncRouteType } from 'components/router/AsyncRoute'; export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ + { path: 'dashboard/activity', page: 'dashboard/activity', type: AsyncRouteType.Experimental }, { path: 'notificationsettings.html', page: 'dashboard/notifications' }, { path: 'usernew.html', page: 'user/usernew' }, { path: 'userprofiles.html', page: 'user/userprofiles' }, diff --git a/src/apps/experimental/routes/dashboard/activity.tsx b/src/apps/experimental/routes/dashboard/activity.tsx new file mode 100644 index 0000000000..f007e104d3 --- /dev/null +++ b/src/apps/experimental/routes/dashboard/activity.tsx @@ -0,0 +1,273 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { getActivityLogApi } from '@jellyfin/sdk/lib/utils/api/activity-log-api'; +import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api'; +import type { ActivityLogEntry } from '@jellyfin/sdk/lib/generated-client/models/activity-log-entry'; +import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; +import PermMedia from '@mui/icons-material/PermMedia'; +import Box from '@mui/material/Box'; +import IconButton from '@mui/material/IconButton'; +import ToggleButton from '@mui/material/ToggleButton'; +import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'; +import Typography from '@mui/material/Typography'; +import { DataGrid, type GridColDef } from '@mui/x-data-grid'; +import { Link, useSearchParams } from 'react-router-dom'; + +import Page from 'components/Page'; +import UserAvatar from 'components/UserAvatar'; +import { useApi } from 'hooks/useApi'; +import { parseISO8601Date, toLocaleDateString, toLocaleTimeString } from 'scripts/datetime'; +import globalize from 'scripts/globalize'; +import { toBoolean } from 'utils/string'; + +import LogLevelChip from '../../components/activityTable/LogLevelChip'; +import OverviewCell from '../../components/activityTable/OverviewCell'; +import GridActionsCellLink from '../../components/GridActionsCellLink'; + +const DEFAULT_PAGE_SIZE = 25; +const VIEW_PARAM = 'useractivity'; + +const enum ActivityView { + All, + User, + System +} + +const getActivityView = (param: string | null) => { + if (param === null) return ActivityView.All; + if (toBoolean(param)) return ActivityView.User; + return ActivityView.System; +}; + +const getRowId = (row: ActivityLogEntry) => row.Id ?? -1; + +const Activity = () => { + const { api } = useApi(); + const [ searchParams, setSearchParams ] = useSearchParams(); + + const [ activityView, setActivityView ] = useState( + getActivityView(searchParams.get(VIEW_PARAM))); + const [ isLoading, setIsLoading ] = useState(true); + const [ paginationModel, setPaginationModel ] = useState({ + page: 0, + pageSize: DEFAULT_PAGE_SIZE + }); + const [ rowCount, setRowCount ] = useState(0); + const [ rows, setRows ] = useState([]); + const [ users, setUsers ] = useState>({}); + + const userColDef: GridColDef[] = activityView !== ActivityView.System ? [ + { + field: 'User', + headerName: globalize.translate('LabelUser'), + width: 60, + valueGetter: ({ row }) => users[row.UserId]?.Name, + renderCell: ({ row }) => ( + + + + ) + } + ] : []; + + const columns: GridColDef[] = [ + { + field: 'Date', + headerName: globalize.translate('LabelDate'), + width: 90, + type: 'date', + valueGetter: ({ value }) => parseISO8601Date(value), + valueFormatter: ({ value }) => toLocaleDateString(value) + }, + { + field: 'Time', + headerName: globalize.translate('LabelTime'), + width: 100, + type: 'dateTime', + valueGetter: ({ row }) => parseISO8601Date(row.Date), + valueFormatter: ({ value }) => toLocaleTimeString(value) + }, + { + field: 'Severity', + headerName: globalize.translate('LabelLevel'), + width: 110, + renderCell: ({ value }) => ( + value ? ( + + ) : undefined + ) + }, + ...userColDef, + { + field: 'Name', + headerName: globalize.translate('LabelName'), + width: 200 + }, + { + field: 'Overview', + headerName: globalize.translate('LabelOverview'), + width: 200, + valueGetter: ({ row }) => row.ShortOverview ?? row.Overview, + renderCell: ({ row }) => ( + + ) + }, + { + field: 'Type', + headerName: globalize.translate('LabelType'), + width: 120 + }, + { + field: 'actions', + type: 'actions', + getActions: ({ row }) => { + const actions = []; + + if (row.ItemId) { + actions.push( + } + label={globalize.translate('LabelMediaDetails')} + title={globalize.translate('LabelMediaDetails')} + to={`/details?id=${row.ItemId}`} + /> + ); + } + + return actions; + } + } + ]; + + const onViewChange = useCallback((_e, newView: ActivityView | null) => { + if (newView !== null) { + setActivityView(newView); + } + }, []); + + useEffect(() => { + if (api) { + const fetchUsers = async () => { + const { data } = await getUserApi(api).getUsers(); + const usersById: Record = {}; + data.forEach(user => { + if (user.Id) { + usersById[user.Id] = user; + } + }); + + setUsers(usersById); + }; + + fetchUsers() + .catch(err => { + console.error('[activity] failed to fetch users', err); + }); + } + }, [ api ]); + + useEffect(() => { + if (api) { + const fetchActivity = async () => { + const params: { + startIndex: number, + limit: number, + hasUserId?: boolean + } = { + startIndex: paginationModel.page * paginationModel.pageSize, + limit: paginationModel.pageSize + }; + if (activityView !== ActivityView.All) { + params.hasUserId = activityView === ActivityView.User; + } + + const { data } = await getActivityLogApi(api) + .getLogEntries(params); + + setRowCount(data.TotalRecordCount ?? 0); + setRows(data.Items ?? []); + setIsLoading(false); + }; + + setIsLoading(true); + fetchActivity() + .catch(err => { + console.error('[activity] failed to fetch activity log entries', err); + }); + } + }, [ activityView, api, paginationModel ]); + + useEffect(() => { + const currentViewParam = getActivityView(searchParams.get(VIEW_PARAM)); + if (currentViewParam !== activityView) { + if (activityView === ActivityView.All) { + searchParams.delete(VIEW_PARAM); + } else { + searchParams.set(VIEW_PARAM, `${activityView === ActivityView.User}`); + } + setSearchParams(searchParams); + } + }, [ activityView, searchParams, setSearchParams ]); + + return ( + +
+ + + + {globalize.translate('HeaderActivity')} + + + + + {globalize.translate('All')} + + + {globalize.translate('LabelUser')} + + + {globalize.translate('LabelSystem')} + + + + +
+
+ ); +}; + +export default Activity; diff --git a/src/apps/experimental/routes/home.tsx b/src/apps/experimental/routes/home.tsx index bf52b82137..3ab9c8bed7 100644 --- a/src/apps/experimental/routes/home.tsx +++ b/src/apps/experimental/routes/home.tsx @@ -65,12 +65,12 @@ const Home: FunctionComponent = () => { depends = 'favorites'; } - return import(/* webpackChunkName: "[request]" */ `../../../controllers/${depends}`).then(({ default: controllerFactory }) => { + return import(/* webpackChunkName: "[request]" */ `../../../controllers/${depends}`).then(({ default: ControllerFactory }) => { let controller = tabControllers[index]; if (!controller) { const tabContent = element.current?.querySelector(".tabContent[data-index='" + index + "']"); - controller = new controllerFactory(tabContent, null); + controller = new ControllerFactory(tabContent, null); tabControllers[index] = controller; } diff --git a/src/apps/experimental/routes/legacyRoutes/admin.ts b/src/apps/experimental/routes/legacyRoutes/admin.ts index e2037ddb11..b53f86ce87 100644 --- a/src/apps/experimental/routes/legacyRoutes/admin.ts +++ b/src/apps/experimental/routes/legacyRoutes/admin.ts @@ -163,12 +163,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'dashboard/scheduledtasks/scheduledtasks', view: 'dashboard/scheduledtasks/scheduledtasks.html' } - }, { - path: 'serveractivity.html', - pageProps: { - controller: 'dashboard/serveractivity', - view: 'dashboard/serveractivity.html' - } }, { path: 'apikeys.html', pageProps: { diff --git a/src/apps/experimental/routes/movies/CollectionsView.tsx b/src/apps/experimental/routes/movies/CollectionsView.tsx index b58cc957e5..ef574b916e 100644 --- a/src/apps/experimental/routes/movies/CollectionsView.tsx +++ b/src/apps/experimental/routes/movies/CollectionsView.tsx @@ -1,9 +1,9 @@ import React, { FC, useCallback } from 'react'; -import ViewItemsContainer from '../../../../components/common/ViewItemsContainer'; -import { LibraryViewProps } from '../../../../types/interface'; +import ViewItemsContainer from 'components/common/ViewItemsContainer'; +import { LibraryViewProps } from 'types/library'; -const CollectionsView: FC = ({ topParentId }) => { +const CollectionsView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'collections'; }, []); @@ -18,7 +18,7 @@ const CollectionsView: FC = ({ topParentId }) => { return ( = ({ topParentId }) => { +const FavoritesView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'favorites'; }, []); @@ -18,7 +18,7 @@ const FavoritesView: FC = ({ topParentId }) => { return ( = ({ topParentId }) => { - const [ itemsResult, setItemsResult ] = useState({}); - - const reloadItems = useCallback(() => { - loading.show(); - window.ApiClient.getGenres( - window.ApiClient.getCurrentUserId(), - { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Movie', - Recursive: true, - EnableTotalRecordCount: false, - ParentId: topParentId - } - ).then((result) => { - setItemsResult(result); - loading.hide(); - }).catch(err => { - console.error('[GenresView] failed to fetch genres', err); - }); - }, [topParentId]); - - useEffect(() => { - reloadItems(); - }, [reloadItems]); +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import React, { FC } from 'react'; +import GenresItemsContainer from '../../components/library/GenresItemsContainer'; +import { LibraryViewProps } from 'types/library'; +import { CollectionType } from 'types/collectionType'; +const GenresView: FC = ({ parentId }) => { return ( ); }; diff --git a/src/apps/experimental/routes/movies/MoviesView.tsx b/src/apps/experimental/routes/movies/MoviesView.tsx index 510ed9e2b2..8796c9a711 100644 --- a/src/apps/experimental/routes/movies/MoviesView.tsx +++ b/src/apps/experimental/routes/movies/MoviesView.tsx @@ -1,9 +1,9 @@ import React, { FC, useCallback } from 'react'; -import ViewItemsContainer from '../../../../components/common/ViewItemsContainer'; -import { LibraryViewProps } from '../../../../types/interface'; +import ViewItemsContainer from 'components/common/ViewItemsContainer'; +import { LibraryViewProps } from 'types/library'; -const MoviesView: FC = ({ topParentId }) => { +const MoviesView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'movies'; }, []); @@ -18,7 +18,7 @@ const MoviesView: FC = ({ topParentId }) => { return ( = ({ topParentId }) => { - const [ latestItems, setLatestItems ] = useState([]); - const [ resumeResult, setResumeResult ] = useState({}); - const [ recommendations, setRecommendations ] = useState([]); - const element = useRef(null); +const SuggestionsView: FC = ({ parentId }) => { + const { + isLoading, + data: movieRecommendationsItems + } = useGetMovieRecommendations(parentId); - const enableScrollX = useCallback(() => { - return !layoutManager.desktop; - }, []); - - const getPortraitShape = useCallback(() => { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - }, [enableScrollX]); - - const getThumbShape = useCallback(() => { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - }, [enableScrollX]); - - const autoFocus = useCallback((page) => { - import('../../../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to load data', err); - }); - }, []); - - const loadResume = useCallback((page, userId, parentId) => { - loading.show(); - const screenWidth = dom.getWindowSize().innerWidth; - const options = { - SortBy: 'DatePlayed', - SortOrder: 'Descending', - IncludeItemTypes: 'Movie', - Filters: 'IsResumable', - Limit: screenWidth >= 1600 ? 5 : 3, - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - CollapseBoxSetItems: false, - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - window.ApiClient.getItems(userId, options).then(result => { - setResumeResult(result); - - loading.hide(); - autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to fetch items', err); - }); - }, [autoFocus]); - - const loadLatest = useCallback((page: HTMLDivElement, userId: string, parentId: string | null) => { - const options = { - IncludeItemTypes: 'Movie', - Limit: 18, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - window.ApiClient.getJSON(window.ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(items => { - setLatestItems(items); - - autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to fetch latest items', err); - }); - }, [autoFocus]); - - const loadSuggestions = useCallback((page, userId) => { - const screenWidth = dom.getWindowSize().innerWidth; - let itemLimit = 5; - if (screenWidth >= 1600) { - itemLimit = 8; - } else if (screenWidth >= 1200) { - itemLimit = 6; - } - const url = window.ApiClient.getUrl('Movies/Recommendations', { - userId: userId, - categoryLimit: 6, - ItemLimit: itemLimit, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' - }); - window.ApiClient.getJSON(url).then(result => { - setRecommendations(result); - - autoFocus(page); - }).catch(err => { - console.error('[SuggestionsView] failed to fetch recommendations', err); - }); - }, [autoFocus]); - - const loadSuggestionsTab = useCallback((view) => { - const parentId = topParentId; - const userId = window.ApiClient.getCurrentUserId(); - loadResume(view, userId, parentId); - loadLatest(view, userId, parentId); - loadSuggestions(view, userId); - }, [loadLatest, loadResume, loadSuggestions, topParentId]); - - useEffect(() => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - - loadSuggestionsTab(page); - }, [loadSuggestionsTab]); + if (isLoading) { + return ; + } return ( -
- + - - - {!recommendations.length ?
-

{globalize.translate('MessageNothingHere')}

-

{globalize.translate('MessageNoMovieSuggestionsAvailable')}

-
: recommendations.map(recommendation => { - return ; - })} -
+ {!movieRecommendationsItems?.length ? ( +
+

{globalize.translate('MessageNothingHere')}

+

+ {globalize.translate( + 'MessageNoMovieSuggestionsAvailable' + )} +

+
+ ) : ( + movieRecommendationsItems.map((recommendation, index) => { + return ( + + ); + }) + )} + ); }; diff --git a/src/apps/experimental/routes/movies/TrailersView.tsx b/src/apps/experimental/routes/movies/TrailersView.tsx index 55f6189cfc..ff0ff0e73e 100644 --- a/src/apps/experimental/routes/movies/TrailersView.tsx +++ b/src/apps/experimental/routes/movies/TrailersView.tsx @@ -1,10 +1,10 @@ import React, { FC, useCallback } from 'react'; -import ViewItemsContainer from '../../../../components/common/ViewItemsContainer'; -import { LibraryViewProps } from '../../../../types/interface'; +import ViewItemsContainer from 'components/common/ViewItemsContainer'; +import { LibraryViewProps } from 'types/library'; -const TrailersView: FC = ({ topParentId }) => { +const TrailersView: FC = ({ parentId }) => { const getBasekey = useCallback(() => { return 'trailers'; }, []); @@ -19,7 +19,7 @@ const TrailersView: FC = ({ topParentId }) => { return ( { const location = useLocation(); const [ searchParams ] = useSearchParams(); + const searchParamsParentId = searchParams.get('topParentId'); const searchParamsTab = searchParams.get('tab'); const currentTabIndex = searchParamsTab !== null ? parseInt(searchParamsTab, 10) : - getDefaultTabIndex(location.pathname, searchParams.get('topParentId')); - const element = useRef(null); + getDefaultTabIndex(location.pathname, searchParamsParentId); const getTabComponent = (index: number) => { if (index == null) { @@ -32,72 +30,41 @@ const Movies: FC = () => { let component; switch (index) { - case 0: - component = ; - break; - case 1: - component = ; + component = ; break; case 2: - component = ; + component = ; break; case 3: - component = ; + component = ; break; case 4: - component = ; + component = ; break; case 5: - component = ; + component = ; break; + default: + component = ; } return component; }; - useEffect(() => { - const page = element.current; - - if (!page) { - console.error('Unexpected null reference'); - return; - } - - if (!page.getAttribute('data-title')) { - const parentId = searchParams.get('topParentId'); - - if (parentId) { - window.ApiClient.getItem(window.ApiClient.getCurrentUserId(), parentId).then((item) => { - page.setAttribute('data-title', item.Name as string); - libraryMenu.setTitle(item.Name); - }).catch(err => { - console.error('[movies] failed to fetch library', err); - page.setAttribute('data-title', globalize.translate('Movies')); - libraryMenu.setTitle(globalize.translate('Movies')); - }); - } else { - page.setAttribute('data-title', globalize.translate('Movies')); - libraryMenu.setTitle(globalize.translate('Movies')); - } - } - }, [ searchParams ]); - return ( -
- - {getTabComponent(currentTabIndex)} + + {getTabComponent(currentTabIndex)} - -
+ ); }; diff --git a/src/apps/experimental/theme.ts b/src/apps/experimental/theme.ts index 7def6bd4b7..d3f84366be 100644 --- a/src/apps/experimental/theme.ts +++ b/src/apps/experimental/theme.ts @@ -21,6 +21,15 @@ const theme = createTheme({ fontFamily: '"Noto Sans", sans-serif', button: { textTransform: 'none' + }, + h1: { + fontSize: '1.8rem' + }, + h2: { + fontSize: '1.5rem' + }, + h3: { + fontSize: '1.17rem' } }, components: { diff --git a/src/apps/stable/App.tsx b/src/apps/stable/App.tsx index bbaec1b922..73c73f5881 100644 --- a/src/apps/stable/App.tsx +++ b/src/apps/stable/App.tsx @@ -51,6 +51,9 @@ const StableApp = () => ( {/* Suppress warnings for unhandled routes */} + + {/* Redirects for old paths */} + } /> ); diff --git a/src/apps/stable/routes/legacyRoutes/admin.ts b/src/apps/stable/routes/legacyRoutes/admin.ts index e2037ddb11..92aaef8041 100644 --- a/src/apps/stable/routes/legacyRoutes/admin.ts +++ b/src/apps/stable/routes/legacyRoutes/admin.ts @@ -164,7 +164,7 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ view: 'dashboard/scheduledtasks/scheduledtasks.html' } }, { - path: 'serveractivity.html', + path: 'dashboard/activity', pageProps: { controller: 'dashboard/serveractivity', view: 'dashboard/serveractivity.html' diff --git a/src/apps/stable/routes/user/userprofiles.tsx b/src/apps/stable/routes/user/userprofiles.tsx index b386533f4b..dc8a6e86fd 100644 --- a/src/apps/stable/routes/user/userprofiles.tsx +++ b/src/apps/stable/routes/user/userprofiles.tsx @@ -48,7 +48,7 @@ const UserProfiles: FunctionComponent = () => { const showUserMenu = (elem: HTMLElement) => { const card = dom.parentWithClass(elem, 'card'); - const userId = card.getAttribute('data-userid'); + const userId = card?.getAttribute('data-userid'); if (!userId) { console.error('Unexpected null user id'); diff --git a/src/components/UserAvatar.tsx b/src/components/UserAvatar.tsx new file mode 100644 index 0000000000..3a2790e835 --- /dev/null +++ b/src/components/UserAvatar.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import type { UserDto } from '@jellyfin/sdk/lib/generated-client/models/user-dto'; +import Avatar from '@mui/material/Avatar'; +import { useTheme } from '@mui/material/styles'; + +import { useApi } from 'hooks/useApi'; + +interface UserAvatarProps { + user?: UserDto +} + +const UserAvatar: FC = ({ user }) => { + const { api } = useApi(); + const theme = useTheme(); + + return user ? ( + + ) : null; +}; + +export default UserAvatar; diff --git a/src/components/appFooter/appFooter.js b/src/components/appFooter/appFooter.js index 3aaab8fe54..38253d2210 100644 --- a/src/components/appFooter/appFooter.js +++ b/src/components/appFooter/appFooter.js @@ -9,28 +9,26 @@ function render() { return elem; } -class appFooter { +class AppFooter { constructor() { - const self = this; + this.element = render(); - self.element = render(); - self.add = function (elem) { - self.element.appendChild(elem); + this.add = function (elem) { + this.element.appendChild(elem); }; - self.insert = function (elem) { + this.insert = function (elem) { if (typeof elem === 'string') { - self.element.insertAdjacentHTML('afterbegin', elem); + this.element.insertAdjacentHTML('afterbegin', elem); } else { - self.element.insertBefore(elem, self.element.firstChild); + this.element.insertBefore(elem, this.element.firstChild); } }; } - destroy() { - const self = this; - self.element = null; + destroy() { + this.element = null; } } -export default new appFooter(); +export default new AppFooter(); diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index 4cd481827f..46655777ff 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -37,7 +37,7 @@ class Backdrop { parent.appendChild(backdropImage); if (!enableAnimation()) { - if (existingBackdropImage && existingBackdropImage.parentNode) { + if (existingBackdropImage?.parentNode) { existingBackdropImage.parentNode.removeChild(existingBackdropImage); } internalBackdrop(true); @@ -51,7 +51,7 @@ class Backdrop { if (backdropImage === self.currentAnimatingElement) { self.currentAnimatingElement = null; } - if (existingBackdropImage && existingBackdropImage.parentNode) { + if (existingBackdropImage?.parentNode) { existingBackdropImage.parentNode.removeChild(existingBackdropImage); } }; diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 5ad759002d..d5b19c0210 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -546,7 +546,7 @@ function getCardImageUrl(item, apiClient, options, shape) { imgType = 'Backdrop'; imgTag = item.ParentBackdropImageTags[0]; itemId = item.ParentBackdropItemId; - } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { + } else if (item.ImageTags?.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { imgType = 'Primary'; imgTag = item.ImageTags.Primary; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; @@ -591,10 +591,10 @@ function getCardImageUrl(item, apiClient, options, shape) { } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { imgType = 'Thumb'; imgTag = item.ImageTags.Thumb; - } else if (item.BackdropImageTags && item.BackdropImageTags.length) { + } else if (item.BackdropImageTags?.length) { imgType = 'Backdrop'; imgTag = item.BackdropImageTags[0]; - } else if (item.ImageTags && item.ImageTags.Thumb) { + } else if (item.ImageTags?.Thumb) { imgType = 'Thumb'; imgTag = item.ImageTags.Thumb; } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { @@ -605,7 +605,7 @@ function getCardImageUrl(item, apiClient, options, shape) { imgType = 'Thumb'; imgTag = item.ParentThumbImageTag; itemId = item.ParentThumbItemId; - } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { + } else if (item.ParentBackdropImageTags?.length && options.inheritThumb !== false) { imgType = 'Backdrop'; imgTag = item.ParentBackdropImageTags[0]; itemId = item.ParentBackdropItemId; @@ -634,7 +634,7 @@ function getCardImageUrl(item, apiClient, options, shape) { return { imgUrl: imgUrl, - blurhash: (blurHashes[imgType] || {})[imgTag], + blurhash: blurHashes[imgType]?.[imgTag], forceName: forceName, coverImage: coverImage }; @@ -1422,7 +1422,7 @@ function buildCard(index, item, apiClient, options) { className += ' card-withuserdata'; } - const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"') : ''; + const positionTicksData = item.UserData?.PlaybackPositionTicks ? (' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"') : ''; const collectionIdData = options.collectionId ? (' data-collectionid="' + options.collectionId + '"') : ''; const playlistIdData = options.playlistId ? (' data-playlistid="' + options.playlistId + '"') : ''; const mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : ''; diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index 4a359cd9f4..21535adc89 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -26,7 +26,7 @@ function buildChapterCardsHtml(item, chapters, options) { } } - const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; + const mediaStreams = (item.MediaSources || [])[0]?.MediaStreams || []; const videoStream = mediaStreams.filter(({ Type }) => { return Type === 'Video'; })[0] || {}; diff --git a/src/components/channelMapper/channelMapper.js b/src/components/channelMapper/channelMapper.js index 819fbfc3e6..a513d4ac03 100644 --- a/src/components/channelMapper/channelMapper.js +++ b/src/components/channelMapper/channelMapper.js @@ -12,7 +12,7 @@ import 'material-design-icons-iconfont'; import '../formdialog.scss'; import ServerConnections from '../ServerConnections'; -export default class channelMapper { +export default class ChannelMapper { constructor(options) { function mapChannel(button, channelId, providerChannelId) { loading.show(); diff --git a/src/components/common/GenresItemsContainer.tsx b/src/components/common/GenresItemsContainer.tsx deleted file mode 100644 index 09623e7e57..0000000000 --- a/src/components/common/GenresItemsContainer.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import '../../elements/emby-button/emby-button'; -import '../../elements/emby-itemscontainer/emby-itemscontainer'; - -import type { BaseItemDtoQueryResult } from '@jellyfin/sdk/lib/generated-client'; -import escapeHTML from 'escape-html'; -import React, { FC, useCallback, useEffect, useRef } from 'react'; - -import { appRouter } from '../router/appRouter'; -import cardBuilder from '../cardbuilder/cardBuilder'; -import layoutManager from '../layoutManager'; -import lazyLoader from '../lazyLoader/lazyLoaderIntersectionObserver'; -import globalize from '../../scripts/globalize'; -import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement'; -import ItemsContainerElement from '../../elements/ItemsContainerElement'; - -const createLinkElement = ({ className, title, href }: { className?: string, title?: string | null, href?: string }) => ({ - __html: ` -

- ${title} -

- -
` -}); - -interface GenresItemsContainerProps { - topParentId?: string | null; - itemsResult: BaseItemDtoQueryResult; -} - -const GenresItemsContainer: FC = ({ - topParentId, - itemsResult = {} -}) => { - const element = useRef(null); - - const enableScrollX = useCallback(() => { - return !layoutManager.desktop; - }, []); - - const getPortraitShape = useCallback(() => { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - }, [enableScrollX]); - - const fillItemsContainer = useCallback((entry) => { - const elem = entry.target; - const id = elem.getAttribute('data-id'); - - const query = { - SortBy: 'Random', - SortOrder: 'Ascending', - IncludeItemTypes: 'Movie', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary', - Limit: 12, - GenreIds: id, - EnableTotalRecordCount: false, - ParentId: topParentId - }; - window.ApiClient.getItems(window.ApiClient.getCurrentUserId(), query).then((result) => { - cardBuilder.buildCards(result.Items || [], { - itemsContainer: elem, - shape: getPortraitShape(), - scalable: true, - overlayMoreButton: true, - allowBottomPadding: true, - showTitle: true, - centerText: true, - showYear: true - }); - }).catch(err => { - console.error('[GenresItemsContainer] failed to fetch items', err); - }); - }, [getPortraitShape, topParentId]); - - useEffect(() => { - const elem = element.current; - lazyLoader.lazyChildren(elem, fillItemsContainer); - }, [itemsResult.Items, fillItemsContainer]); - - const items = itemsResult.Items || []; - return ( -
- { - !items.length ? ( -
-

{globalize.translate('MessageNothingHere')}

-

{globalize.translate('MessageNoGenresAvailable')}

-
- ) : items.map(item => ( -
-
- - {enableScrollX() ? - : - } -
- )) - } -
- ); -}; - -export default GenresItemsContainer; diff --git a/src/components/common/RecommendationContainer.tsx b/src/components/common/RecommendationContainer.tsx deleted file mode 100644 index 6c28272c9f..0000000000 --- a/src/components/common/RecommendationContainer.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { RecommendationDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC } from 'react'; - -import globalize from '../../scripts/globalize'; -import escapeHTML from 'escape-html'; -import SectionContainer from './SectionContainer'; - -interface RecommendationContainerProps { - getPortraitShape: () => string; - enableScrollX: () => boolean; - recommendation?: RecommendationDto; -} - -const RecommendationContainer: FC = ({ getPortraitShape, enableScrollX, recommendation = {} }) => { - let title = ''; - - switch (recommendation.RecommendationType) { - case 'SimilarToRecentlyPlayed': - title = globalize.translate('RecommendationBecauseYouWatched', recommendation.BaselineItemName); - break; - - case 'SimilarToLikedItem': - title = globalize.translate('RecommendationBecauseYouLike', recommendation.BaselineItemName); - break; - - case 'HasDirectorFromRecentlyPlayed': - case 'HasLikedDirector': - title = globalize.translate('RecommendationDirectedBy', recommendation.BaselineItemName); - break; - - case 'HasActorFromRecentlyPlayed': - case 'HasLikedActor': - title = globalize.translate('RecommendationStarring', recommendation.BaselineItemName); - break; - } - - return ; -}; - -export default RecommendationContainer; diff --git a/src/components/common/SectionContainer.tsx b/src/components/common/SectionContainer.tsx deleted file mode 100644 index 13c29ee61e..0000000000 --- a/src/components/common/SectionContainer.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import '../../elements/emby-itemscontainer/emby-itemscontainer'; - -import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; -import React, { FC, useEffect, useRef } from 'react'; - -import cardBuilder from '../cardbuilder/cardBuilder'; -import ItemsContainerElement from '../../elements/ItemsContainerElement'; -import ItemsScrollerContainerElement from '../../elements/ItemsScrollerContainerElement'; -import { CardOptions } from '../../types/interface'; - -interface SectionContainerProps { - sectionTitle: string; - enableScrollX: () => boolean; - items?: BaseItemDto[]; - cardOptions?: CardOptions; -} - -const SectionContainer: FC = ({ - sectionTitle, - enableScrollX, - items = [], - cardOptions = {} -}) => { - const element = useRef(null); - - useEffect(() => { - cardBuilder.buildCards(items, { - itemsContainer: element.current?.querySelector('.itemsContainer'), - parentContainer: element.current?.querySelector('.verticalSection'), - scalable: true, - overlayPlayButton: true, - showTitle: true, - centerText: true, - cardLayout: false, - ...cardOptions - }); - }, [cardOptions, enableScrollX, items]); - - return ( -
-
-
-

- {sectionTitle} -

-
- - {enableScrollX() ? : } - -
-
- ); -}; - -export default SectionContainer; diff --git a/src/components/common/ViewItemsContainer.tsx b/src/components/common/ViewItemsContainer.tsx index 5cbc7aace6..1ea5ef9899 100644 --- a/src/components/common/ViewItemsContainer.tsx +++ b/src/components/common/ViewItemsContainer.tsx @@ -12,12 +12,14 @@ import Shuffle from './Shuffle'; import Sort from './Sort'; import NewCollection from './NewCollection'; import globalize from '../../scripts/globalize'; -import { CardOptions, ViewQuerySettings } from '../../types/interface'; import ServerConnections from '../ServerConnections'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import listview from '../listview/listview'; import cardBuilder from '../cardbuilder/cardBuilder'; +import { ViewQuerySettings } from '../../types/interface'; +import { CardOptions } from '../../types/cardOptions'; + interface ViewItemsContainerProps { topParentId: string | null; isBtnShuffleEnabled?: boolean; diff --git a/src/components/dashboard/users/UserPasswordForm.tsx b/src/components/dashboard/users/UserPasswordForm.tsx index d7801fdb42..3f0d70967d 100644 --- a/src/components/dashboard/users/UserPasswordForm.tsx +++ b/src/components/dashboard/users/UserPasswordForm.tsx @@ -6,7 +6,6 @@ import confirm from '../../confirm/confirm'; import loading from '../../loading/loading'; import toast from '../../toast/toast'; import ButtonElement from '../../../elements/ButtonElement'; -import CheckBoxElement from '../../../elements/CheckBoxElement'; import InputElement from '../../../elements/InputElement'; type IProps = { @@ -33,12 +32,9 @@ const UserPasswordForm: FunctionComponent = ({ userId }: IProps) => { LibraryMenu.setTitle(user.Name); - let showLocalAccessSection = false; - if (user.HasConfiguredPassword) { (page.querySelector('#btnResetPassword') as HTMLDivElement).classList.remove('hide'); (page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.remove('hide'); - showLocalAccessSection = true; } else { (page.querySelector('#btnResetPassword') as HTMLDivElement).classList.add('hide'); (page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.add('hide'); @@ -46,23 +42,6 @@ const UserPasswordForm: FunctionComponent = ({ userId }: IProps) => { const canChangePassword = loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess; (page.querySelector('.passwordSection') as HTMLDivElement).classList.toggle('hide', !canChangePassword); - (page.querySelector('.localAccessSection') as HTMLDivElement).classList.toggle('hide', !(showLocalAccessSection && canChangePassword)); - - const txtEasyPassword = page.querySelector('#txtEasyPassword') as HTMLInputElement; - txtEasyPassword.value = ''; - - if (user.HasConfiguredEasyPassword) { - txtEasyPassword.placeholder = '******'; - (page.querySelector('#btnResetEasyPassword') as HTMLDivElement).classList.remove('hide'); - } else { - txtEasyPassword.removeAttribute('placeholder'); - txtEasyPassword.placeholder = ''; - (page.querySelector('#btnResetEasyPassword') as HTMLDivElement).classList.add('hide'); - } - - const chkEnableLocalEasyPassword = page.querySelector('.chkEnableLocalEasyPassword') as HTMLInputElement; - - chkEnableLocalEasyPassword.checked = user.Configuration.EnableLocalPassword || false; import('../../autoFocuser').then(({ default: autoFocuser }) => { autoFocuser.autoFocus(page); @@ -125,75 +104,6 @@ const UserPasswordForm: FunctionComponent = ({ userId }: IProps) => { }); }; - const onLocalAccessSubmit = (e: Event) => { - loading.show(); - saveEasyPassword(); - e.preventDefault(); - return false; - }; - - const saveEasyPassword = () => { - const easyPassword = (page.querySelector('#txtEasyPassword') as HTMLInputElement).value; - - if (easyPassword) { - window.ApiClient.updateEasyPassword(userId, easyPassword).then(function () { - onEasyPasswordSaved(); - }).catch(err => { - console.error('[UserPasswordForm] failed to update easy password', err); - }); - } else { - onEasyPasswordSaved(); - } - }; - - const onEasyPasswordSaved = () => { - window.ApiClient.getUser(userId).then(function (user) { - if (!user.Configuration) { - throw new Error('Unexpected null user.Configuration'); - } - - if (!user.Id) { - throw new Error('Unexpected null user.Id'); - } - - user.Configuration.EnableLocalPassword = (page.querySelector('.chkEnableLocalEasyPassword') as HTMLInputElement).checked; - window.ApiClient.updateUserConfiguration(user.Id, user.Configuration).then(function () { - loading.hide(); - toast(globalize.translate('SettingsSaved')); - - loadUser().catch(err => { - console.error('[UserPasswordForm] failed to load user', err); - }); - }).catch(err => { - console.error('[UserPasswordForm] failed to update user configuration', err); - }); - }).catch(err => { - console.error('[UserPasswordForm] failed to fetch user', err); - }); - }; - - const resetEasyPassword = () => { - const msg = globalize.translate('PinCodeResetConfirmation'); - - confirm(msg, globalize.translate('HeaderPinCodeReset')).then(function () { - loading.show(); - window.ApiClient.resetEasyPassword(userId).then(function () { - loading.hide(); - Dashboard.alert({ - message: globalize.translate('PinCodeResetComplete'), - title: globalize.translate('HeaderPinCodeReset') - }); - loadUser().catch(err => { - console.error('[UserPasswordForm] failed to load user', err); - }); - }).catch(err => { - console.error('[UserPasswordForm] failed to reset easy password', err); - }); - }).catch(() => { - // confirm dialog was closed - }); - }; - const resetPassword = () => { const msg = globalize.translate('PasswordResetConfirmation'); confirm(msg, globalize.translate('ResetPassword')).then(function () { @@ -216,9 +126,6 @@ const UserPasswordForm: FunctionComponent = ({ userId }: IProps) => { }; (page.querySelector('.updatePasswordForm') as HTMLFormElement).addEventListener('submit', onSubmit); - (page.querySelector('.localAccessForm') as HTMLFormElement).addEventListener('submit', onLocalAccessSubmit); - - (page.querySelector('#btnResetEasyPassword') as HTMLButtonElement).addEventListener('click', resetEasyPassword); (page.querySelector('#btnResetPassword') as HTMLButtonElement).addEventListener('click', resetPassword); }, [loadUser, userId]); @@ -269,53 +176,6 @@ const UserPasswordForm: FunctionComponent = ({ userId }: IProps) => {
-
-
-
-
- {globalize.translate('HeaderEasyPinCode')} -
-
-
- {globalize.translate('EasyPasswordHelp')} -
-
-
- -
-
-
- -
- {globalize.translate('LabelInNetworkSignInWithEasyPasswordHelp')} -
-
-
- - -
-
-
); }; diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 907de76b88..9384ba2056 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -675,12 +675,12 @@ function Guide(options) { }); const activeElement = document.activeElement; - const itemId = activeElement && activeElement.getAttribute ? activeElement.getAttribute('data-id') : null; + const itemId = activeElement?.getAttribute ? activeElement.getAttribute('data-id') : null; let channelRowId = null; if (activeElement) { channelRowId = dom.parentWithClass(activeElement, 'channelPrograms'); - channelRowId = channelRowId && channelRowId.getAttribute ? channelRowId.getAttribute('data-channelid') : null; + channelRowId = channelRowId?.getAttribute ? channelRowId.getAttribute('data-channelid') : null; } renderChannelHeaders(context, channels, apiClient); diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index d5da017e2d..2bb9bb7928 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -76,7 +76,7 @@ export function loadSections(elem, apiClient, user, userSettings) { }); } else { let noLibDescription; - if (user['Policy'] && user['Policy']['IsAdministrator']) { + if (user.Policy?.IsAdministrator) { noLibDescription = globalize.translate('NoCreatedLibraries', '
', ''); } else { noLibDescription = globalize.translate('AskAdminToCreateLibrary'); diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 124caa5e0a..f46885c79d 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -68,7 +68,7 @@ export function handleHlsJsMediaError(instance, reject) { let now = Date.now(); - if (window.performance && window.performance.now) { + if (window.performance?.now) { now = performance.now(); // eslint-disable-line compat/compat } diff --git a/src/components/imageOptionsEditor/imageOptionsEditor.js b/src/components/imageOptionsEditor/imageOptionsEditor.js index 0b9e0ad0b7..6e9d63166c 100644 --- a/src/components/imageOptionsEditor/imageOptionsEditor.js +++ b/src/components/imageOptionsEditor/imageOptionsEditor.js @@ -80,32 +80,28 @@ function saveValues(context, options) { }); } -function showEditor(itemType, options, availableOptions) { - const dlg = dialogHelper.createDialog({ - size: 'small', - removeOnClose: true, - scrollY: false - }); - dlg.classList.add('formDialog'); - dlg.innerHTML = globalize.translateHtml(template); - dlg.addEventListener('close', function () { - saveValues(dlg, options); - }); - loadValues(dlg, itemType, options, availableOptions); - dialogHelper.open(dlg).then(() => { - return; - }).catch(() => { - return; - }); - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); -} - -export class editor { - constructor() { - this.show = showEditor; +class ImageOptionsEditor { + show(itemType, options, availableOptions) { + const dlg = dialogHelper.createDialog({ + size: 'small', + removeOnClose: true, + scrollY: false + }); + dlg.classList.add('formDialog'); + dlg.innerHTML = globalize.translateHtml(template); + dlg.addEventListener('close', function () { + saveValues(dlg, options); + }); + loadValues(dlg, itemType, options, availableOptions); + dialogHelper.open(dlg).then(() => { + return; + }).catch(() => { + return; + }); + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); } } -export default editor; +export default ImageOptionsEditor; diff --git a/src/components/imageOptionsEditor/imageOptionsEditor.template.html b/src/components/imageOptionsEditor/imageOptionsEditor.template.html index 79c8845161..685f84f2d6 100644 --- a/src/components/imageOptionsEditor/imageOptionsEditor.template.html +++ b/src/components/imageOptionsEditor/imageOptionsEditor.template.html @@ -1,5 +1,7 @@
- +

${HeaderImageOptions}

diff --git a/src/components/imageUploader/imageUploader.js b/src/components/imageUploader/imageUploader.js index c2c70d32ec..c016075ba2 100644 --- a/src/components/imageUploader/imageUploader.js +++ b/src/components/imageUploader/imageUploader.js @@ -41,7 +41,7 @@ function onFileReaderError(evt) { function setFiles(page, files) { const file = files[0]; - if (!file || !file.type.match('image.*')) { + if (!file?.type.match('image.*')) { page.querySelector('#imageOutput').innerHTML = ''; page.querySelector('#fldUpload').classList.add('hide'); currentFile = null; diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index beb4bb31a5..178312a314 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -9,7 +9,7 @@ worker.addEventListener( 'message', ({ data: { pixels, hsh, width, height } }) => { const elems = targetDic[hsh]; - if (elems && elems.length) { + if (elems?.length) { for (const elem of elems) { drawBlurhash(elem, pixels, width, height); } diff --git a/src/components/indicators/indicators.js b/src/components/indicators/indicators.js index 9abfffd8e1..ec21ae3e7f 100644 --- a/src/components/indicators/indicators.js +++ b/src/components/indicators/indicators.js @@ -12,7 +12,7 @@ export function enableProgressIndicator(item) { export function getProgressHtml(pct, options) { let containerClass = 'itemProgressBar'; - if (options && options.containerClass) { + if (options?.containerClass) { containerClass += ' ' + options.containerClass; } @@ -21,7 +21,7 @@ export function getProgressHtml(pct, options) { function getAutoTimeProgressHtml(pct, options, isRecording, start, end) { let containerClass = 'itemProgressBar'; - if (options && options.containerClass) { + if (options?.containerClass) { containerClass += ' ' + options.containerClass; } @@ -36,7 +36,7 @@ function getAutoTimeProgressHtml(pct, options, isRecording, start, end) { export function getProgressBarHtml(item, options) { let pct; if (enableProgressIndicator(item) && item.Type !== 'Recording') { - const userData = options && options.userData ? options.userData : item.UserData; + const userData = options?.userData ? options.userData : item.UserData; if (userData) { pct = userData.PlayedPercentage; @@ -90,7 +90,7 @@ export function getPlayedIndicatorHtml(item) { } export function getChildCountIndicatorHtml(item, options) { - const minCount = options && options.minCount ? options.minCount : 0; + const minCount = options?.minCount ? options.minCount : 0; if (item.ChildCount && item.ChildCount > minCount) { return '
' + datetime.toLocaleString(item.ChildCount) + '
'; diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index b96ac14780..0ec982f15a 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -345,8 +345,8 @@ function executeCommand(item, id, options) { }); break; case 'addtoplaylist': - import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor({ + import('./playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { + new PlaylistEditor({ items: [itemId], serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); @@ -630,8 +630,8 @@ function deleteItem(apiClient, item) { } function refresh(apiClient, item) { - import('./refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ + import('./refreshdialog/refreshdialog').then(({ default: RefreshDialog }) => { + new RefreshDialog({ itemIds: [item.Id], serverId: apiClient.serverInfo().Id, mode: item.Type === 'CollectionFolder' ? 'scan' : null diff --git a/src/components/itemHelper.js b/src/components/itemHelper.js index b051fc74c4..ed5982051f 100644 --- a/src/components/itemHelper.js +++ b/src/components/itemHelper.js @@ -117,7 +117,7 @@ export function canEdit(user, item) { } export function isLocalItem(item) { - return item && item.Id && typeof item.Id === 'string' && item.Id.indexOf('local') === 0; + return item?.Id && typeof item.Id === 'string' && item.Id.indexOf('local') === 0; } export function canIdentify (user, item) { diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js index 0eb1348760..a6ce545ee0 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.js +++ b/src/components/itemMediaInfo/itemMediaInfo.js @@ -61,7 +61,7 @@ function getMediaSourceHtml(user, item, version) { if (version.Container) { html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}
`; } - if (version.Formats && version.Formats.length) { + if (version.Formats?.length) { html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}
`; } if (version.Path && user && user.Policy.IsAdministrator) { diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js index b1aa1a1b9b..a999f11086 100644 --- a/src/components/itemidentifier/itemidentifier.js +++ b/src/components/itemidentifier/itemidentifier.js @@ -79,7 +79,7 @@ function searchForIdentificationResults(page) { SearchInfo: lookupInfo }; - if (currentItem && currentItem.Id) { + if (currentItem?.Id) { lookupInfo.ItemId = currentItem.Id; } else { lookupInfo.IncludeDisabledProviders = true; diff --git a/src/components/itemsrefresher.js b/src/components/itemsrefresher.js index 4a784b91c9..5bac0621fa 100644 --- a/src/components/itemsrefresher.js +++ b/src/components/itemsrefresher.js @@ -134,7 +134,7 @@ class ItemsRefresher { } } - if (this.needsRefresh || (options && options.refresh)) { + if (this.needsRefresh || (options?.refresh)) { return this.refreshItems(); } diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index 9f25b6c3e6..36ee3c7978 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -495,7 +495,7 @@ function setImageFetchersIntoOptions(parent, options) { } function setImageOptionsIntoOptions(options) { - const originalTypeOptions = (currentLibraryOptions || {}).TypeOptions || []; + const originalTypeOptions = currentLibraryOptions?.TypeOptions || []; for (const originalTypeOption of originalTypeOptions) { let typeOptions = getTypeOptions(options, originalTypeOption.Type); diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index a6a588dc66..d35bdc3527 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -86,7 +86,7 @@ function getImageUrl(item, size) { type: 'Primary' }; - if (item.ImageTags && item.ImageTags.Primary) { + if (item.ImageTags?.Primary) { options.tag = item.ImageTags.Primary; itemId = item.Id; } else if (item.AlbumId && item.AlbumPrimaryImageTag) { @@ -235,7 +235,7 @@ export function getListViewHtml(options) { const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; - const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : ''; + const positionTicksData = item.UserData?.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : ''; const collectionIdData = options.collectionId ? (` data-collectionid="${options.collectionId}"`) : ''; const playlistIdData = options.playlistId ? (` data-playlistid="${options.playlistId}"`) : ''; const mediaTypeData = item.MediaType ? (` data-mediatype="${item.MediaType}"`) : ''; diff --git a/src/components/mediaLibraryCreator/mediaLibraryCreator.js b/src/components/mediaLibraryCreator/mediaLibraryCreator.js index 8fe961ce5a..6694e5b9a4 100644 --- a/src/components/mediaLibraryCreator/mediaLibraryCreator.js +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.js @@ -188,7 +188,7 @@ function initLibraryOptions(dlg) { }); } -export class showEditor { +export class MediaLibraryCreator { constructor(options) { return new Promise((resolve) => { currentOptions = options; @@ -224,4 +224,4 @@ let currentOptions; let hasChanges = false; let isCreating = false; -export default showEditor; +export default MediaLibraryCreator; diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.js b/src/components/mediaLibraryEditor/mediaLibraryEditor.js index b1389552a8..72bedb2d11 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.js +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.js @@ -93,7 +93,7 @@ function onListItemClick(e) { if (listItem) { const index = parseInt(listItem.getAttribute('data-index'), 10); - const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; + const pathInfos = currentOptions.library.LibraryOptions?.PathInfos || []; const pathInfo = index == null ? {} : pathInfos[index] || {}; const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); @@ -139,7 +139,7 @@ function refreshLibraryFromServer(page) { } function renderLibrary(page, options) { - let pathInfos = (options.library.LibraryOptions || {}).PathInfos || []; + let pathInfos = options.library.LibraryOptions?.PathInfos || []; if (!pathInfos.length) { pathInfos = options.library.Locations.map(p => { @@ -197,7 +197,7 @@ function onDialogClosed() { currentDeferred.resolveWith(null, [hasChanges]); } -export class showEditor { +export class MediaLibraryEditor { constructor(options) { const deferred = jQuery.Deferred(); currentOptions = options; @@ -231,4 +231,4 @@ let currentOptions; let hasChanges = false; let isCreating = false; -export default showEditor; +export default MediaLibraryEditor; diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index b21c615cdd..9c15d316b7 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -759,7 +759,7 @@ function fillItemInfo(context, item, parentalRatingOptions) { context.querySelector('#txtName').value = item.Name || ''; context.querySelector('#txtOriginalName').value = item.OriginalTitle || ''; context.querySelector('#txtOverview').value = item.Overview || ''; - context.querySelector('#txtTagline').value = (item.Taglines && item.Taglines.length ? item.Taglines[0] : ''); + context.querySelector('#txtTagline').value = (item.Taglines?.length ? item.Taglines[0] : ''); context.querySelector('#txtSortName').value = item.ForcedSortName || ''; context.querySelector('#txtCommunityRating').value = item.CommunityRating || ''; @@ -826,7 +826,7 @@ function fillItemInfo(context, item, parentalRatingOptions) { context.querySelector('#txtAirTime').value = item.AirTime || ''; - const placeofBirth = item.ProductionLocations && item.ProductionLocations.length ? item.ProductionLocations[0] : ''; + const placeofBirth = item.ProductionLocations?.length ? item.ProductionLocations[0] : ''; context.querySelector('#txtPlaceOfBirth').value = placeofBirth; context.querySelector('#txtOriginalAspectRatio').value = item.AspectRatio || ''; diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index 989a421cdb..48cbce0e1f 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -6,7 +6,7 @@ import dom from '../../scripts/dom'; import './multiSelect.scss'; import ServerConnections from '../ServerConnections'; import alert from '../alert'; -import playlistEditor from '../playlisteditor/playlisteditor'; +import PlaylistEditor from '../playlisteditor/playlisteditor'; import confirm from '../confirm/confirm'; import itemHelper from '../itemHelper'; import datetime from '../../scripts/datetime'; @@ -269,7 +269,7 @@ function showMenuForSelectedItems(e) { dispatchNeedsRefresh(); break; case 'playlist': - new playlistEditor({ + new PlaylistEditor({ items: items, serverId: serverId }); @@ -299,8 +299,8 @@ function showMenuForSelectedItems(e) { dispatchNeedsRefresh(); break; case 'refresh': - import('../refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ + import('../refreshdialog/refreshdialog').then(({ default: RefreshDialog }) => { + new RefreshDialog({ itemIds: items, serverId: serverId }).show(); diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 9181b3f059..37400559f2 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -243,7 +243,7 @@ function bindEvents(elem) { positionSlider.getBubbleText = function (value) { const state = lastPlayerState; - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + if (!state?.NowPlayingItem || !currentRuntimeTicks) { return '--:--'; } @@ -489,7 +489,7 @@ function imageUrl(item, options) { options = options || {}; options.type = options.type || 'Primary'; - if (item.ImageTags && item.ImageTags[options.type]) { + if (item.ImageTags?.[options.type]) { options.tag = item.ImageTags[options.type]; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 3bfe44e37d..d7d2dcabbe 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -35,7 +35,7 @@ function seriesImageUrl(item, options = {}) { function imageUrl(item, options = {}) { options.type = options.type || 'Primary'; - if (item.ImageTags && item.ImageTags[options.type]) { + if (item.ImageTags?.[options.type]) { options.tag = item.ImageTags[options.type]; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); diff --git a/src/components/playback/nowplayinghelper.js b/src/components/playback/nowplayinghelper.js index a2b72ca84f..7c5dccaf35 100644 --- a/src/components/playback/nowplayinghelper.js +++ b/src/components/playback/nowplayinghelper.js @@ -23,7 +23,7 @@ export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { let bottomText = ''; - if (nowPlayingItem.ArtistItems && nowPlayingItem.ArtistItems.length) { + if (nowPlayingItem.ArtistItems?.length) { bottomItem = { Id: nowPlayingItem.ArtistItems[0].Id, Name: nowPlayingItem.ArtistItems[0].Name, @@ -34,7 +34,7 @@ export function getNowPlayingNames(nowPlayingItem, includeNonNameInfo) { bottomText = nowPlayingItem.ArtistItems.map(function (a) { return a.Name; }).join(', '); - } else if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { + } else if (nowPlayingItem.Artists?.length) { bottomText = nowPlayingItem.Artists.join(', '); } else if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { bottomText = topText; diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 77cdb4b6d5..308acd79f5 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -163,12 +163,12 @@ function backdropImageUrl(apiClient, item, options) { options.quality = 100; } - if (item.BackdropImageTags && item.BackdropImageTags.length) { + if (item.BackdropImageTags?.length) { options.tag = item.BackdropImageTags[0]; return apiClient.getScaledImageUrl(item.Id, options); } - if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + if (item.ParentBackdropImageTags?.length) { options.tag = item.ParentBackdropImageTags[0]; return apiClient.getScaledImageUrl(item.ParentBackdropItemId, options); } @@ -773,7 +773,7 @@ class PlaybackManager { self.setActivePlayer = function (player, targetInfo) { if (player === 'localplayer' || player.name === 'localplayer') { - if (self._currentPlayer && self._currentPlayer.isLocalPlayer) { + if (self._currentPlayer?.isLocalPlayer) { return; } setCurrentPlayerInternal(null, null); @@ -795,7 +795,7 @@ class PlaybackManager { self.trySetActivePlayer = function (player, targetInfo) { if (player === 'localplayer' || player.name === 'localplayer') { - if (self._currentPlayer && self._currentPlayer.isLocalPlayer) { + if (self._currentPlayer?.isLocalPlayer) { return; } return; @@ -967,7 +967,7 @@ class PlaybackManager { return player.isPlaying(); } - return player != null && player.currentSrc() != null; + return player?.currentSrc() != null; }; self.isPlayingMediaType = function (mediaType, player) { @@ -989,7 +989,7 @@ class PlaybackManager { self.isPlayingLocally = function (mediaTypes, player) { player = player || self._currentPlayer; - if (!player || !player.isLocalPlayer) { + if (!player?.isLocalPlayer) { return false; } @@ -1068,7 +1068,7 @@ class PlaybackManager { self.setAspectRatio = function (val, player) { player = player || self._currentPlayer; - if (player && player.setAspectRatio) { + if (player?.setAspectRatio) { player.setAspectRatio(val); } }; @@ -1076,7 +1076,7 @@ class PlaybackManager { self.getSupportedAspectRatios = function (player) { player = player || self._currentPlayer; - if (player && player.getSupportedAspectRatios) { + if (player?.getSupportedAspectRatios) { return player.getSupportedAspectRatios(); } @@ -1086,7 +1086,7 @@ class PlaybackManager { self.getAspectRatio = function (player) { player = player || self._currentPlayer; - if (player && player.getAspectRatio) { + if (player?.getAspectRatio) { return player.getAspectRatio(); } }; @@ -1131,7 +1131,7 @@ class PlaybackManager { self.getSupportedPlaybackRates = function (player) { player = player || self._currentPlayer; - if (player && player.getSupportedPlaybackRates) { + if (player?.getSupportedPlaybackRates) { return player.getSupportedPlaybackRates(); } return []; @@ -1351,7 +1351,7 @@ class PlaybackManager { self.getMaxStreamingBitrate = function (player) { player = player || self._currentPlayer; - if (player && player.getMaxStreamingBitrate) { + if (player?.getMaxStreamingBitrate) { return player.getMaxStreamingBitrate(); } @@ -1370,7 +1370,7 @@ class PlaybackManager { self.enableAutomaticBitrateDetection = function (player) { player = player || self._currentPlayer; - if (player && player.enableAutomaticBitrateDetection) { + if (player?.enableAutomaticBitrateDetection) { return player.enableAutomaticBitrateDetection(); } @@ -1386,7 +1386,7 @@ class PlaybackManager { self.setMaxStreamingBitrate = function (options, player) { player = player || self._currentPlayer; - if (player && player.setMaxStreamingBitrate) { + if (player?.setMaxStreamingBitrate) { return player.setMaxStreamingBitrate(options); } @@ -1443,7 +1443,7 @@ class PlaybackManager { document.webkitCancelFullscreen(); } else { const elem = document.querySelector('video'); - if (elem && elem.webkitEnterFullscreen) { + if (elem?.webkitEnterFullscreen) { elem.webkitEnterFullscreen(); } } @@ -2078,7 +2078,7 @@ class PlaybackManager { const mediaSource = self.currentMediaSource(player); - if (mediaSource && mediaSource.RunTimeTicks) { + if (mediaSource?.RunTimeTicks) { return mediaSource.RunTimeTicks; } @@ -2614,7 +2614,7 @@ class PlaybackManager { if (mediaSource.MediaStreams && player.useFullSubtitleUrls) { mediaSource.MediaStreams.forEach(stream => { - if (stream.DeliveryUrl && stream.DeliveryUrl.startsWith('/')) { + if (stream.DeliveryUrl?.startsWith('/')) { stream.DeliveryUrl = apiClient.getUrl(stream.DeliveryUrl); } }); @@ -3444,7 +3444,7 @@ class PlaybackManager { const streamInfo = getPlayerData(player).streamInfo; - if (streamInfo && streamInfo.started && !streamInfo.ended) { + if (streamInfo?.started && !streamInfo.ended) { reportPlayback(self, state, player, reportPlaylist, serverId, 'reportPlaybackProgress', progressEventName); } @@ -3515,7 +3515,7 @@ class PlaybackManager { const nextItem = this._playQueueManager.getNextItemInfo(); - if (!nextItem || !nextItem.item) { + if (!nextItem?.item) { return Promise.reject(); } @@ -3656,7 +3656,7 @@ class PlaybackManager { async playTrailers(item) { const player = this._currentPlayer; - if (player && player.playTrailers) { + if (player?.playTrailers) { return player.playTrailers(item); } @@ -3668,7 +3668,7 @@ class PlaybackManager { items = await apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id); } - if (!items || !items.length) { + if (!items?.length) { items = (item.RemoteTrailers || []).map((t) => { return { Name: t.Name || (item.Name + ' Trailer'), @@ -3750,7 +3750,7 @@ class PlaybackManager { } setPlaybackRate(value, player = this._currentPlayer) { - if (player && player.setPlaybackRate) { + if (player?.setPlaybackRate) { player.setPlaybackRate(value); // Save the new playback rate in the browser session, to restore when playing a new video. @@ -3759,7 +3759,7 @@ class PlaybackManager { } getPlaybackRate(player = this._currentPlayer) { - if (player && player.getPlaybackRate) { + if (player?.getPlaybackRate) { return player.getPlaybackRate(); } @@ -3767,7 +3767,7 @@ class PlaybackManager { } instantMix(item, player = this._currentPlayer) { - if (player && player.instantMix) { + if (player?.instantMix) { return player.instantMix(item); } @@ -3788,7 +3788,7 @@ class PlaybackManager { } shuffle(shuffleItem, player = this._currentPlayer) { - if (player && player.shuffle) { + if (player?.shuffle) { return player.shuffle(shuffleItem); } @@ -3805,7 +3805,7 @@ class PlaybackManager { const mediaSource = this.currentMediaSource(player); - const mediaStreams = (mediaSource || {}).MediaStreams || []; + const mediaStreams = mediaSource?.MediaStreams || []; return mediaStreams.filter(function (s) { return s.Type === 'Audio'; }).sort(itemHelper.sortTracks); @@ -3821,7 +3821,7 @@ class PlaybackManager { const mediaSource = this.currentMediaSource(player); - const mediaStreams = (mediaSource || {}).MediaStreams || []; + const mediaStreams = mediaSource?.MediaStreams || []; return mediaStreams.filter(function (s) { return s.Type === 'Subtitle'; }).sort(itemHelper.sortTracks); @@ -3960,7 +3960,7 @@ class PlaybackManager { } displayContent(options, player = this._currentPlayer) { - if (player && player.displayContent) { + if (player?.displayContent) { player.displayContent(options); } } diff --git a/src/components/playback/playbackorientation.js b/src/components/playback/playbackorientation.js index a4bd8324c6..b581801f9e 100644 --- a/src/components/playback/playbackorientation.js +++ b/src/components/playback/playbackorientation.js @@ -1,4 +1,3 @@ - import { playbackManager } from './playbackmanager'; import layoutManager from '../layoutManager'; import Events from '../../utils/events.ts'; @@ -19,7 +18,7 @@ Events.on(playbackManager, 'playbackstart', function (e, player) { if (isLocalVideo && layoutManager.mobile) { /* eslint-disable-next-line compat/compat */ - const lockOrientation = window.screen.lockOrientation || window.screen.mozLockOrientation || window.screen.msLockOrientation || (window.screen.orientation && window.screen.orientation.lock); + const lockOrientation = window.screen.lockOrientation || window.screen.mozLockOrientation || window.screen.msLockOrientation || (window.screen.orientation?.lock); if (lockOrientation) { try { @@ -40,7 +39,7 @@ Events.on(playbackManager, 'playbackstart', function (e, player) { Events.on(playbackManager, 'playbackstop', function (e, playbackStopInfo) { if (orientationLocked && !playbackStopInfo.nextMediaType) { /* eslint-disable-next-line compat/compat */ - const unlockOrientation = window.screen.unlockOrientation || window.screen.mozUnlockOrientation || window.screen.msUnlockOrientation || (window.screen.orientation && window.screen.orientation.unlock); + const unlockOrientation = window.screen.unlockOrientation || window.screen.mozUnlockOrientation || window.screen.msUnlockOrientation || (window.screen.orientation?.unlock); if (unlockOrientation) { try { diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index da3be20970..e0acde509b 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -248,7 +248,7 @@ export function show(options) { const player = options.player; const currentItem = playbackManager.currentItem(player); - if (!currentItem || !currentItem.ServerId) { + if (!currentItem?.ServerId) { return showWithUser(options, player, null); } diff --git a/src/components/playback/playmethodhelper.js b/src/components/playback/playmethodhelper.js index 1e7002b45d..5c9511adbc 100644 --- a/src/components/playback/playmethodhelper.js +++ b/src/components/playback/playmethodhelper.js @@ -3,9 +3,9 @@ export function getDisplayPlayMethod(session) { return null; } - if (session.TranscodingInfo && session.TranscodingInfo.IsVideoDirect && session.TranscodingInfo.IsAudioDirect) { + if (session.TranscodingInfo?.IsVideoDirect && session.TranscodingInfo.IsAudioDirect) { return 'Remux'; - } else if (session.TranscodingInfo && session.TranscodingInfo.IsVideoDirect) { + } else if (session.TranscodingInfo?.IsVideoDirect) { return 'DirectStream'; } else if (session.PlayState.PlayMethod === 'Transcode') { return 'Transcode'; diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index d03db1ef71..2d34723721 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -166,7 +166,7 @@ function getTranscodingStats(session, player, displayPlayMethod) { value: session.TranscodingInfo.Framerate + ' fps' }); } - if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + if (session.TranscodingInfo.TranscodeReasons?.length) { sessionStats.push({ label: globalize.translate('LabelReasonForTranscoding'), value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index f0fef99aec..ee7cd2b41a 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -221,7 +221,7 @@ function centerFocus(elem, horiz, on) { }); } -export class showEditor { +export class PlaylistEditor { constructor(options) { const items = options.items || {}; currentServerId = options.serverId; @@ -280,4 +280,4 @@ export class showEditor { } } -export default showEditor; +export default PlaylistEditor; diff --git a/src/components/pluginManager.js b/src/components/pluginManager.js index d8aea5293c..948912cbd8 100644 --- a/src/components/pluginManager.js +++ b/src/components/pluginManager.js @@ -72,13 +72,13 @@ class PluginManager { throw new TypeError('Plugin definitions in window have to be an (async) function returning the plugin class'); } - const pluginClass = await pluginDefinition(); - if (typeof pluginClass !== 'function') { + const PluginClass = await pluginDefinition(); + if (typeof PluginClass !== 'function') { throw new TypeError(`Plugin definition doesn't return a class for '${pluginSpec}'`); } // init plugin and pass basic dependencies - plugin = new pluginClass({ + plugin = new PluginClass({ events: Events, loading, appSettings, diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index ac07e200b5..7bee9dccc7 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -6,7 +6,7 @@ import loading from '../loading/loading'; import scrollHelper from '../../scripts/scrollHelper'; import datetime from '../../scripts/datetime'; import imageLoader from '../images/imageLoader'; -import recordingFields from './recordingfields'; +import RecordingFields from './recordingfields'; import Events from '../../utils/events.ts'; import '../../elements/emby-button/emby-button'; import '../../elements/emby-button/paper-icon-button-light'; @@ -170,7 +170,7 @@ function showEditor(itemId, serverId) { Events.off(currentRecordingFields, 'recordingchanged', onRecordingChanged); executeCloseAction(closeAction, itemId, serverId); - if (currentRecordingFields && currentRecordingFields.hasChanged()) { + if (currentRecordingFields?.hasChanged()) { resolve(); } else { reject(); @@ -185,7 +185,7 @@ function showEditor(itemId, serverId) { reload(dlg, itemId, serverId); - currentRecordingFields = new recordingFields({ + currentRecordingFields = new RecordingFields({ parent: dlg.querySelector('.recordingFields'), programId: itemId, serverId: serverId diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index f6af1a864f..b790edded4 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -119,7 +119,7 @@ function imageUrl(item, options) { options = options || {}; options.type = options.type || 'Primary'; - if (item.ImageTags && item.ImageTags[options.type]) { + if (item.ImageTags?.[options.type]) { options.tag = item.ImageTags[options.type]; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); } @@ -691,10 +691,10 @@ export default function () { } function savePlaylist() { - import('../playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + import('../playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { getSaveablePlaylistItems().then(function (items) { const serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new playlistEditor({ + new PlaylistEditor({ items: items.map(function (i) { return i.Id; }), @@ -800,7 +800,7 @@ export default function () { positionSlider.getBubbleText = function (value) { const state = lastPlayerState; - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + if (!state?.NowPlayingItem || !currentRuntimeTicks) { return '--:--'; } diff --git a/src/components/router/appRouter.js b/src/components/router/appRouter.js index 7c0d8e86c1..253fdee922 100644 --- a/src/components/router/appRouter.js +++ b/src/components/router/appRouter.js @@ -167,12 +167,13 @@ class AppRouter { canGoBack() { const { path, route } = this.currentRouteInfo; + const pathOnly = path?.split('?')[0] ?? ''; if (!route) { return false; } - if (!document.querySelector('.dialogContainer') && (START_PAGE_TYPES.includes(route.type) || START_PAGE_PATHS.includes(path))) { + if (!document.querySelector('.dialogContainer') && (START_PAGE_TYPES.includes(route.type) || START_PAGE_PATHS.includes(pathOnly))) { return false; } @@ -320,7 +321,7 @@ class AppRouter { path: ctx.path }; }).catch((result) => { - if (!result || !result.cancelled) { + if (!result?.cancelled) { onNewViewNeeded(); } }); @@ -402,7 +403,7 @@ class AppRouter { const isCurrentRouteStartup = this.currentRouteInfo ? this.currentRouteInfo.route.startup : true; const shouldExitApp = ctx.isBack && route.isDefaultRoute && isCurrentRouteStartup; - if (!shouldExitApp && (!apiClient || !apiClient.isLoggedIn()) && !route.anonymous) { + if (!shouldExitApp && (!apiClient?.isLoggedIn()) && !route.anonymous) { console.debug('[appRouter] route does not allow anonymous access: redirecting to login'); this.#beginConnectionWizard(); return; @@ -416,7 +417,7 @@ class AppRouter { return; } - if (apiClient && apiClient.isLoggedIn()) { + if (apiClient?.isLoggedIn()) { console.debug('[appRouter] user is authenticated'); if (route.roles) { diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index 4dfbce8c5d..7dc840e8b6 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -33,7 +33,7 @@ function playAllFromHere(card, serverId, queue) { } const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); - if (itemsContainer && itemsContainer.fetchData) { + if (itemsContainer?.fetchData) { const queryOptions = queue ? { StartIndex: startIndex } : {}; return itemsContainer.fetchData(queryOptions).then(result => { @@ -270,8 +270,8 @@ function executeAction(card, target, action) { } function addToPlaylist(item) { - import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor().show({ + import('./playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { + new PlaylistEditor().show({ items: [item.Id], serverId: item.ServerId diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 5ec641b683..20307c9d18 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -41,7 +41,7 @@ function getImageUrl(item, options, apiClient) { return apiClient.getScaledImageUrl(item, options); } - if (item.ImageTags && item.ImageTags[options.type]) { + if (item.ImageTags?.[options.type]) { options.tag = item.ImageTags[options.type]; return apiClient.getScaledImageUrl(item.Id, options); } @@ -70,7 +70,7 @@ function getBackdropImageUrl(item, options, apiClient) { options.quality = 100; } - if (item.BackdropImageTags && item.BackdropImageTags.length) { + if (item.BackdropImageTags?.length) { options.tag = item.BackdropImageTags[0]; return apiClient.getScaledImageUrl(item.Id, options); } @@ -87,7 +87,7 @@ function getImgUrl(item, user) { const apiClient = ServerConnections.getApiClient(item.ServerId); const imageOptions = {}; - if (item.BackdropImageTags && item.BackdropImageTags.length) { + if (item.BackdropImageTags?.length) { return getBackdropImageUrl(item, imageOptions, apiClient); } else { if (item.MediaType === 'Photo' && user && user.Policy.EnableContentDownloading) { diff --git a/src/components/tabbedview/tabbedview.js b/src/components/tabbedview/tabbedview.js index 8c865ec9f6..2bb85db8c3 100644 --- a/src/components/tabbedview/tabbedview.js +++ b/src/components/tabbedview/tabbedview.js @@ -65,7 +65,7 @@ class TabbedView { const previousIndex = e.detail.previousIndex; const previousTabController = previousIndex == null ? null : self.tabControllers[previousIndex]; - if (previousTabController && previousTabController.onPause) { + if (previousTabController?.onPause) { previousTabController.onPause(); } @@ -93,7 +93,7 @@ class TabbedView { if (!currentTabController) { mainTabsManager.selectedTabIndex(this.initialTabIndex); - } else if (currentTabController && currentTabController.onResume) { + } else if (currentTabController?.onResume) { currentTabController.onResume({}); } } @@ -101,7 +101,7 @@ class TabbedView { onPause() { const currentTabController = this.currentTabController; - if (currentTabController && currentTabController.onPause) { + if (currentTabController?.onPause) { currentTabController.onPause(); } } diff --git a/src/components/themeMediaPlayer.js b/src/components/themeMediaPlayer.js index 9dea2a7770..bad76612ce 100644 --- a/src/components/themeMediaPlayer.js +++ b/src/components/themeMediaPlayer.js @@ -86,7 +86,7 @@ document.addEventListener('viewshow', function (e) { const state = e.detail.state || {}; const item = state.item; - if (item && item.ServerId) { + if (item?.ServerId) { loadThemeMedia(item); return; } diff --git a/src/components/tunerPicker.js b/src/components/tunerPicker.js index 68c6644092..5cb34addc7 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -120,7 +120,7 @@ function discoverDevices(view) { }); } -function tunerPicker() { +function TunerPicker() { this.show = function () { const dialogOptions = { removeOnClose: true, @@ -182,4 +182,4 @@ function tunerPicker() { let currentDevices = []; -export default tunerPicker; +export default TunerPicker; diff --git a/src/components/viewManager/ViewManagerPage.tsx b/src/components/viewManager/ViewManagerPage.tsx index 98cf0dcb01..8795e9edd2 100644 --- a/src/components/viewManager/ViewManagerPage.tsx +++ b/src/components/viewManager/ViewManagerPage.tsx @@ -47,7 +47,7 @@ const ViewManagerPage: FunctionComponent = ({ viewManager.tryRestoreView(viewOptions) .catch(async (result?: RestoreViewFailResponse) => { - if (!result || !result.cancelled) { + if (!result?.cancelled) { const [ controllerFactory, viewHtml ] = await Promise.all([ import(/* webpackChunkName: "[request]" */ `../../controllers/${controller}`), import(/* webpackChunkName: "[request]" */ `../../controllers/${view}`) diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 12a5f1a9d6..9f02fe3042 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -21,6 +21,7 @@ viewContainer.setOnBeforeChange(function (newView, isRestored, options) { newView.initComplete = true; if (typeof options.controllerFactory === 'function') { + // eslint-disable-next-line new-cap new options.controllerFactory(newView, eventDetail.detail.params); } else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') { new options.controllerFactory.default(newView, eventDetail.detail.params); diff --git a/src/controllers/dashboard/dashboard.html b/src/controllers/dashboard/dashboard.html index 7fd60f7695..a76cf11971 100644 --- a/src/controllers/dashboard/dashboard.html +++ b/src/controllers/dashboard/dashboard.html @@ -44,7 +44,7 @@
- +

${HeaderActivity}

@@ -61,7 +61,7 @@
- +

${Alerts}

diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index 95912a55d6..372000a685 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -47,7 +47,7 @@ function showPlaybackInfo(btn, session) { text.push(globalize.translate('MediaIsBeingConverted')); text.push(DashboardPage.getSessionNowPlayingStreamInfo(session)); - if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + if (session.TranscodingInfo?.TranscodeReasons?.length) { text.push('
'); text.push(globalize.translate('LabelReasonForTranscoding')); session.TranscodingInfo.TranscodeReasons.forEach(function (transcodeReason) { @@ -90,7 +90,7 @@ function showOptionsMenu(btn, session) { }); } - if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + if (session.TranscodingInfo?.TranscodeReasons?.length) { menuItems.push({ name: globalize.translate('ViewPlaybackInfo'), id: 'transcodinginfo' @@ -402,7 +402,7 @@ window.DashboardPage = { } else if (displayPlayMethod === 'DirectStream') { html += globalize.translate('DirectStreaming'); } else if (displayPlayMethod === 'Transcode') { - if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { + if (session.TranscodingInfo?.Framerate) { html += `${globalize.translate('Framerate')}: ${session.TranscodingInfo.Framerate}fps`; } @@ -481,7 +481,7 @@ window.DashboardPage = { let topText = escapeHtml(itemHelper.getDisplayName(nowPlayingItem)); let bottomText = ''; - if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { + if (nowPlayingItem.Artists?.length) { bottomText = topText; topText = escapeHtml(nowPlayingItem.Artists[0]); } else { @@ -493,7 +493,7 @@ window.DashboardPage = { } } - if (nowPlayingItem.ImageTags && nowPlayingItem.ImageTags.Logo) { + if (nowPlayingItem.ImageTags?.Logo) { imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.Id, { tag: nowPlayingItem.ImageTags.Logo, maxHeight: 24, @@ -571,7 +571,7 @@ window.DashboardPage = { const btnSessionPlayPauseIcon = btnSessionPlayPause.querySelector('.material-icons'); btnSessionPlayPauseIcon.classList.remove('play_arrow', 'pause'); - btnSessionPlayPauseIcon.classList.add(session.PlayState && session.PlayState.IsPaused ? 'play_arrow' : 'pause'); + btnSessionPlayPauseIcon.classList.add(session.PlayState?.IsPaused ? 'play_arrow' : 'pause'); row.querySelector('.sessionNowPlayingTime').innerText = DashboardPage.getSessionNowPlayingTime(session); row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); @@ -620,7 +620,7 @@ window.DashboardPage = { getNowPlayingImageUrl: function (item) { /* Screen width is multiplied by 0.2, as the there is currently no way to get the width of elements that aren't created yet. */ - if (item && item.BackdropImageTags && item.BackdropImageTags.length) { + if (item?.BackdropImageTags?.length) { return ApiClient.getScaledImageUrl(item.Id, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Backdrop', @@ -628,7 +628,7 @@ window.DashboardPage = { }); } - if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + if (item?.ParentBackdropImageTags?.length) { return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Backdrop', @@ -636,7 +636,7 @@ window.DashboardPage = { }); } - if (item && item.BackdropImageTag) { + if (item?.BackdropImageTag) { return ApiClient.getScaledImageUrl(item.BackdropItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Backdrop', @@ -644,7 +644,7 @@ window.DashboardPage = { }); } - const imageTags = (item || {}).ImageTags || {}; + const imageTags = item?.ImageTags || {}; if (item && imageTags.Thumb) { return ApiClient.getScaledImageUrl(item.Id, { @@ -654,7 +654,7 @@ window.DashboardPage = { }); } - if (item && item.ParentThumbImageTag) { + if (item?.ParentThumbImageTag) { return ApiClient.getScaledImageUrl(item.ParentThumbItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Thumb', @@ -662,7 +662,7 @@ window.DashboardPage = { }); } - if (item && item.ThumbImageTag) { + if (item?.ThumbImageTag) { return ApiClient.getScaledImageUrl(item.ThumbItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Thumb', @@ -678,7 +678,7 @@ window.DashboardPage = { }); } - if (item && item.PrimaryImageTag) { + if (item?.PrimaryImageTag) { return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Primary', @@ -686,7 +686,7 @@ window.DashboardPage = { }); } - if (item && item.AlbumPrimaryImageTag) { + if (item?.AlbumPrimaryImageTag) { return ApiClient.getScaledImageUrl(item.AlbumId, { maxWidth: Math.round(dom.getScreenWidth() * 0.20), type: 'Primary', diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index dd3e3ad957..47583f2510 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -415,7 +415,7 @@ function renderContainerProfiles(page, profiles) { html += ''; html += '

' + globalize.translate('ValueContainer', profile.Container || allText) + '

'; - if (profile.Conditions && profile.Conditions.length) { + if (profile.Conditions?.length) { html += '

'; html += globalize.translate('ValueConditions', profile.Conditions.map(function (c) { return c.Property; @@ -487,7 +487,7 @@ function renderCodecProfiles(page, profiles) { html += ''; html += '

' + globalize.translate('ValueCodec', profile.Codec || allText) + '

'; - if (profile.Conditions && profile.Conditions.length) { + if (profile.Conditions?.length) { html += '

'; html += globalize.translate('ValueConditions', profile.Conditions.map(function (c) { return c.Property; @@ -567,7 +567,7 @@ function renderResponseProfiles(page, profiles) { } } - if (profile.Conditions && profile.Conditions.length) { + if (profile.Conditions?.length) { html += '

'; html += globalize.translate('ValueConditions', profile.Conditions.map(function (c) { return c.Property; diff --git a/src/controllers/dashboard/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html index 3d4ed0b99b..59aae89d1e 100644 --- a/src/controllers/dashboard/encodingsettings.html +++ b/src/controllers/dashboard/encodingsettings.html @@ -335,6 +335,24 @@

${AllowFfmpegThrottlingHelp}
+
+ +
${AllowSegmentDeletionHelp}
+
+ +
+ +
${LabelThrottleDelaySecondsHelp}
+
+ +
+ +
${LabelSegmentKeepSecondsHelp}
+
+
-
- -
${LabelCreateHttpPortMapHelp}
-
-
- +
+
${LabelPublicHttpPortHelp}
@@ -159,36 +152,6 @@
${LabelPublishedServerUriHelp}
-
- -
${LabelUDPPortRangeHelp}
-
-
- -
${LabelHDHomerunPortRangeHelp}
-
- - -
-

${HeaderDebugging}

-
- -
${LabelEnableSSDPTracingHelp}
-
-
- -
${LabelSSDPTracingFilterHelp}
-
-
- -
${LabelAutoDiscoveryTracingHelp}
-
diff --git a/src/controllers/dashboard/networking.js b/src/controllers/dashboard/networking.js index 57a8a51b53..8ad957ab5f 100644 --- a/src/controllers/dashboard/networking.js +++ b/src/controllers/dashboard/networking.js @@ -48,10 +48,10 @@ function onSubmit(e) { }); config.IsRemoteIPFilterBlacklist = form.querySelector('#selectExternalAddressFilterMode').value === 'blacklist'; - config.PublicPort = form.querySelector('#txtPublicPort').value; + config.PublicHttpPort = form.querySelector('#txtPublicHttpPort').value; config.PublicHttpsPort = form.querySelector('#txtPublicHttpsPort').value; - config.HttpServerPortNumber = form.querySelector('#txtPortNumber').value; - config.HttpsPortNumber = form.querySelector('#txtHttpsPort').value; + config.InternalHttpPort = form.querySelector('#txtPortNumber').value; + config.InternalHttpsPort = form.querySelector('#txtHttpsPort').value; config.EnableHttps = form.querySelector('#chkEnableHttps').checked; config.RequireHttps = form.querySelector('#chkRequireHttps').checked; config.EnableUPnP = enableUpnp; @@ -59,16 +59,9 @@ function onSubmit(e) { config.EnableRemoteAccess = form.querySelector('#chkRemoteAccess').checked; config.CertificatePath = form.querySelector('#txtCertificatePath').value || null; config.CertificatePassword = form.querySelector('#txtCertPassword').value || null; - config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; config.AutoDiscovery = form.querySelector('#chkAutodiscovery').checked; - config.AutoDiscoveryTracing = form.querySelector('#chkAutodiscoveryTracing').checked; - config.EnableIPV6 = form.querySelector('#chkEnableIP6').checked; - config.EnableIPV4 = form.querySelector('#chkEnableIP4').checked; - config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; - config.UDPPortRange = form.querySelector('#txtUDPPortRange').value; - config.HDHomerunPortRange = form.querySelector('#txtHDHomerunPortRange').value; - config.EnableSSDPTracing = form.querySelector('#chkEnableSSDPTracing').checked; - config.SSDPTracingFilter = form.querySelector('#txtSSDPTracingFilter').value; + config.EnableIPv6 = form.querySelector('#chkEnableIP6').checked; + config.EnableIPv4 = form.querySelector('#chkEnableIP4').checked; ApiClient.updateNamedConfiguration('network', config).then(Dashboard.processServerConfigurationUpdateResult, Dashboard.processErrorResponse); }); }); @@ -83,7 +76,7 @@ function triggerChange(select) { } function getValidationAlert(form) { - if (form.querySelector('#txtPublicPort').value === form.querySelector('#txtPublicHttpsPort').value) { + if (form.querySelector('#txtPublicHttpPort').value === form.querySelector('#txtPublicHttpsPort').value) { return 'The public http and https ports must be different.'; } @@ -131,8 +124,8 @@ function confirmSelections(localAddress, enableUpnp, callback) { export default function (view) { function loadPage(page, config) { - page.querySelector('#txtPortNumber').value = config.HttpServerPortNumber; - page.querySelector('#txtPublicPort').value = config.PublicPort; + page.querySelector('#txtPortNumber').value = config.InternalHttpPort; + page.querySelector('#txtPublicHttpPort').value = config.PublicHttpPort; page.querySelector('#txtPublicHttpsPort').value = config.PublicHttpsPort; page.querySelector('#txtLocalAddress').value = (config.LocalNetworkAddresses || []).join(', '); page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); @@ -140,7 +133,7 @@ export default function (view) { page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; page.querySelector('#chkRemoteAccess').checked = config.EnableRemoteAccess == null || config.EnableRemoteAccess; - page.querySelector('#txtHttpsPort').value = config.HttpsPortNumber; + page.querySelector('#txtHttpsPort').value = config.InternalHttpsPort; page.querySelector('#chkEnableHttps').checked = config.EnableHttps; page.querySelector('#chkRequireHttps').checked = config.RequireHttps; page.querySelector('#txtBaseUrl').value = config.BaseUrl || ''; @@ -149,16 +142,9 @@ export default function (view) { page.querySelector('#txtCertPassword').value = config.CertificatePassword || ''; page.querySelector('#chkEnableUpnp').checked = config.EnableUPnP; triggerChange(page.querySelector('#chkRemoteAccess')); - page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; page.querySelector('#chkAutodiscovery').checked = config.AutoDiscovery; - page.querySelector('#chkAutodiscoveryTracing').checked = config.AutoDiscoveryTracing; - page.querySelector('#chkEnableIP6').checked = config.EnableIPV6; - page.querySelector('#chkEnableIP4').checked = config.EnableIPV4; - page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; - page.querySelector('#txtUDPPortRange').value = config.UDPPortRange || ''; - page.querySelector('#txtHDHomerunPortRange').checked = config.HDHomerunPortRange || ''; - page.querySelector('#chkEnableSSDPTracing').checked = config.EnableSSDPTracing; - page.querySelector('#txtSSDPTracingFilter').value = config.SSDPTracingFilter || ''; + page.querySelector('#chkEnableIP6').checked = config.EnableIPv6; + page.querySelector('#chkEnableIP4').checked = config.EnableIPv4; page.querySelector('#txtPublishedServer').value = (config.PublishedServerUriBySubnet || []).join(', '); loading.hide(); } @@ -167,13 +153,13 @@ export default function (view) { if (this.checked) { view.querySelector('.fldExternalAddressFilter').classList.remove('hide'); view.querySelector('.fldExternalAddressFilterMode').classList.remove('hide'); - view.querySelector('.fldPublicPort').classList.remove('hide'); + view.querySelector('.fldPublicHttpPort').classList.remove('hide'); view.querySelector('.fldPublicHttpsPort').classList.remove('hide'); view.querySelector('.fldEnableUpnp').classList.remove('hide'); } else { view.querySelector('.fldExternalAddressFilter').classList.add('hide'); view.querySelector('.fldExternalAddressFilterMode').classList.add('hide'); - view.querySelector('.fldPublicPort').classList.add('hide'); + view.querySelector('.fldPublicHttpPort').classList.add('hide'); view.querySelector('.fldPublicHttpsPort').classList.add('hide'); view.querySelector('.fldEnableUpnp').classList.add('hide'); } diff --git a/src/controllers/dashboard/plugins/add/index.html b/src/controllers/dashboard/plugins/add/index.html index 7f83304609..2be40c6206 100644 --- a/src/controllers/dashboard/plugins/add/index.html +++ b/src/controllers/dashboard/plugins/add/index.html @@ -34,7 +34,9 @@
-

+

${LabelDeveloper}:

+

${LabelRepositoryName}:

+

${LabelRepositoryUrl}:

diff --git a/src/controllers/dashboard/plugins/add/index.js b/src/controllers/dashboard/plugins/add/index.js index 62ae70ab4f..ca19d3f4d9 100644 --- a/src/controllers/dashboard/plugins/add/index.js +++ b/src/controllers/dashboard/plugins/add/index.js @@ -64,6 +64,16 @@ function renderPackage(pkg, installedPlugins, page) { $('#description', page).text(pkg.description); $('#developer', page).text(pkg.owner); + // This is a hack; the repository name and URL should be part of the global values + // for the plugin, not each individual version. So we just use the top (latest) + // version to get this information. If it's missing (no versions), then say so. + if (pkg.versions.length) { + $('#repositoryName', page).text(pkg.versions[0].repositoryName); + $('#repositoryUrl', page).text(pkg.versions[0].repositoryUrl); + } else { + $('#repositoryName', page).text(globalize.translate('Unknown')); + $('#repositoryUrl', page).text(globalize.translate('Unknown')); + } if (installedPlugin) { const currentVersionText = globalize.translate('MessageYouHaveVersionInstalled', '' + installedPlugin.Version + ''); @@ -80,7 +90,7 @@ function alertText(options) { } function performInstallation(page, name, guid, version) { - const developer = $('#developer', page).html().toLowerCase(); + const repositoryUrl = $('#repositoryUrl', page).html().toLowerCase(); const alertCallback = function () { loading.show(); @@ -93,7 +103,9 @@ function performInstallation(page, name, guid, version) { }); }; - if (developer !== 'jellyfin') { + // Check the repository URL for the official Jellyfin repository domain, or + // present the warning for 3rd party plugins. + if (!repositoryUrl.startsWith('https://repo.jellyfin.org/')) { loading.hide(); let msg = globalize.translate('MessagePluginInstallDisclaimer'); msg += '
'; diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 14ceff290c..3c83b6e394 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -2,6 +2,7 @@ import loading from '../../../../components/loading/loading'; import libraryMenu from '../../../../scripts/libraryMenu'; import globalize from '../../../../scripts/globalize'; import dialogHelper from '../../../../components/dialogHelper/dialogHelper'; +import confirm from '../../../../components/confirm/confirm'; import '../../../../elements/emby-button/emby-button'; import '../../../../elements/emby-checkbox/emby-checkbox'; @@ -166,14 +167,36 @@ export default function(view) { dialog.querySelector('.newPluginForm').addEventListener('submit', e => { e.preventDefault(); - repositories.push({ - Name: dialog.querySelector('#txtRepositoryName').value, - Url: dialog.querySelector('#txtRepositoryUrl').value, - Enabled: true - }); + const repositoryUrl = dialog.querySelector('#txtRepositoryUrl').value.toLowerCase(); + + const alertCallback = function () { + repositories.push({ + Name: dialog.querySelector('#txtRepositoryName').value, + Url: dialog.querySelector('#txtRepositoryUrl').value, + Enabled: true + }); + saveList(view); + dialogHelper.close(dialog); + }; + + // Check the repository URL for the official Jellyfin repository domain, or + // present the warning for 3rd party plugins. + if (!repositoryUrl.startsWith('https://repo.jellyfin.org/')) { + let msg = globalize.translate('MessageRepositoryInstallDisclaimer'); + msg += '
'; + msg += '
'; + msg += globalize.translate('PleaseConfirmRepositoryInstallation'); + + confirm(msg, globalize.translate('HeaderConfirmRepositoryInstallation')).then(function () { + alertCallback(); + }).catch(() => { + console.debug('repository not installed'); + dialogHelper.close(dialog); + }); + } else { + alertCallback(); + } - saveList(view); - dialogHelper.close(dialog); return false; }); diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index 909bbfffe3..215948205f 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -24,12 +24,6 @@ function fillTimeOfDay(select) { }).join(''); } -Array.prototype.remove = function (from, to) { - const rest = this.slice((to || from) + 1 || this.length); - this.length = from < 0 ? this.length + from : from; - return this.push.apply(this, rest); -}; - const ScheduledTaskPage = { refreshScheduledTask: function (view) { loading.show(); @@ -57,13 +51,13 @@ const ScheduledTaskPage = { html += '
'; html += ''; - if (trigger.MaxRuntimeMs) { + if (trigger.MaxRuntimeTicks) { html += '
'; } else { html += '
'; } html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; - if (trigger.MaxRuntimeMs) { + if (trigger.MaxRuntimeTicks) { html += '
'; const hours = trigger.MaxRuntimeTicks / 36e9; if (hours == 1) { @@ -143,7 +137,7 @@ const ScheduledTaskPage = { loading.show(); const id = getParameterByName('id'); ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.remove(index); + task.Triggers.splice(index, 1); ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { ScheduledTaskPage.refreshScheduledTask(view); }); @@ -201,7 +195,7 @@ const ScheduledTaskPage = { let timeLimit = $('#txtTimeLimit', page).val() || '0'; timeLimit = parseFloat(timeLimit) * 3600000; - trigger.MaxRuntimeMs = timeLimit || null; + trigger.MaxRuntimeTicks = timeLimit * 1e4 || null; return trigger; } diff --git a/src/controllers/home.js b/src/controllers/home.js index 657d406f67..e85d4eb83a 100644 --- a/src/controllers/home.js +++ b/src/controllers/home.js @@ -49,11 +49,11 @@ class HomeView extends TabbedView { } const instance = this; - return import(/* webpackChunkName: "[request]" */ `../controllers/${depends}`).then(({ default: controllerFactory }) => { + return import(/* webpackChunkName: "[request]" */ `../controllers/${depends}`).then(({ default: ControllerFactory }) => { let controller = instance.tabControllers[index]; if (!controller) { - controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); + controller = new ControllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); instance.tabControllers[index] = controller; } diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 68d431b0c4..37f5d3108a 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -406,7 +406,7 @@ function renderName(item, container, context) { if (item.AlbumArtists) { parentNameHtml.push(getArtistLinksHtml(item.AlbumArtists, item.ServerId, context)); parentNameLast = true; - } else if (item.ArtistItems && item.ArtistItems.length && item.Type === 'MusicVideo') { + } else if (item.ArtistItems?.length && item.Type === 'MusicVideo') { parentNameHtml.push(getArtistLinksHtml(item.ArtistItems, item.ServerId, context)); parentNameLast = true; } else if (item.SeriesName && item.Type === 'Episode') { @@ -475,7 +475,7 @@ function renderName(item, container, context) { } function setTrailerButtonVisibility(page, item) { - if ((item.LocalTrailerCount || item.RemoteTrailers && item.RemoteTrailers.length) && playbackManager.getSupportedCommands().indexOf('PlayTrailers') !== -1) { + if ((item.LocalTrailerCount || item.RemoteTrailers?.length) && playbackManager.getSupportedCommands().indexOf('PlayTrailers') !== -1) { hideAll(page, 'btnPlayTrailer', true); } else { hideAll(page, 'btnPlayTrailer'); @@ -505,7 +505,7 @@ function renderDetailPageBackdrop(page, item, apiClient) { let hasbackdrop = false; const itemBackdropElement = page.querySelector('#itemBackdrop'); - if (item.BackdropImageTags && item.BackdropImageTags.length) { + if (item.BackdropImageTags?.length) { imgUrl = apiClient.getScaledImageUrl(item.Id, { type: 'Backdrop', maxWidth: dom.getScreenWidth(), @@ -523,7 +523,7 @@ function renderDetailPageBackdrop(page, item, apiClient) { }); imageLoader.lazyImage(itemBackdropElement, imgUrl); hasbackdrop = true; - } else if (item.ImageTags && item.ImageTags.Primary) { + } else if (item.ImageTags?.Primary) { imgUrl = apiClient.getScaledImageUrl(item.Id, { type: 'Primary', maxWidth: dom.getScreenWidth(), @@ -660,7 +660,7 @@ function logoImageUrl(item, apiClient, options) { options = options || {}; options.type = 'Logo'; - if (item.ImageTags && item.ImageTags.Logo) { + if (item.ImageTags?.Logo) { options.tag = item.ImageTags.Logo; return apiClient.getScaledImageUrl(item.Id, options); } @@ -691,8 +691,8 @@ function showRecordingFields(instance, page, item, user) { const recordingFieldsElement = page.querySelector('.recordingFields'); if (item.Type == 'Program' && user.Policy.EnableLiveTvManagement) { - import('../../components/recordingcreator/recordingfields').then(({ default: recordingFields }) => { - instance.currentRecordingFields = new recordingFields({ + import('../../components/recordingcreator/recordingfields').then(({ default: RecordingFields }) => { + instance.currentRecordingFields = new RecordingFields({ parent: recordingFieldsElement, programId: item.Id, serverId: item.ServerId @@ -1054,7 +1054,7 @@ function renderMiscInfo(page, item) { function renderTagline(page, item) { const taglineElement = page.querySelector('.tagline'); - if (item.Taglines && item.Taglines.length) { + if (item.Taglines?.length) { taglineElement.classList.remove('hide'); taglineElement.innerHTML = '' + escapeHtml(item.Taglines[0]) + ''; } else { @@ -1263,7 +1263,7 @@ function renderSeriesAirTime(page, item) { return; } let html = ''; - if (item.AirDays && item.AirDays.length) { + if (item.AirDays?.length) { if (item.AirDays.length == 7) { html += 'daily'; } else { @@ -1826,7 +1826,7 @@ function renderCast(page, item) { }); } -function itemDetailPage() { +function ItemDetailPage() { const self = this; self.setInitialCollapsibleState = setInitialCollapsibleState; self.renderDetails = renderDetails; @@ -1846,7 +1846,7 @@ function onTrackSelectionsSubmit(e) { return false; } -window.ItemDetailPage = new itemDetailPage(); +window.ItemDetailPage = new ItemDetailPage(); export default function (view, params) { function getApiClient() { diff --git a/src/controllers/list.js b/src/controllers/list.js index 62ef3c12cb..0378a1025c 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -399,8 +399,8 @@ function showSortMenu() { function onNewItemClick() { const instance = this; - import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor({ + import('../components/playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { + new PlaylistEditor({ items: [], serverId: instance.params.serverId }); diff --git a/src/controllers/livetv/livetvguide.js b/src/controllers/livetv/livetvguide.js index c56d4ebd04..2d109b6bef 100644 --- a/src/controllers/livetv/livetvguide.js +++ b/src/controllers/livetv/livetvguide.js @@ -1,4 +1,4 @@ -import tvguide from '../../components/guide/guide'; +import Guide from '../../components/guide/guide'; export default function (view, params, tabContent) { let guideInstance; @@ -6,7 +6,7 @@ export default function (view, params, tabContent) { self.renderTab = function () { if (!guideInstance) { - guideInstance = new tvguide({ + guideInstance = new Guide({ element: tabContent, serverId: ApiClient.serverId() }); diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index eb604b2d29..d8325f7a2b 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -229,7 +229,7 @@ export default function (view, params) { function onTabChange(evt) { const previousTabController = tabControllers[parseInt(evt.detail.previousIndex, 10)]; - if (previousTabController && previousTabController.onHide) { + if (previousTabController?.onHide) { previousTabController.onHide(); } @@ -274,7 +274,7 @@ export default function (view, params) { break; } - import(`../livetv/${depends}`).then(({ default: controllerFactory }) => { + import(`../livetv/${depends}`).then(({ default: ControllerFactory }) => { let tabContent; if (index === 0) { @@ -290,7 +290,7 @@ export default function (view, params) { if (index === 0) { controller = self; } else { - controller = new controllerFactory(view, params, tabContent); + controller = new ControllerFactory(view, params, tabContent); } tabControllers[index] = controller; @@ -388,7 +388,7 @@ export default function (view, params) { inputManager.on(window, onInputCommand); }); view.addEventListener('viewbeforehide', function () { - if (currentTabController && currentTabController.onHide) { + if (currentTabController?.onHide) { currentTabController.onHide(); } diff --git a/src/controllers/livetvguideprovider.js b/src/controllers/livetvguideprovider.js index fbc77cc7b6..7a133945f2 100644 --- a/src/controllers/livetvguideprovider.js +++ b/src/controllers/livetvguideprovider.js @@ -9,8 +9,8 @@ function onListingsSubmitted() { } function init(page, type, providerId) { - import(`../components/tvproviders/${type}`).then(({ default: factory }) => { - const instance = new factory(page, providerId, {}); + import(`../components/tvproviders/${type}`).then(({ default: ProviderFactory }) => { + const instance = new ProviderFactory(page, providerId, {}); Events.on(instance, 'submitted', onListingsSubmitted); instance.init(); }); diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js index 5eb13c9793..3c0e304939 100644 --- a/src/controllers/livetvstatus.js +++ b/src/controllers/livetvstatus.js @@ -165,8 +165,8 @@ function showProviderOptions(page, providerId, button) { } function mapChannels(page, providerId) { - import('../components/channelMapper/channelMapper').then(({ default: channelMapper }) => { - new channelMapper({ + import('../components/channelMapper/channelMapper').then(({ default: ChannelMapper }) => { + new ChannelMapper({ serverId: ApiClient.serverInfo().Id, providerId: providerId }).show(); diff --git a/src/controllers/livetvtuner.js b/src/controllers/livetvtuner.js index b15eea9d8a..7f6ec20270 100644 --- a/src/controllers/livetvtuner.js +++ b/src/controllers/livetvtuner.js @@ -106,8 +106,8 @@ function submitForm(page) { } function getDetectedDevice() { - return import('../components/tunerPicker').then(({ default: tunerPicker }) => { - return new tunerPicker().show({ + return import('../components/tunerPicker').then(({ default: TunerPicker }) => { + return new TunerPicker().show({ serverId: ApiClient.serverId() }); }); diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index 6ec5243715..0d89706cf8 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -289,8 +289,8 @@ export default function (view, params, tabContent, options) { query = userSettings.loadQuerySettings(savedQueryKey, query); this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: query, mode: 'movies', serverId: ApiClient.serverId() diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 2d6aa223cb..1dc60c30e1 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -314,7 +314,7 @@ export default function (view, params) { break; } - import(`../movies/${depends}`).then(({ default: controllerFactory }) => { + import(`../movies/${depends}`).then(({ default: ControllerFactory }) => { let tabContent; if (index === suggestionsTabIndex) { @@ -330,11 +330,11 @@ export default function (view, params) { if (index === suggestionsTabIndex) { controller = this; } else if (index == 0 || index == 3) { - controller = new controllerFactory(view, params, tabContent, { + controller = new ControllerFactory(view, params, tabContent, { mode: index ? 'favorites' : 'movies' }); } else { - controller = new controllerFactory(view, params, tabContent); + controller = new ControllerFactory(view, params, tabContent); } tabControllers[index] = controller; diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index a96d7e51aa..81f9f8b149 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -182,8 +182,8 @@ export default function (view, params, tabContent) { let isLoading = false; this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: getQuery(), mode: 'movies', serverId: ApiClient.serverId() diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index 04ea17a983..6c0c45a0f0 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -189,8 +189,8 @@ export default function (view, params, tabContent) { let isLoading = false; this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: getQuery(), mode: 'albums', serverId: ApiClient.serverId() diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 084f092db3..4126086ade 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -170,8 +170,8 @@ export default function (view, params, tabContent) { let isLoading = false; this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: getQuery(tabContent), mode: this.mode, serverId: ApiClient.serverId() diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index 3599ecedc8..dc32aca6f6 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -290,7 +290,7 @@ export default function (view, params) { break; } - import(`../music/${depends}`).then(({ default: controllerFactory }) => { + import(`../music/${depends}`).then(({ default: ControllerFactory }) => { let tabContent; if (index == 1) { @@ -306,7 +306,7 @@ export default function (view, params) { if (index === 1) { controller = this; } else { - controller = new controllerFactory(view, params, tabContent); + controller = new ControllerFactory(view, params, tabContent); } if (index == 2) { diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 06af0b3081..5e5337d032 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -135,8 +135,8 @@ export default function (view, params, tabContent) { let isLoading = false; self.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: getQuery(tabContent), mode: 'songs', serverId: ApiClient.serverId() diff --git a/src/controllers/playback/queue/index.js b/src/controllers/playback/queue/index.js index cf39aa145b..602135a794 100644 --- a/src/controllers/playback/queue/index.js +++ b/src/controllers/playback/queue/index.js @@ -1,9 +1,9 @@ -import remotecontrolFactory from '../../../components/remotecontrol/remotecontrol'; +import RemoteControl from '../../../components/remotecontrol/remotecontrol'; import libraryMenu from '../../../scripts/libraryMenu'; import '../../../elements/emby-button/emby-button'; export default function (view) { - const remoteControl = new remotecontrolFactory(); + const remoteControl = new RemoteControl(); remoteControl.init(view, view.querySelector('.remoteControlContent')); view.addEventListener('viewshow', function () { libraryMenu.setTransparentMenu(true); diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index cb237d3466..0feb6e3752 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -666,7 +666,7 @@ export default function (view) { if (item.Type === 'TvChannel') { const program = item.CurrentProgram; - if (program && program.EndDate) { + if (program?.EndDate) { try { const endDate = datetime.parseISO8601Date(program.EndDate); @@ -1685,7 +1685,7 @@ export default function (view) { ticks *= value; const item = currentItem; - if (item && item.Chapters && item.Chapters.length && item.Chapters[0].ImageTag) { + if (item?.Chapters?.length && item.Chapters[0].ImageTag) { const html = getChapterBubbleHtml(ServerConnections.getApiClient(item.ServerId), item, item.Chapters, ticks); if (html) { diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index 38b7d077c3..f2f6c605d7 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -171,8 +171,8 @@ export default function (view, params, tabContent) { let isLoading = false; self.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: getQuery(tabContent), mode: 'episodes', serverId: ApiClient.serverId() diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 68019511ba..d3673bf6ae 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -268,7 +268,7 @@ export default function (view, params) { break; } - import(`../shows/${depends}`).then(({ default: controllerFactory }) => { + import(`../shows/${depends}`).then(({ default: ControllerFactory }) => { let tabContent; if (index === 1) { @@ -284,7 +284,7 @@ export default function (view, params) { if (index === 1) { controller = self; } else { - controller = new controllerFactory(view, params, tabContent); + controller = new ControllerFactory(view, params, tabContent); } tabControllers[index] = controller; diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index d378cd34d5..3dea00f1b8 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -199,8 +199,8 @@ export default function (view, params, tabContent) { let isLoading = false; this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ + import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => { + const filterDialog = new FilterDialog({ query: getQuery(tabContent), mode: 'series', serverId: ApiClient.serverId() diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index 1b7067cef8..81b0c3b3ee 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -12,7 +12,7 @@ if (Object.getOwnPropertyDescriptor && Object.defineProperty) { const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); // descriptor returning null in webos - if (descriptor && descriptor.configurable) { + if (descriptor?.configurable) { const baseSetMethod = descriptor.set; descriptor.set = function (value) { baseSetMethod.call(this, value); diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index d34cc25438..b951fa7d2f 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -37,7 +37,7 @@ function onContextMenu(e) { const card = dom.parentWithAttribute(target, 'data-id'); // check for serverId, it won't be present on selectserver - if (card && card.getAttribute('data-serverid')) { + if (card?.getAttribute('data-serverid')) { inputManager.handleCommand('menu', { sourceElement: card }); @@ -357,7 +357,7 @@ ItemsContainerPrototype.resume = function (options) { } } - if (this.needsRefresh || (options && options.refresh)) { + if (this.needsRefresh || (options?.refresh)) { return this.refreshItems(); } diff --git a/src/elements/emby-playstatebutton/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js index 6e7756d902..6bc43b461a 100644 --- a/src/elements/emby-playstatebutton/emby-playstatebutton.js +++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js @@ -132,7 +132,7 @@ EmbyPlaystateButtonPrototype.setItem = function (item) { this.setAttribute('data-serverid', item.ServerId); this.setAttribute('data-type', item.Type); - const played = item.UserData && item.UserData.Played; + const played = item.UserData?.Played; setState(this, played); bindEvents(this); } else { diff --git a/src/elements/emby-scrollbuttons/ScrollButtons.tsx b/src/elements/emby-scrollbuttons/ScrollButtons.tsx index 6f4bce3826..aaeb9bc23e 100644 --- a/src/elements/emby-scrollbuttons/ScrollButtons.tsx +++ b/src/elements/emby-scrollbuttons/ScrollButtons.tsx @@ -10,7 +10,6 @@ enum Direction { } interface ScrollButtonsProps { - scrollRef?: React.MutableRefObject; scrollerFactoryRef: React.MutableRefObject; scrollState: { scrollSize: number; diff --git a/src/elements/emby-scroller/Scroller.tsx b/src/elements/emby-scroller/Scroller.tsx index 773d184c59..cb3d5b75b4 100644 --- a/src/elements/emby-scroller/Scroller.tsx +++ b/src/elements/emby-scroller/Scroller.tsx @@ -1,10 +1,11 @@ import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; +import useElementSize from 'hooks/useElementSize'; import layoutManager from '../../components/layoutManager'; import dom from '../../scripts/dom'; import browser from '../../scripts/browser'; import focusManager from '../../components/focusManager'; -import scrollerFactory from '../../libraries/scroller'; +import ScrollerFactory from '../../libraries/scroller'; import ScrollButtons from '../emby-scrollbuttons/ScrollButtons'; import './emby-scroller.scss'; @@ -32,15 +33,15 @@ const Scroller: FC = ({ isAllowNativeSmoothScrollEnabled, children }) => { + const [scrollRef, size] = useElementSize(); + const [showControls, setShowControls] = useState(false); const [scrollState, setScrollState] = useState({ - scrollSize: 0, + scrollSize: size.width, scrollPos: 0, scrollWidth: 0 }); - - const scrollRef = useRef(null); - const scrollerFactoryRef = useRef(null); + const scrollerFactoryRef = useRef(null); const getScrollSlider = useCallback(() => { if (scrollerFactoryRef.current) { @@ -125,7 +126,7 @@ const Scroller: FC = ({ }); }, [getScrollPosition, getScrollSize, getScrollWidth]); - const initCenterFocus = useCallback((elem: EventTarget, scrollerInstance: scrollerFactory) => { + const initCenterFocus = useCallback((elem, scrollerInstance: ScrollerFactory) => { dom.addEventListener(elem, 'focus', function (e: FocusEvent) { const focused = focusManager.focusableParent(e.target); if (focused) { @@ -150,15 +151,10 @@ const Scroller: FC = ({ }, [scrollerFactoryRef]); useEffect(() => { - const scrollerElement = scrollRef.current as HTMLDivElement; - const horizontal = isHorizontalEnabled !== false; const scrollbuttons = isScrollButtonsEnabled !== false; const mousewheel = isMouseWheelEnabled !== false; - const slider = scrollerElement.querySelector('.scrollSlider'); - - const scrollFrame = scrollerElement; const enableScrollButtons = layoutManager.desktop && horizontal && scrollbuttons; const options = { @@ -166,7 +162,7 @@ const Scroller: FC = ({ mouseDragging: 1, mouseWheel: mousewheel, touchDragging: 1, - slidee: slider, + slidee: scrollRef.current?.querySelector('.scrollSlider'), scrollBy: 200, speed: horizontal ? 270 : 240, elasticBounds: 1, @@ -183,12 +179,12 @@ const Scroller: FC = ({ }; // If just inserted it might not have any height yet - yes this is a hack - scrollerFactoryRef.current = new scrollerFactory(scrollFrame, options); + scrollerFactoryRef.current = new ScrollerFactory(scrollRef.current, options); scrollerFactoryRef.current.init(); scrollerFactoryRef.current.reload(); if (layoutManager.tv && isCenterFocusEnabled) { - initCenterFocus(scrollerElement, scrollerFactoryRef.current); + initCenterFocus(scrollRef.current, scrollerFactoryRef.current); } if (enableScrollButtons) { @@ -200,9 +196,8 @@ const Scroller: FC = ({ } return () => { - const scrollerInstance = scrollerFactoryRef.current; - if (scrollerInstance) { - scrollerInstance.destroy(); + if (scrollerFactoryRef.current) { + scrollerFactoryRef.current.destroy(); scrollerFactoryRef.current = null; } @@ -223,7 +218,8 @@ const Scroller: FC = ({ isScrollEventEnabled, isSkipFocusWhenVisibleEnabled, onScroll, - removeScrollEventListener + removeScrollEventListener, + scrollRef ]); return ( @@ -231,7 +227,6 @@ const Scroller: FC = ({ { showControls && scrollState.scrollWidth > scrollState.scrollSize + 20 && diff --git a/src/elements/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js index eb5f3abfe4..cf716bed41 100644 --- a/src/elements/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -1,4 +1,4 @@ -import scroller from '../../libraries/scroller'; +import ScrollerFactory from '../../libraries/scroller'; import dom from '../../scripts/dom'; import layoutManager from '../../components/layoutManager'; import inputManager from '../../scripts/inputManager'; @@ -140,7 +140,7 @@ ScrollerPrototype.attachedCallback = function () { }; // If just inserted it might not have any height yet - yes this is a hack - this.scroller = new scroller(scrollFrame, options); + this.scroller = new ScrollerFactory(scrollFrame, options); this.scroller.init(); this.scroller.reload(); diff --git a/src/elements/emby-scroller/emby-scroller.scss b/src/elements/emby-scroller/emby-scroller.scss index b02fb00096..ad2fdf54cf 100644 --- a/src/elements/emby-scroller/emby-scroller.scss +++ b/src/elements/emby-scroller/emby-scroller.scss @@ -3,10 +3,10 @@ } .emby-scroller { - margin-left: 3.3%; - margin-left: max(env(safe-area-inset-left), 3.3%); - margin-right: 3.3%; - margin-right: max(env(safe-area-inset-right), 3.3%); + padding-left: 3.3%; + padding-left: max(env(safe-area-inset-left), 3.3%); + padding-right: 3.3%; + padding-right: max(env(safe-area-inset-right), 3.3%); } .servers > .card > .cardBox { @@ -26,13 +26,3 @@ margin-left: 1.2em; } } - -.layout-tv .emby-scroller, -.layout-mobile .emby-scroller { - padding-left: 3.3%; - padding-left: max(env(safe-area-inset-left), 3.3%); - padding-right: 3.3%; - padding-right: max(env(safe-area-inset-right), 3.3%); - margin-left: 0; - margin-right: 0; -} diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index bdba41638c..ec93e61fd6 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -14,7 +14,7 @@ let supportsValueSetOverride = false; if (Object.getOwnPropertyDescriptor && Object.defineProperty) { const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); // descriptor returning null in webos - if (descriptor && descriptor.configurable) { + if (descriptor?.configurable) { supportsValueSetOverride = true; } } @@ -167,7 +167,7 @@ function setMarker(range, valueMarker, marker, valueProgress) { } function updateMarkers(range, currentValue) { - if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { + if (range.markerInfo?.length && range.markerElements?.length) { for (let i = 0, length = range.markerElements.length; i < length; i++) { if (range.markerInfo.length > i) { setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); @@ -382,6 +382,8 @@ EmbySliderPrototype.attachedCallback = function () { } else { startInterval(this); } + + updateValues.call(this); }; /** diff --git a/src/elements/emby-slider/emby-slider.scss b/src/elements/emby-slider/emby-slider.scss index a1d690b0fa..c0f7d5e743 100644 --- a/src/elements/emby-slider/emby-slider.scss +++ b/src/elements/emby-slider/emby-slider.scss @@ -230,6 +230,7 @@ display: flex; align-items: center; justify-content: center; + z-index: 1; } .sliderBubbleText { diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index 22d12e75af..b4b541a3dd 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -1,6 +1,6 @@ import 'webcomponents.js/webcomponents-lite'; import dom from '../../scripts/dom'; -import scroller from '../../libraries/scroller'; +import ScrollerFactory from '../../libraries/scroller'; import browser from '../../scripts/browser'; import focusManager from '../../components/focusManager'; import layoutManager from '../../components/layoutManager'; @@ -124,7 +124,7 @@ function initScroller(tabs) { const contentScrollSlider = tabs.querySelector('.emby-tabs-slider'); if (contentScrollSlider) { - tabs.scroller = new scroller(tabs, { + tabs.scroller = new ScrollerFactory(tabs, { horizontal: 1, itemNav: 0, mouseDragging: 1, diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index 04ce1a42d9..d186398a06 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -19,7 +19,7 @@ function calculateOffset(textarea) { return offset; } -function autoGrow(textarea, maxLines) { +function AutoGrow(textarea, maxLines) { const self = this; if (maxLines === undefined) { @@ -74,7 +74,7 @@ if (Object.getOwnPropertyDescriptor && Object.defineProperty) { const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); // descriptor returning null in webos - if (descriptor && descriptor.configurable) { + if (descriptor?.configurable) { const baseSetMethod = descriptor.set; descriptor.set = function (value) { baseSetMethod.call(this, value); @@ -125,7 +125,7 @@ EmbyTextAreaPrototype.attachedCallback = function () { label.innerText = text; }; - new autoGrow(this); + new AutoGrow(this); }; document.registerElement('emby-textarea', { diff --git a/src/hooks/useApi.tsx b/src/hooks/useApi.tsx index ff94b7a537..a1eb4b624d 100644 --- a/src/hooks/useApi.tsx +++ b/src/hooks/useApi.tsx @@ -7,7 +7,7 @@ import ServerConnections from '../components/ServerConnections'; import events from '../utils/events'; import { toApi } from '../utils/jellyfin-apiclient/compat'; -interface JellyfinApiContext { +export interface JellyfinApiContext { __legacyApiClient__?: ApiClient api?: Api user?: UserDto diff --git a/src/hooks/useElementSize.ts b/src/hooks/useElementSize.ts new file mode 100644 index 0000000000..aeb7000ee4 --- /dev/null +++ b/src/hooks/useElementSize.ts @@ -0,0 +1,25 @@ +import { MutableRefObject, useLayoutEffect, useRef, useState } from 'react'; +import useResizeObserver from '@react-hook/resize-observer'; + +interface Size { + width: number; + height: number; +} + +export default function useElementSize< + T extends HTMLElement = HTMLDivElement +>(): [MutableRefObject, Size] { + const target = useRef(null); + const [size, setSize] = useState({ + width: 0, + height: 0 + }); + + useLayoutEffect(() => { + target.current && setSize(target.current.getBoundingClientRect()); + }, [target]); + + useResizeObserver(target, (entry) => setSize(entry.contentRect)); + + return [target, size]; +} diff --git a/src/hooks/useFetchItems.ts b/src/hooks/useFetchItems.ts new file mode 100644 index 0000000000..c0ed89b85c --- /dev/null +++ b/src/hooks/useFetchItems.ts @@ -0,0 +1,364 @@ +import type { ItemsApiGetItemsRequest } from '@jellyfin/sdk/lib/generated-client'; +import { AxiosRequestConfig } from 'axios'; + +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'; +import { ItemFields } from '@jellyfin/sdk/lib/generated-client/models/item-fields'; +import { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { getFilterApi } from '@jellyfin/sdk/lib/utils/api/filter-api'; +import { getGenresApi } from '@jellyfin/sdk/lib/utils/api/genres-api'; +import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api'; +import { getMoviesApi } from '@jellyfin/sdk/lib/utils/api/movies-api'; +import { getStudiosApi } from '@jellyfin/sdk/lib/utils/api/studios-api'; +import { getTvShowsApi } from '@jellyfin/sdk/lib/utils/api/tv-shows-api'; +import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api'; +import { useQuery } from '@tanstack/react-query'; + +import { JellyfinApiContext, useApi } from './useApi'; +import { Sections, SectionsViewType } from 'types/suggestionsSections'; +import { ParentId } from 'types/library'; + +const fetchGetItem = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id && parentId) { + const response = await getUserLibraryApi(api).getItem( + { + userId: user.Id, + itemId: parentId + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetItem = (parentId: ParentId) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['Item', parentId], + queryFn: ({ signal }) => fetchGetItem(currentApi, parentId, { signal }), + enabled: !!parentId + }); +}; + +const fetchGetItems = async ( + currentApi: JellyfinApiContext, + parametersOptions: ItemsApiGetItemsRequest, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getItemsApi(api).getItems( + { + userId: user.Id, + ...parametersOptions + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetItems = (parametersOptions: ItemsApiGetItemsRequest) => { + const currentApi = useApi(); + return useQuery({ + queryKey: [ + 'Items', + { + ...parametersOptions + } + ], + queryFn: ({ signal }) => + fetchGetItems(currentApi, parametersOptions, { signal }), + cacheTime: parametersOptions.sortBy?.includes(ItemSortBy.Random) ? 0 : undefined + }); +}; + +const fetchGetMovieRecommendations = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getMoviesApi(api).getMovieRecommendations( + { + userId: user.Id, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + parentId: parentId ?? undefined, + categoryLimit: 6, + itemLimit: 20 + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetMovieRecommendations = (parentId: ParentId) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['MovieRecommendations', parentId], + queryFn: ({ signal }) => + fetchGetMovieRecommendations(currentApi, parentId, { signal }), + enabled: !!parentId + }); +}; + +const fetchGetItemsBySuggestionsType = async ( + currentApi: JellyfinApiContext, + sections: Sections, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + let response; + switch (sections.viewType) { + case SectionsViewType.NextUp: { + response = ( + await getTvShowsApi(api).getNextUp( + { + userId: user.Id, + limit: 25, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + parentId: parentId ?? undefined, + imageTypeLimit: 1, + enableImageTypes: [ + ImageType.Primary, + ImageType.Backdrop, + ImageType.Thumb + ], + enableTotalRecordCount: false, + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data.Items; + break; + } + case SectionsViewType.ResumeItems: { + response = ( + await getItemsApi(api).getResumeItems( + { + userId: user?.Id, + parentId: parentId ?? undefined, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + imageTypeLimit: 1, + enableImageTypes: [ImageType.Thumb], + enableTotalRecordCount: false, + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data.Items; + break; + } + case SectionsViewType.LatestMedia: { + response = ( + await getUserLibraryApi(api).getLatestMedia( + { + userId: user.Id, + fields: [ + ItemFields.PrimaryImageAspectRatio, + ItemFields.MediaSourceCount, + ItemFields.BasicSyncInfo + ], + parentId: parentId ?? undefined, + imageTypeLimit: 1, + enableImageTypes: [ImageType.Primary], + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data; + break; + } + default: { + response = ( + await getItemsApi(api).getItems( + { + userId: user.Id, + parentId: parentId ?? undefined, + recursive: true, + fields: [ItemFields.PrimaryImageAspectRatio], + filters: [ItemFilter.IsPlayed], + imageTypeLimit: 1, + enableImageTypes: [ + ImageType.Primary, + ImageType.Backdrop, + ImageType.Thumb + ], + limit: 25, + enableTotalRecordCount: false, + ...sections.parametersOptions + }, + { + signal: options?.signal + } + ) + ).data.Items; + break; + } + } + return response; + } +}; + +export const useGetItemsBySectionType = ( + sections: Sections, + parentId: ParentId +) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['ItemsBySuggestionsType', sections.view], + queryFn: ({ signal }) => + fetchGetItemsBySuggestionsType( + currentApi, + sections, + parentId, + { signal } + ), + enabled: !!sections.view + }); +}; + +const fetchGetGenres = async ( + currentApi: JellyfinApiContext, + itemType: BaseItemKind, + parentId: ParentId, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getGenresApi(api).getGenres( + { + userId: user.Id, + sortBy: [ItemSortBy.SortName], + sortOrder: [SortOrder.Ascending], + includeItemTypes: [itemType], + enableTotalRecordCount: false, + parentId: parentId ?? undefined + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetGenres = (itemType: BaseItemKind, parentId: ParentId) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['Genres', parentId], + queryFn: ({ signal }) => + fetchGetGenres(currentApi, itemType, parentId, { signal }), + enabled: !!parentId + }); +}; + +const fetchGetStudios = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + itemType: BaseItemKind, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getStudiosApi(api).getStudios( + { + userId: user.Id, + includeItemTypes: [itemType], + fields: [ + ItemFields.DateCreated, + ItemFields.PrimaryImageAspectRatio + ], + enableImageTypes: [ImageType.Thumb], + parentId: parentId ?? undefined, + enableTotalRecordCount: false + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetStudios = (parentId: ParentId, itemType: BaseItemKind) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['Studios', parentId, itemType], + queryFn: ({ signal }) => + fetchGetStudios(currentApi, parentId, itemType, { signal }), + enabled: !!parentId + }); +}; + +const fetchGetQueryFiltersLegacy = async ( + currentApi: JellyfinApiContext, + parentId: ParentId, + itemType: BaseItemKind, + options?: AxiosRequestConfig +) => { + const { api, user } = currentApi; + if (api && user?.Id) { + const response = await getFilterApi(api).getQueryFiltersLegacy( + { + userId: user.Id, + parentId: parentId ?? undefined, + includeItemTypes: [itemType] + }, + { + signal: options?.signal + } + ); + return response.data; + } +}; + +export const useGetQueryFiltersLegacy = ( + parentId: ParentId, + itemType: BaseItemKind +) => { + const currentApi = useApi(); + return useQuery({ + queryKey: ['QueryFiltersLegacy', parentId, itemType], + queryFn: ({ signal }) => + fetchGetQueryFiltersLegacy(currentApi, parentId, itemType, { + signal + }), + enabled: !!parentId + }); +}; diff --git a/src/legacy/focusPreventScroll.js b/src/legacy/focusPreventScroll.js index ad2f12a4b7..f21cce2d40 100644 --- a/src/legacy/focusPreventScroll.js +++ b/src/legacy/focusPreventScroll.js @@ -33,7 +33,7 @@ if (HTMLElement.prototype.nativeFocus === undefined) { this.nativeFocus(); // Restore window scroll if preventScroll - if (options && options.preventScroll) { + if (options?.preventScroll) { window.scroll(scrollX, scrollY); } }; diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index 539682a076..ef6dd1e04f 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -131,7 +131,7 @@ class NavDrawer { if (this.isPeeking) { this.onMenuTouchMove(e); } else { - if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) { + if ((getTouches(e)[0]?.clientX || 0) <= options.handleSize) { this.isPeeking = true; if (e.type === 'touchstart') { diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index abfa30cdf0..12edc468cc 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -175,11 +175,19 @@ const scrollerFactory = function (frame, options) { requiresReflow = false; // Reset global variables - frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight; + const frameStyle = window.getComputedStyle(frame); + if (o.horizontal) { + frameSize = frame.clientWidth; + frameSize -= parseFloat(frameStyle.paddingLeft) + parseFloat(frameStyle.paddingRight); + } else { + frameSize = frame.clientHeight; + frameSize -= parseFloat(frameStyle.paddingTop) + parseFloat(frameStyle.paddingBottom); + } + frameSize = Math.round(frameSize); slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']); - // Set position limits & relativess + // Set position limits & relatives self._pos.end = Math.max(slideeSize - frameSize, 0); if (globalize.getIsRTL()) self._pos.end *= -1; @@ -904,6 +912,7 @@ scrollerFactory.prototype.toCenter = function (item, immediate) { }; scrollerFactory.create = function (frame, options) { + // eslint-disable-next-line new-cap const instance = new scrollerFactory(frame, options); return Promise.resolve(instance); }; diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index 8924d06951..6418d84e6b 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -384,7 +384,7 @@ export class BookPlayer { } canPlayItem(item) { - return item.Path && item.Path.endsWith('epub'); + return item.Path?.endsWith('epub'); } } diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index deb479c4b4..569e347cd2 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -2,7 +2,7 @@ import appSettings from '../../scripts/settings/appSettings'; import * as userSettings from '../../scripts/settings/userSettings'; import { playbackManager } from '../../components/playback/playbackmanager'; import globalize from '../../scripts/globalize'; -import castSenderApiLoader from './castSenderApi'; +import CastSenderApi from './castSenderApi'; import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; import { PluginType } from '../../types/plugin.ts'; @@ -103,7 +103,7 @@ class CastPlayer { return; } - if (!chrome.cast || !chrome.cast.isAvailable) { + if (!chrome.cast?.isAvailable) { setTimeout(this.initializeCastPlayer.bind(this), 1000); return; } @@ -322,14 +322,14 @@ class CastPlayer { const session = player.session; - if (session && session.receiver && session.receiver.friendlyName) { + if (session?.receiver?.friendlyName) { receiverName = session.receiver.friendlyName; } let apiClient; - if (message.options && message.options.ServerId) { + if (message.options?.ServerId) { apiClient = ServerConnections.getApiClient(message.options.ServerId); - } else if (message.options && message.options.items && message.options.items.length) { + } else if (message.options?.items?.length) { apiClient = ServerConnections.getApiClient(message.options.items[0].ServerId); } else { apiClient = ServerConnections.currentApiClient(); @@ -350,7 +350,7 @@ class CastPlayer { message.maxBitrate = bitrateSetting; } - if (message.options && message.options.items) { + if (message.options?.items) { message.subtitleAppearance = userSettings.getSubtitleAppearanceSettings(); message.subtitleBurnIn = appSettings.get('subtitleburnin') || ''; } @@ -451,10 +451,10 @@ function onVolumeDownKeyDown() { } function normalizeImages(state) { - if (state && state.NowPlayingItem) { + if (state?.NowPlayingItem) { const item = state.NowPlayingItem; - if ((!item.ImageTags || !item.ImageTags.Primary) && item.PrimaryImageTag) { + if ((!item.ImageTags?.Primary) && item.PrimaryImageTag) { item.ImageTags = item.ImageTags || {}; item.ImageTags.Primary = item.PrimaryImageTag; } @@ -576,7 +576,7 @@ class ChromecastPlayer { this.isLocalPlayer = false; this.lastPlayerData = {}; - new castSenderApiLoader().load().then(initializeChromecast.bind(this)); + new CastSenderApi().load().then(initializeChromecast.bind(this)); } tryPair() { @@ -599,7 +599,7 @@ class ChromecastPlayer { getTargets() { const targets = []; - if (this._castPlayer && this._castPlayer.hasReceivers) { + if (this._castPlayer?.hasReceivers) { targets.push(this.getCurrentTargetInfo()); } @@ -612,7 +612,7 @@ class ChromecastPlayer { const castPlayer = this._castPlayer; - if (castPlayer.session && castPlayer.session.receiver && castPlayer.session.receiver.friendlyName) { + if (castPlayer.session?.receiver?.friendlyName) { appName = castPlayer.session.receiver.friendlyName; } diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 41aab79511..2742e8d8cc 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -417,7 +417,7 @@ class HtmlAudioPlayer { const mediaElement = this._mediaElement; if (mediaElement) { const seekable = mediaElement.seekable; - if (seekable && seekable.length) { + if (seekable?.length) { let start = seekable.start(0); let end = seekable.end(0); diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index e0a30cb6b8..c6d9eeb68b 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -1,3 +1,5 @@ +import DOMPurify from 'dompurify'; + import browser from '../../scripts/browser'; import { appHost } from '../../components/apphost'; import loading from '../../components/loading/loading'; @@ -434,6 +436,7 @@ export class HtmlVideoPlayer { const includeCorsCredentials = await getIncludeCorsCredentials(); const hls = new Hls({ + startPosition: options.playerStartPositionTicks / 10000000, manifestLoadingTimeOut: 20000, maxBufferLength: maxBufferLength, xhrSetup(xhr) { @@ -1008,7 +1011,7 @@ export class HtmlVideoPlayer { } if (elem.videoWidth === 0 && elem.videoHeight === 0) { - const mediaSource = (this._currentPlayOptions || {}).mediaSource; + const mediaSource = this._currentPlayOptions?.mediaSource; // Only trigger this if there is media info // Avoid triggering in situations where it might not actually have a video stream (audio only live tv channel) @@ -1269,13 +1272,13 @@ export class HtmlVideoPlayer { availableFonts: { 'liberation sans': `${appRouter.baseUrl()}/default.woff2` }, // Disabled eslint compat, but is safe as corejs3 polyfills URL // eslint-disable-next-line compat/compat - workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url), + workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url).href, // eslint-disable-next-line compat/compat - wasmUrl: new URL('jassub/dist/jassub-worker.wasm', import.meta.url), + wasmUrl: new URL('jassub/dist/jassub-worker.wasm', import.meta.url).href, // eslint-disable-next-line compat/compat - legacyWasmUrl: new URL('jassub/dist/jassub-worker.wasm.js', import.meta.url), + legacyWasmUrl: new URL('jassub/dist/jassub-worker.wasm.js', import.meta.url).href, // eslint-disable-next-line compat/compat - modernWasmUrl : new URL('jassub/dist/jassub-worker-modern.wasm', import.meta.url), + modernWasmUrl : new URL('jassub/dist/jassub-worker-modern.wasm', import.meta.url).href, timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000, // new jassub options; override all, even defaults blendMode: 'js', @@ -1488,8 +1491,8 @@ export class HtmlVideoPlayer { // add some cues to show the text // in safari, the cues need to be added before setting the track mode to showing for (const trackEvent of data.TrackEvents) { - const trackCueObject = window.VTTCue || window.TextTrackCue; - const cue = new trackCueObject(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false)); + const TrackCue = window.VTTCue || window.TextTrackCue; + const cue = new TrackCue(trackEvent.StartPositionTicks / 10000000, trackEvent.EndPositionTicks / 10000000, normalizeTrackEventText(trackEvent.Text, false)); if (cue.line === 'auto') { cue.line = cueLine; @@ -1534,8 +1537,9 @@ export class HtmlVideoPlayer { } } - if (selectedTrackEvent && selectedTrackEvent.Text) { - subtitleTextElement.innerHTML = normalizeTrackEventText(selectedTrackEvent.Text, true); + if (selectedTrackEvent?.Text) { + subtitleTextElement.innerHTML = DOMPurify.sanitize( + normalizeTrackEventText(selectedTrackEvent.Text, true)); subtitleTextElement.classList.remove('hide'); } else { subtitleTextElement.classList.add('hide'); @@ -1809,7 +1813,7 @@ export class HtmlVideoPlayer { Windows.UI.ViewManagement.ApplicationView.getForCurrentView().tryEnterViewModeAsync(Windows.UI.ViewManagement.ApplicationViewMode.default); } } else { - if (video && video.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { + if (video?.webkitSupportsPresentationMode && typeof video.webkitSetPresentationMode === 'function') { video.webkitSetPresentationMode(isEnabled ? 'picture-in-picture' : 'inline'); } } @@ -1888,7 +1892,7 @@ export class HtmlVideoPlayer { const mediaElement = this.#mediaElement; if (mediaElement) { const seekable = mediaElement.seekable; - if (seekable && seekable.length) { + if (seekable?.length) { let start = seekable.start(0); let end = seekable.end(0); diff --git a/src/plugins/logoScreensaver/plugin.js b/src/plugins/logoScreensaver/plugin.js index fc999ccccf..9cd0e1a445 100644 --- a/src/plugins/logoScreensaver/plugin.js +++ b/src/plugins/logoScreensaver/plugin.js @@ -25,7 +25,7 @@ export default function () { const elem = document.querySelector('.logoScreenSaverImage'); - if (elem && elem.animate) { + if (elem?.animate) { const random = randomInt(0, animations.length - 1); animations[random](elem, 1); diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js index 00625c2776..6a14a4a684 100644 --- a/src/plugins/pdfPlayer/plugin.js +++ b/src/plugins/pdfPlayer/plugin.js @@ -312,7 +312,7 @@ export class PdfPlayer { } canPlayItem(item) { - return item.Path && item.Path.endsWith('pdf'); + return item.Path?.endsWith('pdf'); } } diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js index 98e0e843ab..6feef63575 100644 --- a/src/plugins/sessionPlayer/plugin.js +++ b/src/plugins/sessionPlayer/plugin.js @@ -157,7 +157,7 @@ function subscribeToPlayerUpdates(instance) { } function normalizeImages(state, apiClient) { - if (state && state.NowPlayingItem) { + if (state?.NowPlayingItem) { const item = state.NowPlayingItem; if (!item.ImageTags || !item.ImageTags.Primary && item.PrimaryImageTag) { diff --git a/src/plugins/syncPlay/core/Manager.js b/src/plugins/syncPlay/core/Manager.js index ee1e3e3ae7..9f25f44608 100644 --- a/src/plugins/syncPlay/core/Manager.js +++ b/src/plugins/syncPlay/core/Manager.js @@ -246,7 +246,7 @@ class Manager { /** * Handles a playback command from the server. - * @param {Object} cmd The playback command. + * @param {Object|null} cmd The playback command. */ processCommand(cmd) { if (cmd === null) return; @@ -294,7 +294,7 @@ class Manager { /** * Handles a group state change. - * @param {Object} update The group state update. + * @param {Object|null} update The group state update. */ processStateChange(update) { if (update === null || update.State === null || update.Reason === null) return; diff --git a/src/scripts/autocast.js b/src/scripts/autocast.js index 854403b4da..9ed7de5df7 100644 --- a/src/scripts/autocast.js +++ b/src/scripts/autocast.js @@ -12,7 +12,7 @@ export function enable(enabled) { if (enabled) { const currentPlayerInfo = playbackManager.getPlayerInfo(); - if (currentPlayerInfo && currentPlayerInfo.id) { + if (currentPlayerInfo?.id) { localStorage.setItem('autocastPlayerId', currentPlayerInfo.id); } } else { diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index f99e0ab343..98be70a9b1 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -3,7 +3,7 @@ import * as userSettings from './settings/userSettings'; import browser from './browser'; function canPlayH264(videoTestElement) { - return !!(videoTestElement.canPlayType && videoTestElement.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '')); + return !!(videoTestElement.canPlayType?.('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '')); } function canPlayHevc(videoTestElement, options) { @@ -121,7 +121,7 @@ function canPlayAudioFormat(format) { typeString = 'audio/ogg; codecs="opus"'; } else if (format === 'alac') { - if (browser.iOS || browser.osx) { + if (browser.iOS || browser.osx && browser.safari) { return true; } } else if (format === 'mp2') { diff --git a/src/scripts/dom.js b/src/scripts/dom.js index 9488c7e16d..cd0fd71849 100644 --- a/src/scripts/dom.js +++ b/src/scripts/dom.js @@ -15,7 +15,7 @@ export function parentWithAttribute(elem, name, value) { while ((value ? elem.getAttribute(name) !== value : !elem.getAttribute(name))) { elem = elem.parentNode; - if (!elem || !elem.getAttribute) { + if (!elem?.getAttribute) { return null; } } @@ -65,7 +65,7 @@ function containsAnyClass(classList, classNames) { * Returns parent of element with one of specified class names. * @param {HTMLElement} elem - Element whose parent need to find. * @param {(string|Array)} classNames - Class name or array of class names. - * @returns {HTMLElement} Parent with one of specified class names. + * @returns {HTMLElement|null} Parent with one of specified class names. */ export function parentWithClass(elem, classNames) { // accept both string and array passed in diff --git a/src/scripts/gamepadtokey.js b/src/scripts/gamepadtokey.js index 847ef02a52..cfd6e3790c 100644 --- a/src/scripts/gamepadtokey.js +++ b/src/scripts/gamepadtokey.js @@ -349,7 +349,7 @@ function isGamepadConnected() { const gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ for (let i = 0, len = gamepads.length; i < len; i++) { const gamepad = gamepads[i]; - if (gamepad && gamepad.connected) { + if (gamepad?.connected) { return true; } } diff --git a/src/scripts/globalize.js b/src/scripts/globalize.js index 256207acec..21260e2be4 100644 --- a/src/scripts/globalize.js +++ b/src/scripts/globalize.js @@ -37,7 +37,7 @@ function getDefaultLanguage() { if (navigator.userLanguage) { return navigator.userLanguage; } - if (navigator.languages && navigator.languages.length) { + if (navigator.languages?.length) { return navigator.languages[0]; } @@ -217,12 +217,12 @@ function translateKey(key) { function translateKeyFromModule(key, module) { let dictionary = getDictionary(module, getCurrentLocale()); - if (dictionary && dictionary[key]) { + if (dictionary?.[key]) { return dictionary[key]; } dictionary = getDictionary(module, fallbackCulture); - if (dictionary && dictionary[key]) { + if (dictionary?.[key]) { return dictionary[key]; } diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index ae59829aa5..b77dfa92e1 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -71,7 +71,7 @@ function renderHeader() { } function getCurrentApiClient() { - if (currentUser && currentUser.localUser) { + if (currentUser?.localUser) { return ServerConnections.getApiClient(currentUser.localUser.ServerId); } @@ -127,7 +127,7 @@ function updateUserInHeader(user) { let hasImage; - if (user && user.name) { + if (user?.name) { if (user.imageUrl) { const url = user.imageUrl; updateHeaderUserButton(url); @@ -143,7 +143,7 @@ function updateUserInHeader(user) { updateHeaderUserButton(null); } - if (user && user.localUser) { + if (user?.localUser) { if (headerHomeButton) { headerHomeButton.classList.remove('hide'); } @@ -322,7 +322,7 @@ function refreshLibraryInfoInDrawer(user) { // libraries are added here html += '
'; - if (user.localUser && user.localUser.Policy.IsAdministrator) { + if (user.localUser?.Policy.IsAdministrator) { html += '
'; html += '

'; html += globalize.translate('HeaderAdmin'); @@ -466,7 +466,7 @@ function createToolsMenuList(pluginItems) { }); links.push({ name: globalize.translate('HeaderActivity'), - href: '#/serveractivity.html', + href: '#/dashboard/activity', pageIds: ['serverActivityPage'], icon: 'assessment' }); diff --git a/src/scripts/playlists.js b/src/scripts/playlists.js index eb3fac9ece..aba0c389ae 100644 --- a/src/scripts/playlists.js +++ b/src/scripts/playlists.js @@ -189,9 +189,9 @@ export default function (view) { reloadItems(); }); view.querySelector('.btnNewPlaylist').addEventListener('click', function () { - import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + import('../components/playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => { const serverId = ApiClient.serverInfo().Id; - new playlistEditor({ + new PlaylistEditor({ items: [], serverId: serverId }); diff --git a/src/scripts/screensavermanager.js b/src/scripts/screensavermanager.js index c63d530685..a187da6ae2 100644 --- a/src/scripts/screensavermanager.js +++ b/src/scripts/screensavermanager.js @@ -94,7 +94,7 @@ function ScreenSaverManager() { let isLoggedIn; const apiClient = ServerConnections.currentApiClient(); - if (apiClient && apiClient.isLoggedIn()) { + if (apiClient?.isLoggedIn()) { isLoggedIn = true; } diff --git a/src/strings/ar.json b/src/strings/ar.json index 7914472ee0..e0006ed9b8 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -121,7 +121,7 @@ "HeaderConnectionFailure": "فشل في الاتصال", "HeaderContainerProfile": "عريضة الحاوية", "HeaderContainerProfileHelp": "تشير ملفات تعريف الحاوية إلى قيود الجهاز عند تشغيل تنسيقات معينة. إذا تم تطبيق قيود ، فسيتم تحويل ترميز الوسائط ، حتى إذا تم تكوين التنسيق للتشغيل المباشر.", - "HeaderContinueWatching": "استئناف المشاهدة", + "HeaderContinueWatching": "أستئناف المشاهدة", "HeaderCustomDlnaProfiles": "الحسابات المخصوصة", "HeaderDateIssued": "تاريخ الإصدار", "HeaderDefaultRecordingSettings": "إعدادات التسجيل الافتراضية", @@ -803,7 +803,7 @@ "ConfirmDeleteItems": "حذف هذه العناصر سوف يحذفها من نظام الملفات ومن مكتبة الوسائط. هل ترغب حقاً فى الاستمرار؟", "EveryNDays": "كل {0} يوم", "ConfirmDeleteItem": "حذف هذا العنصر سوف يحذفه من نظام الملفات ومن مكتبة الوسائط. هل ترغب حقاً فى الاستمرار؟", - "DropShadow": "إضافة ظل خلفي‏", + "DropShadow": "ظل خلفي‏", "LabelDropShadow": "اسقاط الظل", "EditSubtitles": "تعديل الترجمات", "EditMetadata": "تعديل البيانات التعريفية", @@ -886,7 +886,7 @@ "ButtonTogglePlaylist": "قائمة التشغيل", "BoxSet": "طقم", "ButtonSplit": "تقسيم", - "AllowFfmpegThrottlingHelp": "عند تفعيلها؛ فسوف تتوقف عملية الترميز transcoding توقفا مؤقتا كلما تقدمت العملية عن موضع التشغيل بنسبة كافية، تهدف هذه الخاصية إلى التقليل من استهلاك الطاقة، وتكون ذات منفعة كبيرة عندما تتم عملية المشاهدة بانتظام دون القفز عدة دقائق في المشاهدة ما بين الحينة والأخرى. كما ينطبق الأمر ذاته على عملية نسخ الملف إلى حاوية أخرى لتتوافق مع الجهاز remuxing.", + "AllowFfmpegThrottlingHelp": "عندما يتقدم تحويل الشفرة (transcode) أو remux بدرجة كافية من موضع التشغيل الحالي، تتوقف العملية مؤقتا حتى تستهلك موارد أقل. هذا مفيد للغاية عند المشاهدة دون القفز كثيرا. قم بإقاف تشغيل هذه الخاصية أذا واجهت مشاكل في تشغيل الفديو.", "InstallingPackage": "تثبيت {0} (الإصدار {1})", "Images": "الصور", "Identify": "التعرف على الوسائط", @@ -1694,5 +1694,6 @@ "LabelDummyChapterCount": "الحد", "LabelDummyChapterCountHelp": "العدد الأقصى لصور الفصول التي سيتم استئصالها من كل ملف للوسائط.", "LabelChapterImageResolution": "الدقة", - "LabelEnableAudioVbr": "فعل تشفير VBR الصوتي" + "LabelEnableAudioVbr": "فعل تشفير VBR الصوتي", + "AllowCollectionManagement": "السماح لهذا المستخدم بإدارة المجموعات" } diff --git a/src/strings/be-by.json b/src/strings/be-by.json index 5e8b80f924..3d5c913b1a 100644 --- a/src/strings/be-by.json +++ b/src/strings/be-by.json @@ -1684,11 +1684,11 @@ "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Extras часта маюць такое ж убудаванае імя, што і бацькоўскі, пазначце гэта, каб у любым выпадку выкарыстоўваць для іх убудаваныя загалоўкі.", "HeaderDummyChapter": "Выявы раздзела", "LabelDummyChapterDuration": "Інтэрвал", - "LabelDummyChapterDurationHelp": "Інтэрвал вымання выявы главы ў секундах.", + "LabelDummyChapterDurationHelp": "Інтэрвал паміж малюнкамі раздзелаў. Усталюйце 0, каб адключыць стварэнне вобразаў раздзелаў. Змена гэтай опцыі не паўплывае на існуючыя выявы раздзелаў.", "LabelDummyChapterCount": "Ліміт", "LabelDummyChapterCountHelp": "Максімальная колькасць выяваў раздзелаў, якія будуць выняты для кожнага медыяфайла.", "LabelChapterImageResolution": "Дазвол", - "LabelChapterImageResolutionHelp": "Дазвол вынятых малюнкаў раздзелаў.", + "LabelChapterImageResolutionHelp": "Разрозненне створаных малюнкаў раздзелаў. Змена гэтай опцыі не паўплывае на існуючыя выявы раздзелаў.", "ResolutionMatchSource": "Супадзенне з крыніцай", "SecondarySubtitles": "Дадатковыя субтытры", "SubtitleBlack": "Чорны", @@ -1719,5 +1719,10 @@ "MenuOpen": "Адкрыць меню", "MenuClose": "Зачыніць меню", "AllowCollectionManagement": "Дазволіць гэтаму карыстальніку кіраваць калекцыямі", - "UserMenu": "Меню карыстальніка" + "UserMenu": "Меню карыстальніка", + "PasswordRequiredForAdmin": "Для ўліковых запісаў адміністратара патрабуецца пароль.", + "GetThePlugin": "Атрымаць плагін", + "LabelSyncPlayNoGroups": "Няма даступных груп", + "Notifications": "Апавяшчэнні", + "NotificationsMovedMessage": "Функцыянальнасць апавяшчэнняў перанесена ў плагін Webhook." } diff --git a/src/strings/bn_BD.json b/src/strings/bn_BD.json index baf0647d83..440552ec77 100644 --- a/src/strings/bn_BD.json +++ b/src/strings/bn_BD.json @@ -13,7 +13,7 @@ "Album": "অ্যালবাম", "AirDate": "উন্মুক্তের তারিখ", "AdditionalNotificationServices": "আরো নোটিফিকেশন সার্ভিস ইনস্টল করতে প্লাগিন ক্যাটালগে ব্রাউস করুন।", - "AddedOnValue": "এডেড {০}", + "AddedOnValue": "এডেড {0}", "AddToPlaylist": "প্লেলিস্টে অ্যাড করুন", "AddToPlayQueue": "প্লে কিউ তে অ্যাড করুন", "AddToCollection": "কালেকশন এ অ্যাড করুন", @@ -62,7 +62,7 @@ "Artists": "শিল্পীবৃন্দ", "Artist": "শিল্পী", "Art": "ক্লিয়ার-আর্ট", - "AroundTime": "কাছাকাছি", + "AroundTime": "কাছাকাছি {0}", "Anytime": "যে কোনো সময়", "AnyLanguage": "যেকোনো ভাষা", "AlwaysPlaySubtitlesHelp": "ভাষার পছন্দের সাথে মিলিয়ে সাবটাইটেলগুলি লোড করা হবে।", @@ -142,7 +142,7 @@ "AllowOnTheFlySubtitleExtractionHelp": "এমবেডেড সাবটাইটেল ভিডিও থেকে আলাদা করে প্লেইন টেক্সটে ক্লায়েন্টদের কাছে বিতরণ করা যেতে পারে যাতে ভিডিও ট্রান্সকোডিং এড়ানো যায়। কিন্তু কিছু সিস্টেমে এটি দীর্ঘ সময় নিতে পারে এবং এই প্রক্রিয়াটি চলাকালী্ন, ভিডিও প্লেব্যাক থেমে থাকতে পারে। ক্লায়েন্ট ডিভাইস দ্বারা সমর্থিত না হলে ভিডিও ট্রান্সকোডিংয়ের সাথে এম্বেড করা সাবটাইটেলগুলিকে বার্ন করার জন্য অপশনটি ডিসেবল করুন.", "AllowMediaConversionHelp": "কনভার্ট মিডিয়া ফিচারটির জন্য অনুমোদন দিন বা বাতিল করুন।", "AllowFfmpegThrottlingHelp": "যখন একটি ট্রান্সকোড বা রিমুক্স বর্তমান প্লেব্যাক অবস্থান থেকে যথেষ্ট এগিয়ে যায়, তখন প্রক্রিয়াটি থামানো যাতে এটি কম প্রসেস পাওয়ার গ্রহণ করে। প্রায়ই না টেনে (Seek) দেখার সময় এটি সবচেয়ে কার্যকর। আপনি যদি প্লেব্যাক সমস্যা অনুভব করেন তবে এটি বন্ধ করুন।", - "AgeValue": "({০}) বছর পুরান", + "AgeValue": "({0} বছর পুরান)", "AddToFavorites": "পছন্দের তালিকায় যোগ করুন", "ErrorDeletingItem": "সার্ভার থেকে আইটেমটি মুছে ফেলার সময় একটি ত্রুটি ছিল৷ অনুগ্রহ করে পরীক্ষা করুন যে জেলিফিন মিডিয়া ফোল্ডারে লেখার অ্যাক্সেস পেয়েছে এবং আবার চেষ্টা করুন৷", "ErrorAddingXmlTvFile": "XMLTV ফাইল অ্যাক্সেস করার সময় একটি ত্রুটি ছিল৷ অনুগ্রহ করে নিশ্চিত করুন যে ফাইলটি বিদ্যমান এবং আবার চেষ্টা করুন৷", @@ -152,7 +152,7 @@ "Episodes": "পর্বগুলি", "Episode": "পর্ব", "Engineer": "শব্দ প্রকৌশলী", - "EndsAtValue": "শেষ হয় {০}", + "EndsAtValue": "শেষ হয় {0}", "Ended": "শেষ হয়েছে", "EncoderPresetHelp": "পারফরমেন্স উন্নত করতে একটি দ্রুত মান অথবা কোয়ালিটি উন্নত করতে একটি ধীর মান চয়ন করুন৷", "EnableTonemapping": "টোন ম্যাপিং চালু করুন", @@ -229,11 +229,11 @@ "DeleteDevicesConfirmation": "আপনি কি নিশ্চিত যে আপনি সমস্ত ডিভাইস মুছে ফেলতে চান? অন্য সব সেশন লগ আউট করা হবে. পরের বার ব্যবহারকারী সাইন ইন করলে ডিভাইসগুলি আবার প্রদর্শিত হবে৷", "DeleteAll": "সব মুছুন", "Data": "তথ্য", - "DashboardVersionNumber": "ভার্সনঃ {০}", - "DashboardServerName": "সার্ভারঃ {০}", + "DashboardVersionNumber": "ভার্সনঃ {0}", + "DashboardServerName": "সার্ভারঃ {0}", "DashboardOperatingSystem": "অপারেটিং সিস্টেমঃ {০}", "DashboardArchitecture": "আর্কিটেকচারঃ {০}", - "DailyAt": "প্রতিদিন {০}", + "DailyAt": "প্রতিদিন {0}", "Cursive": "কার্সিভ", "CopyStreamURLSuccess": "URL সফলভাবে কপি করা হয়েছে৷", "CopyFailed": "কপি করতে পারিনি", @@ -251,7 +251,7 @@ "CinemaModeConfigurationHelp": "সিনেমা মোড মূল প্লে-ব্লেকের আগে ট্রেলার এবং কাস্টম ইন্ট্রো প্লে করার ক্ষমতা সহ সরাসরি আপনার বসার ঘরে থিয়েটারের অভিজ্ঞতা নিয়ে আসে।", "Channels": "চ্যানেলগুলো", "ChannelNumber": "চ্যানেল নাম্বার", - "ChannelNameOnly": "চ্যানেল {০} শুধু", + "ChannelNameOnly": "চ্যানেল {0} শুধু", "ChannelAccessHelp": "এই ব্যবহারকারীর সাথে শেয়ার করার জন্য চ্যানেল নির্বাচন করুন। অ্যাডমিনিস্ট্রেটর মেটাডেটা ম্যানেজার ব্যবহার করে সমস্ত চ্যানেল সম্পাদনা করতে সক্ষম হবেন।", "HeaderCancelRecording": "রেকর্ডিং বাতিল করুন", "HeaderBranding": "ব্র্যান্ডিং", @@ -294,7 +294,7 @@ "Fullscreen": "ফুল-স্ক্রিন", "Friday": "শুক্রবার", "Framerate": "ফ্রেমরেট", - "FormatValue": "ফরমেটঃ {০}", + "FormatValue": "ফরমেটঃ {0}", "Folders": "ফোল্ডার", "Filters": "ফিল্টারগুলি", "Filter": "ফিল্টার", diff --git a/src/strings/ch.json b/src/strings/ch.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/src/strings/ch.json @@ -0,0 +1 @@ +{} diff --git a/src/strings/cs.json b/src/strings/cs.json index fb896cb303..423fbbe2fa 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -692,11 +692,11 @@ "MessageNoPluginsInstalled": "Nemáte instalovány žádné zásuvné moduly.", "MessageNoTrailersFound": "Chcete-li si zlepšit zážitek ze sledování, nainstalujte si kanál s upoutávkami.", "MessageNothingHere": "Tady nic není.", - "MessagePasswordResetForUsers": "Následujícím uživatelům bylo resetováno heslo. Nyní se mohou přihlásit pomocí kódů Easy PIN, které byly použity k provedení resetu.", + "MessagePasswordResetForUsers": "Následujícím uživatelům bylo resetováno heslo. Nyní se mohou přihlásit pomocí kódu PIN, který byl použit k provedení resetu.", "MessagePlayAccessRestricted": "Přehrávání tohoto obsahu je aktuálně omezeno. Další informace získáte od správce serveru.", "MessagePleaseEnsureInternetMetadata": "Prosím zkontrolujte, zda máte povoleno stahování metadat z internetu.", "MessagePluginConfigurationRequiresLocalAccess": "Pro konfiguraci zásuvného modulu se přihlaste přímo na lokální server.", - "MessagePluginInstallDisclaimer": "Zásuvné moduly vytvořené členy komunity jsou skvělým způsobem, jak si zlepšit prožitek pomocí dalších funkcí. Před instalací se prosím seznamte se všemi dopady, které mohou doplňky na server mít, např.: pomalejší skenování knihovny, delší zpracování na pozadí nebo snížená stabilita systému.", + "MessagePluginInstallDisclaimer": "UPOZORNĚNÍ: Instalace zásuvného modulu třetí strany má určitá rizika. Může obsahovat nestabilní nebo zákeřný kód, a může se kdykoliv změnit. Instalujte zásuvné moduly jen těch autorů, kterým důvěřujete, a mějte na vědomi, jaké potenciální dopady to může mít, včetně kontaktování externích služeb, delšího skenování knihovny, nebo dalších procesů na pozadí.", "MessageReenableUser": "Viz níže pro znovuzapnutí", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Z vaší knihovny budou odstraněny následující zdroje médií", "MessageUnableToConnectToServer": "Nejsme schopni se připojit k vybranému serveru právě teď. Prosím, ujistěte se, že je spuštěn a zkuste to znovu.", @@ -1737,5 +1737,29 @@ "GetThePlugin": "Získat zásuvný modul", "Notifications": "Oznámení", "NotificationsMovedMessage": "Funkce oznámení se přesunula do zásuvného modulu Webhook.", - "PasswordRequiredForAdmin": "Administrátorské účty musejí mít nastaveno heslo." + "PasswordRequiredForAdmin": "Administrátorské účty musejí mít nastaveno heslo.", + "PleaseConfirmRepositoryInstallation": "Kliknutím na OK potvrďte, ze jste si přečetli text uvedený výše, a že chcete pokračovat v instalaci repozitáře zásuvného modulu.", + "Unknown": "Neznámý", + "LabelSyncPlayNoGroups": "Žádné skupiny nejsou k dispozici", + "HeaderConfirmRepositoryInstallation": "Potvrdit instalaci repozitáře zásuvných modulů", + "LabelDeveloper": "Vývojář", + "MessageRepositoryInstallDisclaimer": "UPOZORNĚNÍ: Instalace zásuvného modulu třetí strany má určitá rizika. Může obsahovat nestabilní nebo zákeřný kód, a může se kdykoliv změnit. Instalujte zásuvné moduly jen těch autorů, kterým důvěřujete.", + "LabelDate": "Datum", + "LabelLevel": "Úroveň", + "LabelMediaDetails": "Podrobnosti i médii", + "LabelSystem": "Systém", + "LogLevel.Trace": "Stopa", + "LogLevel.Debug": "Lazení", + "LogLevel.Information": "Informace", + "LogLevel.Warning": "Varování", + "LogLevel.Error": "Chyba", + "LogLevel.Critical": "Kritická", + "LogLevel.None": "Žádný", + "HeaderEpisodesStatus": "Stav epizod", + "AllowSegmentDeletion": "Odstranit části", + "AllowSegmentDeletionHelp": "Odstranit staré překódované části souboru po poslání klientovi. Díky tomu není nutné skladovat celý překódovaný soubor na disku. Funguje pouze při zapnuté funkci Omezit překódování. V případě problémů s přehráváním vypněte.", + "LabelThrottleDelaySeconds": "Omezit po", + "LabelThrottleDelaySecondsHelp": "Čas v sekundách po kterém bude překódování pozastaveno. Musí být dost velké na to, aby měl klient dostatečné množství přehrávaného souboru v záloze. Funguje pouze se zapnutou funkcí Omezit překódování.", + "LabelSegmentKeepSeconds": "Doba ponechání částí", + "LabelSegmentKeepSecondsHelp": "Čas v sekundách, po který budou části překódovaného souboru uloženy. Musí být delší než čas určený v \"Omezit po\". Funguje pouze při zapnuté funkci Odstranění částí." } diff --git a/src/strings/cy.json b/src/strings/cy.json index 61954c975b..70c18334c9 100644 --- a/src/strings/cy.json +++ b/src/strings/cy.json @@ -617,7 +617,7 @@ "TabParentalControl": "Rheolaeth Rhieni", "DeletedScene": "Golygfa wedi'i dileu", "ContainerNotSupported": "Nid yw'r cynhwysydd yn cael ei gefnogi", - "EnableIntelLowPowerH264HwEncoder": "Galluogi amgodiwr caledwedd Intel Low-Power H.264.", + "EnableIntelLowPowerH264HwEncoder": "Galluogi amgodiwr caledwedd Intel Low-Power H.264", "Sample": "Sampl", "BehindTheScenes": "Tu ôl i'r Llenni", "Clip": "Clip", diff --git a/src/strings/da.json b/src/strings/da.json index 9004249c0d..b6a39c19e5 100644 --- a/src/strings/da.json +++ b/src/strings/da.json @@ -20,7 +20,7 @@ "AllowRemoteAccessHelp": "Hvis ikke markeret, vil alle forbindelser udefra blive blokeret.", "AllowedRemoteAddressesHelp": "Komma separeret liste over IP-adresser eller IP/rundsendings-adresser til netværk som har tilladelse til at forbinde udefra. Hvis dette felt er tomt, er alle eksterne adresser tilladt.", "Anytime": "Altid", - "AroundTime": "Cirka [0]", + "AroundTime": "Cirka {0}", "AsManyAsPossible": "Så mange som muligt", "AspectRatio": "Billedformat", "Audio": "Lyd", diff --git a/src/strings/de.json b/src/strings/de.json index f0b8c994cd..6f4d185349 100644 --- a/src/strings/de.json +++ b/src/strings/de.json @@ -67,11 +67,11 @@ "ButtonOpen": "Öffnen", "ButtonParentalControl": "Kindersicherung", "ButtonPreviousTrack": "Vorheriges Lied", - "ButtonQuickStartGuide": "Schnellstart Anleitung", + "ButtonQuickStartGuide": "Schnellstartanleitung", "ButtonRefreshGuideData": "TV-Programmdaten aktualisieren", "ButtonRemove": "Entfernen", "ButtonRename": "Umbenennen", - "ButtonResetEasyPassword": "Easy PIN Code zurücksetzen", + "ButtonResetEasyPassword": "Easy PIN-Code zurücksetzen", "ButtonResume": "Fortsetzen", "ButtonRevoke": "Wiederrufen", "ButtonScanAllLibraries": "Alle Bibliotheken scannen", @@ -94,7 +94,7 @@ "CinemaModeConfigurationHelp": "Der Kinomodus bringt mit der Fähigkeit, Trailer und benutzerdefinierte Intros vor dem Hauptfilm abzuspielen, das Kinoerlebnis direkt in dein Wohnzimmer.", "Collections": "Sammlungen", "ColorSpace": "Farbraum", - "CommunityRating": "Community Bewertung", + "CommunityRating": "Communitybewertung", "Composer": "Komponist", "ConfigureDateAdded": "Legt fest, wie das Feld 'Hinzugefügt am' in NFOs interpretiert werden soll", "ConfirmDeleteImage": "Bild löschen?", @@ -105,7 +105,7 @@ "Connect": "Verbinden", "ContinueWatching": "Weiterschauen", "Continuing": "Fortlaufend", - "CriticRating": "Kritiker Bewertung", + "CriticRating": "Kritikerbewertung", "CustomDlnaProfilesHelp": "Erstelle ein benutzerdefiniertes Profil für ein neues Zielgerät, oder um ein vorhandenes Systemprofil zu überschreiben.", "DateAdded": "Hinzugefügt", "DatePlayed": "Abgespielt", @@ -197,7 +197,7 @@ "GroupBySeries": "Nach Serien gruppieren", "GroupVersions": "Versionen gruppieren", "GuestStar": "Gaststar", - "Guide": "TV Guide", + "Guide": "Fernsehprogramm", "GuideProviderLogin": "Anmelden", "GuideProviderSelectListings": "Listen wählen", "H264CrfHelp": "Der Constant Rate Factor (CRF) bezeichnet die Einstellung für die Standardqualität des x264 und x265 Encoders. Setze einen Wert zwischen 0 und 51. Ein niedriger Wert resultiert in besserer Qualität (auf Kosten einer größeren Datei). Gängige Werte sind 18-28. Der Standard für x264 ist 23 und x265 ist 28, diese sollten als Referenzen verwendet werden.", @@ -243,7 +243,7 @@ "HeaderContinueWatching": "Weiterschauen", "HeaderCustomDlnaProfiles": "Benutzerdefinierte Profile", "HeaderDateIssued": "Erstellungsdatum", - "HeaderDefaultRecordingSettings": "Standard Aufnahmeeinstellungen", + "HeaderDefaultRecordingSettings": "Standardaufnahmeeinstellungen", "HeaderDeleteDevice": "Gerät löschen", "HeaderDeleteItem": "Objekt löschen", "HeaderDeleteItems": "Objekte löschen", @@ -253,8 +253,8 @@ "HeaderDeveloperInfo": "Entwicklerinformationen", "HeaderDeviceAccess": "Gerätezugriff", "HeaderDevices": "Geräte", - "HeaderDirectPlayProfile": "Direktwiedergabe-Profil", - "HeaderDirectPlayProfileHelp": "Füge Direktwiedergabe-Profile hinzu, um die nativen Abspielmöglichkeiten von Geräten festzulegen.", + "HeaderDirectPlayProfile": "Direktwiedergabeprofil", + "HeaderDirectPlayProfileHelp": "Füge Direktwiedergabeprofile hinzu, um die nativen Abspielmöglichkeiten von Geräten festzulegen.", "HeaderDownloadSync": "Herunterladen & Synchronisieren", "HeaderEasyPinCode": "Einfacher Easy PIN Code", "HeaderEditImages": "Bilder bearbeiten", @@ -275,7 +275,7 @@ "HeaderImageOptions": "Bild-Einstellungen", "HeaderImageSettings": "Bild-Einstellungen", "HeaderInstall": "Installieren", - "HeaderInstantMix": "Sofortiger Mix", + "HeaderInstantMix": "Sofortmix", "HeaderKeepRecording": "Aufnahme behalten", "HeaderKeepSeries": "Serie behalten", "HeaderKodiMetadataHelp": "Um NFO-Metadaten zu aktivieren oder zu deaktivieren, bearbeite eine Bibliothek und finde den Abschnitt zum Speichern von Metadaten.", @@ -347,7 +347,7 @@ "HeaderSetupLibrary": "Medienbibliotheken einrichten", "HeaderSortBy": "Sortiert nach", "HeaderSortOrder": "Sortierung", - "HeaderSpecialEpisodeInfo": "Spezialepisoden Information", + "HeaderSpecialEpisodeInfo": "Information zur Spezialepisode", "HeaderStartNow": "Jetzt starten", "HeaderStopRecording": "Aufnahme stoppen", "HeaderSubtitleAppearance": "Untertiteldarstellung", @@ -429,31 +429,31 @@ "LabelCommunityRating": "Community Bewertung", "LabelContentType": "Typ des Inhalts", "LabelCountry": "Land", - "LabelCriticRating": "Kritiker Bewertung", + "LabelCriticRating": "Kritikerbewertung", "LabelCurrentPassword": "Aktuelles Passwort", - "LabelCustomCertificatePath": "Benutzerdefinierter SSL-Zertifikatspfad", + "LabelCustomCertificatePath": "Benutzerdefinierter Pfad zum SSL-Zertifikat", "LabelCustomCertificatePathHelp": "Pfad zu einer PKCS-#12-Datei, die ein Zertifikat und einen privaten Schlüssel enthält, um TLS-Unterstützung für eine eigene Domain zu aktivieren.", "LabelCustomCss": "Benutzerdefinierter CSS-Code", "LabelCustomCssHelp": "Wende deinen eigenen, benutzerdefinierten CSS-Code für Theming/Branding auf die Weboberfläche an.", "LabelCustomDeviceDisplayNameHelp": "Lege einen individuellen Anzeigenamen fest oder lasse das Feld leer, um den vom Gerät übermittelten Namen zu nutzen.", "LabelCustomRating": "Eigene Bewertung", - "LabelDashboardTheme": "Server Dashboard Theme", + "LabelDashboardTheme": "Server-Dashboard-Theme", "LabelDateAdded": "Hinzugefügt", "LabelDateAddedBehavior": "Verhalten des Hinzufügedatums bei neuen Inhalten", "LabelDateAddedBehaviorHelp": "Wenn ein Metadatenwert vorhanden ist, wird dieser immer gegenüber den anderen Optionen bevorzugt.", - "LabelDateTimeLocale": "Datum/Zeit lokal", + "LabelDateTimeLocale": "Spracheinstellung für Datums-/Zeitangaben", "LabelDay": "Wochentag", "LabelDeathDate": "Todesdatum", - "LabelDefaultScreen": "Standardscreen", + "LabelDefaultScreen": "Standardansicht", "LabelDefaultUser": "Standardbenutzer", "LabelDefaultUserHelp": "Legt fest, welche Benutzerbibliothek auf verbundenen Geräten angezeigt werden soll. Dies kann für jedes Gerät durch Profile überschrieben werden.", "LabelDeviceDescription": "Gerätebeschreibung", - "LabelDidlMode": "DIDL Modus", - "LabelDiscNumber": "Discnummer", + "LabelDidlMode": "DIDL-Modus", + "LabelDiscNumber": "Disknummer", "LabelDisplayLanguage": "Anzeigesprache", "LabelDisplayLanguageHelp": "Die Übersetzung von Jellyfin ist ein laufendes Projekt.", "LabelDisplayMode": "Bildschirmmodus", - "LabelDisplayName": "Anzeige Name", + "LabelDisplayName": "Anzeigename", "LabelDisplayOrder": "Anzeigereihenfolge", "LabelDisplaySpecialsWithinSeasons": "Sonderinhalt innerhalb der Staffel, in der er ausgestrahlt wurde, anzeigen", "LabelDownMixAudioScale": "Audioverstärkung beim Heruntermischen", @@ -461,7 +461,7 @@ "LabelDownloadLanguages": "Herunterzuladende Sprachen", "LabelDropImageHere": "Foto hierher ziehen oder klicken zum Durchsuchen.", "LabelDropShadow": "Schlagschatten", - "LabelEasyPinCode": "Einfacher PIN-Code", + "LabelEasyPinCode": "Easy PIN-Code", "LabelEmbedAlbumArtDidl": "Albumcover in DIDL einbetten", "LabelEmbedAlbumArtDidlHelp": "Einige Geräte bevorzugen diese Methode um Albumcover ermitteln zu können. Andere wiederum können evtl. nichts abspielen, wenn diese Funktion aktiviert ist.", "LabelEnableAutomaticPortMap": "Automatisches Port-Mapping aktivieren", @@ -473,7 +473,7 @@ "LabelEnableDlnaDebugLogging": "Fehlerprotokollierung für DLNA aktivieren", "LabelEnableDlnaDebugLoggingHelp": "Erzeugt große Logdateien und sollte nur zur Fehlersuche genutzt werden.", "LabelEnableDlnaPlayTo": "'Wiedergeben auf' DLNA-Funktion aktivieren", - "LabelEnableDlnaPlayToHelp": "Jellyfin kann Geräte in deinem Netzwerk erkennen und bietet die Möglichkeit diese fernzusteuern.", + "LabelEnableDlnaPlayToHelp": "Jellyfin kann Geräte in deinem Netzwerk erkennen und bietet die Möglichkeit, diese fernzusteuern.", "LabelEnableDlnaServer": "DLNA-Server aktivieren", "LabelEnableDlnaServerHelp": "Erlaube UPnP-Geräten in deinem Netzwerk den Zugriff und die Wiedergabe von Inhalten.", "LabelEnableHardwareDecodingFor": "Hardware-Dekodierung aktivieren für", @@ -509,8 +509,8 @@ "LabelImageFetchersHelp": "Aktiviere und ordne deine bevorzugten Bildquellen nach Präferenzen.", "LabelImageType": "Bildtyp", "LabelImportOnlyFavoriteChannels": "Auf favorisierte Kanäle beschränken", - "LabelInNetworkSignInWithEasyPassword": "Login mit einfachem PIN-Code für das eigene Netzwerk aktivieren", - "LabelInNetworkSignInWithEasyPasswordHelp": "Der einfache PIN erleichtert das Anmelden an Clients in deinem lokalen Netzwerk. Dein normales Passwort brauchst du dann nur noch, um dich von Außerhalb anzumelden. Wenn du die PIN leer lässt, benötigst du im lokalen Netzwerk überhaupt kein Passwort mehr.", + "LabelInNetworkSignInWithEasyPassword": "Login mit Easy PIN-Code für das eigene Netzwerk aktivieren", + "LabelInNetworkSignInWithEasyPasswordHelp": "Der Easy PIN-Code erleichtert das Anmelden an Clients in deinem lokalen Netzwerk. Dein normales Passwort brauchst du dann nur noch, um dich von außerhalb anzumelden. Wenn du die PIN leer lässt, benötigst du im lokalen Netzwerk überhaupt kein Passwort mehr.", "LabelInternetQuality": "Internetqualität", "LabelKeepUpTo": "Fortführen", "LabelKidsCategories": "Kinderkategorien", @@ -542,7 +542,7 @@ "LabelMaxResumePercentageHelp": "Titel werden als vollständig gesehen markiert, wenn sie nach dieser Zeit gestoppt werden.", "LabelMaxScreenshotsPerItem": "Maximale Anzahl von Screenshots pro Element:", "LabelMaxStreamingBitrate": "Maximale Streaming-Qualität", - "LabelMaxStreamingBitrateHelp": "Wähle die maximale Bitrate während des streamens.", + "LabelMaxStreamingBitrateHelp": "Wähle die maximale Bitrate während des Streamens.", "LabelMessageText": "Inhalt der Nachricht", "LabelMessageTitle": "Titel der Nachricht", "LabelMetadata": "Metadaten", @@ -552,7 +552,7 @@ "LabelMetadataPathHelp": "Wähle ein Verzeichnis für die heruntergeladenen Artworks und Metadaten.", "LabelMetadataReaders": "Metadatenleser", "LabelMetadataReadersHelp": "Ordne die bevorzugten lokalen Metadatenquellen nach Priorität. Die erste gefundene Datei wird gelesen.", - "LabelMetadataSavers": "Metadaten-Speicherer", + "LabelMetadataSavers": "Metadatenspeicher", "LabelMetadataSaversHelp": "Wähle die Dateiformate aus, die beim Speichern deiner Metadaten verwendet werden sollen.", "LabelMethod": "Methode", "LabelMinBackdropDownloadWidth": "Minimale Breite der zu herunterladenden Hintergründe", @@ -564,12 +564,12 @@ "LabelModelDescription": "Modellbeschreibung", "LabelModelName": "Modellname", "LabelModelNumber": "Modellnummer", - "LabelModelUrl": "Modell URL", + "LabelModelUrl": "Modell-URL", "LabelMonitorUsers": "Überwache Aktivität von", "LabelMovieCategories": "Filmkategorien", "LabelMoviePrefix": "Filmpräfix", "LabelMoviePrefixHelp": "Wenn ein Präfix in Filmtiteln angewendet wird, gib es hier ein, damit der Server es korrekt behandeln kann.", - "LabelMovieRecordingPath": "Film Aufnahmepfad", + "LabelMovieRecordingPath": "Filmaufnahmepfad", "LabelMusicStreamingTranscodingBitrate": "Bitrate für Musik-Transkodierung", "LabelMusicStreamingTranscodingBitrateHelp": "Wähle die maximale Bitrate für das Streamen von Musik.", "LabelNewName": "Neuer Name", @@ -578,14 +578,14 @@ "LabelNewsCategories": "Nachrichtenkategorien", "LabelNotificationEnabled": "Diese Benachrichtigung aktivieren", "LabelNumber": "Nummer", - "LabelNumberOfGuideDays": "Anzahl von Tagen für die Programminformationen geladen werden sollen", - "LabelNumberOfGuideDaysHelp": "Das Laden von Programmdaten weiterer Tage bietet einen besseren Überblick und die Möglichkeit weiter in die Zukunft zu planen. Aber es wird länger dauern alles herunterzuladen. Auto wählt auf Grundlage der Kanalanzahl.", + "LabelNumberOfGuideDays": "Anzahl der Tage, für die Programminformationen geladen werden sollen", + "LabelNumberOfGuideDaysHelp": "Das Laden von Programmdaten weiterer Tage bietet einen besseren Überblick und die Möglichkeit weiter in die Zukunft zu planen. Allerdings wird es länger dauern, alles herunterzuladen. Auto wählt auf Grundlage der Kanalanzahl.", "LabelOptionalNetworkPath": "Geteilter Netzwerkordner", "LabelOptionalNetworkPathHelp": "Wenn dieser Ordner in deinem Netzwerk geteilt wird, kann die Weitergabe des Netzwerkpfades Clients auf anderen Geräten direkten Zugang zu den Mediendateien ermöglichen. Beispielsweise {0} oder {1}.", - "LabelOriginalAspectRatio": "Originales Seitenverhältnis", - "LabelOriginalTitle": "Originaler Titel", + "LabelOriginalAspectRatio": "Ursprüngliches Seitenverhältnis", + "LabelOriginalTitle": "Originaltitel", "LabelOverview": "Übersicht", - "LabelParentNumber": "Ursprungsnummer", + "LabelParentNumber": "Übergeordnete Nummer", "LabelParentalRating": "Altersfreigabe", "LabelPassword": "Passwort", "LabelPasswordConfirm": "Passwort (bestätigen)", @@ -596,8 +596,8 @@ "LabelPlaceOfBirth": "Geburtsort", "LabelPlayDefaultAudioTrack": "Standardtonspur unabhängig von der Sprache abspielen", "LabelPlaylist": "Wiedergabeliste", - "LabelPostProcessor": "Nachbearbeitungs Anwendung", - "LabelPostProcessorArguments": "Nachbearbeitung Kommandozeilen-Argumente", + "LabelPostProcessor": "Anwendung für die Nachbearbeitung", + "LabelPostProcessorArguments": "Kommandozeilen-Argumente für die Nachbearbeitung", "LabelPostProcessorArgumentsHelp": "Verwende {path} als das Verzeichnis für Aufnahmen.", "LabelPreferredDisplayLanguage": "Bevorzugte Anzeigesprache", "LabelPreferredSubtitleLanguage": "Bevorzugte Untertitelsprache", @@ -607,14 +607,14 @@ "LabelProfileVideoCodecs": "Videocodecs", "LabelProtocol": "Protokoll", "LabelProtocolInfo": "Protokollinformation", - "LabelProtocolInfoHelp": "Der Wert, der für die Beantwortung von GetProtocolInfo Anfragen durch die Endgeräte benutzt wird.", + "LabelProtocolInfoHelp": "Der Wert, der für die Beantwortung von GetProtocolInfo-Anfragen durch die Endgeräte benutzt wird.", "LabelPublicHttpPort": "Öffentliche HTTP-Portnummer", "LabelPublicHttpPortHelp": "Die öffentliche Portnummer sollte einem lokalen HTTP-Port zugewiesen werden.", "LabelPublicHttpsPort": "Öffentliche HTTPS-Portnummer", "LabelPublicHttpsPortHelp": "Die öffentliche Portnummer sollte einem lokalen HTTPS-Port zugewiesen werden.", "LabelReasonForTranscoding": "Grund für die Transkodierung", "LabelRecord": "Aufnahme", - "LabelRecordingPath": "Standard Aufnahmepfad", + "LabelRecordingPath": "Standardaufnahmepfad", "LabelRecordingPathHelp": "Legt das Verzeichnis für Aufnahmen fest. Wird es leer gelassen, wird das Daten-Verzeichnis des Servers verwendet.", "LabelRefreshMode": "Aktualisierungsmodus", "LabelReleaseDate": "Veröffentlichungsdatum", @@ -632,7 +632,7 @@ "LabelSelectVersionToInstall": "Version für die Installation auswählen", "LabelSendNotificationToUsers": "Benachrichtigung senden an", "LabelSerialNumber": "Seriennummer", - "LabelSeriesRecordingPath": "Serien Aufnahmepfad", + "LabelSeriesRecordingPath": "Aufnahmepfad für Serien", "LabelServerHost": "Adresse", "LabelServerHostHelp": "192.168.1.100:8096 oder https://myserver.com", "LabelSimultaneousConnectionLimit": "Paralleler Streamlimit", @@ -646,28 +646,28 @@ "LabelSonyAggregationFlagsHelp": "Legt den Inhalt des 'aggregationFlags'-Elements im 'urn:schemas-sonycom:av' Namensraum fest.", "LabelSortBy": "Sortiert nach", "LabelSortOrder": "Sortierreihenfolge", - "LabelSortTitle": "Sortierungs Titel", + "LabelSortTitle": "Sortiertitel", "LabelSource": "Quelle", "LabelSpecialSeasonsDisplayName": "Anzeigename für Serien-Specials", "LabelSportsCategories": "Sportkategorie", "LabelStartWhenPossible": "Starte wenn möglich", "LabelStopWhenPossible": "Stoppe wenn möglich", "LabelStopping": "Stoppe", - "LabelSubtitleDownloaders": "Untertitel Downloader", + "LabelSubtitleDownloaders": "Untertitel-Downloader", "LabelSubtitleFormatHelp": "Beispiel: srt", "LabelSubtitlePlaybackMode": "Untertitelmodus", "LabelSupportedMediaTypes": "Unterstüzte Medientypen", - "LabelTVHomeScreen": "TV-Mode Startseite", + "LabelTVHomeScreen": "Startseite im Fernsehmodus", "LabelTextBackgroundColor": "Hintergrundfarbe des Textes", "LabelTextColor": "Textfarbe", "LabelTextSize": "Textgröße", - "LabelTheme": "Thema", + "LabelTheme": "Theme", "LabelTime": "Zeit", "LabelTimeLimitHours": "Zeitlimit (Stunden)", "LabelTitle": "Titel", "LabelTrackNumber": "Liednummer", "LabelTranscodingTempPathHelp": "Wähle einen eigenen Pfad für transkodierte Dateien. Lasse das Feld frei, um den Standardspeicherort zu nutzen.", - "LabelTranscodingThreadCount": "Anzahl der Transkodierungs-Threads", + "LabelTranscodingThreadCount": "Anzahl der Transkodierungsthreads", "LabelTranscodingThreadCountHelp": "Legt die maximale Anzahl von Transkodierungs-Threads fest. Das Reduzieren der Thread-Anzahl verringert die CPU-Auslastung, verhindert aber möglicherweise eine ausreichend schnelle Transkodierung für eine störungsfrei Wiedergabe.", "LabelTriggerType": "Auslöser-Typ", "LabelTunerIpAddress": "IP-Adresse des Tuners", @@ -677,7 +677,7 @@ "LabelUseNotificationServices": "Nutze folgende Dienste", "LabelUser": "Benutzer", "LabelUserAgent": "User-Agent", - "LabelUserLibrary": "Benutzer Bibliothek", + "LabelUserLibrary": "Benutzerbibliothek", "LabelUserLibraryHelp": "Wähle aus, welche Medienbibliothek auf den Endgeräten angezeigt werden soll. Ohne Eintrag wird die Standardeinstellung beibehalten.", "LabelUserRemoteClientBitrateLimitHelp": "Diese Einstellung überschreibt die globale Standardeinstellung in den Abspieleinstellungen des Servers.", "LabelUsername": "Benutzername", @@ -698,7 +698,7 @@ "Large": "Groß", "LatestFromLibrary": "Kürzlich hinzugefügt in {0}", "LearnHowYouCanContribute": "Erfahre, wie du unterstützen kannst.", - "LibraryAccessHelp": "Wähle die Bibliotheken aus, die du mit diesem Benutzer teilen möchtest. Administratoren können den Metadaten-Manager verwenden um alle Ordner zu bearbeiten.", + "LibraryAccessHelp": "Wähle die Bibliotheken aus, die du mit diesem Benutzer teilen möchtest. Administratoren können den Metadaten-Manager verwenden, um alle Ordner zu bearbeiten.", "List": "Liste", "LiveBroadcasts": "Liveübertragungen", "ManageLibrary": "Bibliothek verwalten", @@ -741,29 +741,29 @@ "MessageContactAdminToResetPassword": "Bitte kontaktiere deinen Systemadministrator, um dein Passwort zurücksetzen zu lassen.", "MessageCreateAccountAt": "Erstelle ein Konto bei {0}", "MessageDeleteTaskTrigger": "Bist du dir sicher, dass du diesen Aufgabenauslöser entfernen möchtest?", - "MessageDirectoryPickerBSDInstruction": "Für BSD musst du ggf. Speicherplatz in deinem FreeNAS Jail so konfigurieren, dass Jellyfin auf deine Medien zugreifen kann.", - "MessageDirectoryPickerLinuxInstruction": "Für Linux auf Arch Linux, CentOS, Debian, Fedora, openSUSE oder Ubuntu muss der Service Benutzer mindestens lesenden Zugriff auf die Speicherorte der Medien besitzen.", + "MessageDirectoryPickerBSDInstruction": "Für BSD musst du ggf. Speicherplatz in deinem 'FreeNAS Jail' so konfigurieren, dass Jellyfin auf deine Medien zugreifen kann.", + "MessageDirectoryPickerLinuxInstruction": "Für Linux auf Arch Linux, CentOS, Debian, Fedora, openSUSE oder Ubuntu muss der Servicebenutzer mindestens lesenden Zugriff auf die Speicherorte der Medien besitzen.", "MessageDownloadQueued": "Download eingereiht.", "MessageEnablingOptionLongerScans": "Die Aktivierung dieser Option kann erheblich längere Bibliotheks-Scans verursachen.", "MessageFileReadError": "Fehler beim Lesen der Datei. Bitte versuche es erneut.", "MessageForgotPasswordFileCreated": "Die folgende Datei wurde auf deinem Server erstellt und enthält eine Anleitung, wie fortgefahren werden muss", - "MessageForgotPasswordInNetworkRequired": "Bitte versuche es erneut innerhalb deines Heimnetzwerks, um die Passwort Zurücksetzung zu starten.", + "MessageForgotPasswordInNetworkRequired": "Bitte versuche die Passwortrücksetzung noch einmal innerhalb deines Heimnetzwerks.", "MessageInvalidForgotPasswordPin": "Ein ungültiger oder abgelaufener PIN-Code wurde eingegeben. Bitte versuche es erneut.", "MessageInvalidUser": "Falscher Benutzername oder Passwort. Bitte versuche es erneut.", "MessageItemSaved": "Element gespeichert.", "MessageItemsAdded": "Element hinzugefügt.", - "MessageLeaveEmptyToInherit": "Leer lassen um die Einstellungen vom übergeordneten Objekt oder dem systemweiten Standardwert zu erben.", + "MessageLeaveEmptyToInherit": "Leer lassen, um die Einstellungen vom übergeordneten Eintrag oder dem systemweiten Standardwert zu verwenden.", "MessageNoAvailablePlugins": "Keine verfügbaren Erweiterungen.", "MessageNoMovieSuggestionsAvailable": "Momentan sind keine Filmvorschläge verfügbar. Schaue und bewerte zuerst deine Filme. Komme danach zurück, um deine Filmvorschläge anzuschauen.", "MessageNoPluginsInstalled": "Du hast keine Plugins installiert.", "MessageNoTrailersFound": "Installiere den Filmvorschau-Kanal um die Film-Bibliothek mit Filmvorschauen aus dem Internet zu erweitern.", "MessageNothingHere": "Nichts hier.", - "MessagePasswordResetForUsers": "Die Passwörter der folgenden Benutzer wurden zurückgesetzt. Diese können sich nun mit den PIN-Codes anmelden, mit denen der Reset durchgeführt wurde.", + "MessagePasswordResetForUsers": "Die Passwörter der folgenden Benutzer wurden zurückgesetzt. Diese können sich nun mit den PIN-Codes anmelden, welche für das zurücksetzen benutzt wurden.", "MessagePlayAccessRestricted": "Das Abspielen dieses Inhaltes ist derzeit eingeschränkt. Bitte kontaktiere deinen Server-Administrator für weitere Informationen.", - "MessagePleaseEnsureInternetMetadata": "Bitte sicherstellen, dass das Herunterladen von Internet Metadaten aktiviert ist.", + "MessagePleaseEnsureInternetMetadata": "Bitte sicherstellen, dass das Herunterladen von Metadaten aus dem Internet aktiviert ist.", "MessagePleaseWait": "Bitte warten, dies kann eine Minute dauern.", "MessagePluginConfigurationRequiresLocalAccess": "Melde dich bitte direkt an deinem lokalen Server an, um dieses Plugin einzurichten.", - "MessagePluginInstallDisclaimer": "Plugins aus der Community sind eine gute Möglichkeit um dein Erlebnis mit weiteren Funktionen und Vorteilen aufzuwerten. Bevor du diese installierst, sei dir den daraus resultierenden möglichen Umständen für deinen Server bewusst. Dies können z.B. längere Bibliotheken Scans, weiterführende Verarbeitung von Daten im Hintergrund sowie Systeminstabilität sein.", + "MessagePluginInstallDisclaimer": "WARNUNG: Mit dem Installieren eines Drittanbieterplugins entstehen Risiken. Es könnte nicht stabilen oder bösartigen Code enthalten und kann jederzeit von seinem Entwickler verändert werden. Installiere daher nur Plugins von Entwicklern, denen du vertraust. Sei dir den möglichen Auswirkungen bewusst, die eine Installation haben kann, einschließlich Anfragen an externe Dienste, verlängerte Bibliotheksscans oder zusätzliche Hintergrundverarbeitungen.", "MessageReenableUser": "Siehe unten zur Reaktivierung", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Die folgenden Medienverzeichnisse werden aus der Bibliothek entfernt", "MessageUnableToConnectToServer": "Wir können gerade keine Verbindung zum gewählten Server herstellen. Bitte stelle sicher, dass dieser läuft und versuche es erneut.", @@ -771,7 +771,7 @@ "MessageYouHaveVersionInstalled": "Du hast momentan Version {0} installiert.", "Metadata": "Metadaten", "MetadataManager": "Metadaten-Manager", - "MetadataSettingChangeHelp": "Das Verändern der Metadata-Einstellungen hat nur Einfluss auf neu hinzugefügte Inhalte. Um eine Aktualisierung bereits hinzugefügter Inhalte durchzuführen, öffne bitte die Detailansicht und klicke die Aktualisieren-Schaltfläche. Die Aktualisierung mehrerer Inhalte kann im Metadata Manager durchgeführt werden.", + "MetadataSettingChangeHelp": "Das Verändern der Metadateneinstellungen hat nur Einfluss auf neu hinzugefügte Inhalte. Um eine Aktualisierung bereits hinzugefügter Inhalte durchzuführen, öffne bitte die Detailansicht und klicke die Aktualisieren-Schaltfläche. Die Massenaktualisierung kann im Metadaten-Manager durchgeführt werden.", "MinutesAfter": "Minuten nach", "MinutesBefore": "Minuten vor", "Mobile": "Smartphone", @@ -826,7 +826,7 @@ "OptionAutomaticallyGroupSeries": "Automatisches Zusammenführen von Serieninhalten, die über mehrere Ordner verteilt sind", "OptionAutomaticallyGroupSeriesHelp": "Inhalte einer Serie in verschiedenen Ordnern werden innerhalb einer Bibliothek als eine Serie angezeigt.", "OptionCommunityRating": "Community Bewertung", - "OptionCriticRating": "Kritiker Bewertung", + "OptionCriticRating": "Kritikerbewertung", "OptionCustomUsers": "Benutzerdefiniert", "OptionDaily": "Täglich", "OptionDateAdded": "Hinzugefügt am", @@ -883,10 +883,10 @@ "OptionResumable": "Fortsetzbar", "OptionSaveMetadataAsHidden": "Speichere Metadaten und Bilder als versteckte Dateien", "OptionSaveMetadataAsHiddenHelp": "Diese Änderung betrifft nur neu gespeicherte Metadaten. Schon gespeicherte Metadaten werden erst aktualisiert, wenn der Server diese erneut speichert.", - "OptionTvdbRating": "TheTVDB Bewertung", + "OptionTvdbRating": "TheTVDB-Bewertung", "OptionUnairedEpisode": "Nicht ausgestrahlte Episoden", "OptionWakeFromSleep": "Aufwachen nach dem Schlafen", - "OptionWeekdays": "Wöchentlich", + "OptionWeekdays": "An Wochentagen", "OptionWeekends": "An Wochenenden", "OptionWeekly": "Wöchentlich", "OriginalAirDateValue": "Erstausstrahlung: {0}", @@ -917,8 +917,8 @@ "PleaseAddAtLeastOneFolder": "Bitte füge mindestens ein Verzeichnis zur Bibliothek durch Klicken der \"Hinzufügen\"-Schaltfläche hinzu.", "PleaseConfirmPluginInstallation": "Bitte bestätige mit OK, dass du den oben stehenden Text gelesen hast und die Installation des Plugins fortführen möchtest.", "PleaseEnterNameOrId": "Bitte gib einen Namen oder eine externe ID an.", - "PleaseRestartServerName": "Bitte Jellyfin an {0} neustarten.", - "PleaseSelectTwoItems": "Bitte wähle mindestens zwei Elemente aus.", + "PleaseRestartServerName": "Bitte Jellyfin auf {0} neustarten.", + "PleaseSelectTwoItems": "Bitte mindestens zwei Einträge auswählen.", "MessagePluginInstalled": "Das Plugin wurde erfolgreich installiert. Der Server muss neu gestartet werden, um die Änderungen zu übernehmen.", "PreferEmbeddedTitlesOverFileNames": "Eingebettete Titel dem Dateinamen bevorzugen", "PreferEmbeddedTitlesOverFileNamesHelp": "Bestimmt den Anzeigetitel, der verwendet werden soll, wenn keine Internet-Metadaten oder lokalen Metadaten verfügbar sind.", @@ -945,7 +945,7 @@ "RecordingScheduled": "Aufnahme geplant.", "Recordings": "Aufnahmen", "Refresh": "Aktualisieren", - "RefreshDialogHelp": "Metadaten werden auf Basis der Einstellungen und Internet Services, die im Dashboard aktiviert sind, aktualisiert.", + "RefreshDialogHelp": "Metadaten werden auf Basis der Einstellungen und Internetdienste, die im Dashboard aktiviert sind, aktualisiert.", "RefreshMetadata": "Metadaten aktualisieren", "RefreshQueued": "Aktualisierung eingereiht.", "ReleaseDate": "Veröffentlichungsdatum", @@ -954,7 +954,7 @@ "RemoveFromPlaylist": "Von Wiedergabeliste entfernen", "Repeat": "Wiederholen", "RepeatAll": "Alles wiederholen", - "RepeatEpisodes": "Wiederholung Episoden", + "RepeatEpisodes": "Episoden wiederholen", "RepeatMode": "Wiederholungsmodus", "RepeatOne": "Dieses wiederholen", "ReplaceAllMetadata": "Alle Metadaten ersetzen", @@ -964,10 +964,10 @@ "Runtime": "Laufzeit", "Saturday": "Samstag", "Save": "Speichern", - "SaveSubtitlesIntoMediaFolders": "Speichere Untertitel in den Medienverzeichnissen", - "SaveSubtitlesIntoMediaFoldersHelp": "Das Speichern der Untertitel neben den Video-Dateien macht es deutlich leichter diese zu verwalten.", - "ScanForNewAndUpdatedFiles": "Scanne nach neuen und aktualisierten Dateien", - "ScanLibrary": "Scanne Bibliothek", + "SaveSubtitlesIntoMediaFolders": "Untertitel in den Medienverzeichnissen speichern", + "SaveSubtitlesIntoMediaFoldersHelp": "Das Speichern der Untertitel neben den Videodateien macht die Verwaltung deutlich leichter.", + "ScanForNewAndUpdatedFiles": "Nach neuen und aktualisierten Dateien scannen", + "ScanLibrary": "Bibliothek scannen", "Schedule": "Zeitplan", "Screenshot": "Screenshot", "Search": "Suche", @@ -978,42 +978,42 @@ "SendMessage": "Nachricht senden", "Series": "Serien", "SeriesCancelled": "Serie abgebrochen.", - "SeriesDisplayOrderHelp": "Sortiere Episoden nach Ausstrahlungsdatum, DVD Reihenfolge oder absoluter Nummerierung.", - "SeriesRecordingScheduled": "Serien-Aufnahme geplant.", + "SeriesDisplayOrderHelp": "Sortiere Episoden nach Ausstrahlungsdatum, DVD-Reihenfolge oder absoluter Nummerierung.", + "SeriesRecordingScheduled": "Serienaufnahme geplant.", "SeriesSettings": "Serieneinstellungen", "SeriesYearToPresent": "{0}-Heute", - "ServerNameIsRestarting": "Jellyfin Server an {0} startet neu.", - "ServerNameIsShuttingDown": "Der Server an {0} fährt herunter.", + "ServerNameIsRestarting": "Jellyfin-Server auf {0} startet neu.", + "ServerNameIsShuttingDown": "Der Server auf {0} fährt herunter.", "ServerRestartNeededAfterPluginInstall": "Jellyfin muss nach der Installation eines Plugins neu gestartet werden.", "ServerUpdateNeeded": "Dieser Server muss aktualisiert werden. Um die neueste Version herunterzuladen, besuche bitte {0}", "Settings": "Einstellungen", "SettingsSaved": "Einstellungen gespeichert.", - "SettingsWarning": "Das Verändern dieser Werte kann Instabilität und Verbindungsprobleme hervorrufen. Wenn Probleme auftreten sollten, empfehlen wir, diese Einstellungen auf die Standardwerte zurück zu stellen.", + "SettingsWarning": "Das Verändern dieser Werte kann Instabilität und Verbindungsprobleme hervorrufen. Wenn Probleme auftreten sollten, empfehlen wir, diese Einstellungen auf die Standardwerte zurückzustellen.", "Share": "Teilen", "ShowAdvancedSettings": "Erweiterte Einstellungen anzeigen", - "ShowIndicatorsFor": "Zeige Indikatoren für", + "ShowIndicatorsFor": "Indikatoren anzeigen für", "ShowTitle": "Titel anzeigen", "ShowYear": "Jahr anzeigen", "Shows": "Serien", "Shuffle": "Zufallswiedergabe", "SimultaneousConnectionLimitHelp": "Die maximale Anzahl der parallel erlaubten Streams. 0 für kein Limit.", "SkipEpisodesAlreadyInMyLibrary": "Nehme keine Episoden auf, die schon in meiner Bibliothek verfügbar sind", - "SkipEpisodesAlreadyInMyLibraryHelp": "Episoden werden mittels Staffel- und Episodennummer verglichen, wenn verfügbar.", + "SkipEpisodesAlreadyInMyLibraryHelp": "Wenn verfügbar, werden Episoden mittels Staffel- und Episodennummer verglichen.", "Small": "Klein", "SmallCaps": "Kapitälchen", "Smaller": "Kleiner", "SmartSubtitlesHelp": "Untertitel, die den Spracheinstellungen entsprechen, werden nur bei einer Tonspur in fremder Sprache heruntergeladen.", "Sort": "Sortieren", "SortByValue": "Sortieren nach {0}", - "SortChannelsBy": "Sortiere Kanäle nach", + "SortChannelsBy": "Kanäle sortieren nach", "SortName": "Sortiername", "Sports": "Sport", "StopRecording": "Aufnahme stoppen", - "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Diese Einstellungen werden auch auf jede Google Cast Wiedergabe angewendet, die von diesem Gerät gestartet wird.", - "SubtitleAppearanceSettingsDisclaimer": "Die folgenden Einstellungen gelten nicht für die oben erwähnten grafischen Untertitel oder ASS/SSA-Untertitel, die ihre eigenen Stile einbetten.", - "SubtitleDownloadersHelp": "Aktiviere und bewerte Deine bevorzugten Untertitel Downloader in der Reihenfolge der Priorität.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Diese Einstellungen werden auch auf jede Google Cast-Wiedergabe angewendet, die von diesem Gerät gestartet wird.", + "SubtitleAppearanceSettingsDisclaimer": "Die folgenden Einstellungen gelten nicht für die oben erwähnten grafischen Untertitel oder ASS-/SSA-Untertitel, die ihre eigenen Stile einbetten.", + "SubtitleDownloadersHelp": "Aktiviere und bewerte deine bevorzugten Untertitel-Downloader in der Reihenfolge der Priorität.", "Subtitles": "Untertitel", - "Suggestions": "Empfehlungen", + "Suggestions": "Vorschläge", "Sunday": "Sonntag", "Sync": "Synchronisation", "SystemDlnaProfilesHelp": "Systemprofile sind schreibgeschützt. Änderungen an einem Systemprofil werden als neues benutzerdefiniertes Profil gespeichert.", @@ -1027,7 +1027,7 @@ "TabMusic": "Musik", "TabMyPlugins": "Meine Plugins", "TabNetworks": "Fernsehsender", - "TabNfoSettings": "NFO Einstellungen", + "TabNfoSettings": "NFO-Einstellungen", "TabNotifications": "Benachrichtigungen", "TabOther": "Andere", "TabParentalControl": "Kindersicherung", @@ -1041,8 +1041,8 @@ "TheseSettingsAffectSubtitlesOnThisDevice": "Diese Einstellungen beeinflussen Untertitel auf diesem Gerät", "ThisWizardWillGuideYou": "Dieser Assistent wird dich durch den Einrichtungsprozess führen. Um zu beginnen, wähle bitte deine bevorzugte Sprache.", "Thursday": "Donnerstag", - "TitleHardwareAcceleration": "Hardware Beschleunigung", - "TitleHostingSettings": "Hosting Einstellungen", + "TitleHardwareAcceleration": "Hardwarebeschleunigung", + "TitleHostingSettings": "Hosting-Einstellungen", "TitlePlayback": "Wiedergabe", "TrackCount": "{0} Titel", "Trailers": "Trailer", @@ -1139,7 +1139,7 @@ "MessageImageFileTypeAllowed": "Nur JPEG- und PNG-Dateien werden unterstützt.", "MessageImageTypeNotSelected": "Bitte wähle einen Bildtyp aus dem Drop-Down Menü aus.", "Normal": "Normal", - "LabelDynamicExternalId": "{0} Id", + "LabelDynamicExternalId": "{0}-ID", "LabelStatus": "Status", "Live": "Live", "LiveTV": "Live-TV", @@ -1205,7 +1205,7 @@ "ButtonAddImage": "Bild hinzufügen", "LabelSize": "Größe", "LabelTranscodes": "Transkodierungen", - "LabelTranscodingProgress": "Transkodierungsfortschritt", + "LabelTranscodingProgress": "Fortschritt der Transkodierung", "LabelAudioBitDepth": "Audio-Bittiefe", "LabelPleaseRestart": "Die Änderungen werden nach dem manuellen Neuladen des Webclients wirksam.", "LabelVideoBitrate": "Video-Bitrate", @@ -1215,7 +1215,7 @@ "LabelBaseUrlHelp": "Füge ein benutzerdefiniertes Unterverzeichnis zur Server-URL hinzu, zum Beispiel: http://example.com/<baseurl>", "LabelFolder": "Ordner", "LabelPasswordResetProvider": "Anbieter zum Zurücksetzen des Passwortes", - "LabelPlayMethod": "Spielmethode", + "LabelPlayMethod": "Abspielmethode", "DashboardOperatingSystem": "Betriebssystem: {0}", "DashboardArchitecture": "Architektur: {0}", "LabelVideoCodec": "Videocodec", @@ -1225,13 +1225,13 @@ "MessageNoServersAvailable": "Die automatische Serversuche konnte keinen Server finden.", "LabelPlayer": "Player", "MediaInfoCodecTag": "Codec-Tag", - "SubtitleOffset": "Untertitel-Synchronisierung", + "SubtitleOffset": "Untertitelzeitversatz", "PlaybackData": "Wiedergabeinformationen", "MusicVideo": "Musikvideo", "MusicLibraryHelp": "Überprüfe den {0}Musikbenennungsguide{1}.", "OptionRandom": "Zufällig", "TabNetworking": "Netzwerk", - "ButtonSplit": "Geteilt", + "ButtonSplit": "Teilen", "SelectAdminUsername": "Bitte wähle einen Benutzernamen für den Administrator-Account.", "HeaderNavigation": "Navigation", "MessageConfirmAppExit": "Wirklich verlassen?", @@ -1254,7 +1254,7 @@ "OnWakeFromSleep": "Beim Aufwachen aus \"Energie sparen\"", "WeeklyAt": "{0} um {1}", "DailyAt": "Täglich um {0}", - "LastSeen": "Zuletzt gesehen {0}", + "LastSeen": "Zuletzt angesehen {0}", "PersonRole": "als {0}", "ListPaging": "{0}-{1} von {2}", "WriteAccessRequired": "Jellyfin benötigt Schreibrechte auf diesem Ordner. Bitte prüfe die Schreibrechte und versuche es erneut.", @@ -1295,9 +1295,9 @@ "MessageSyncPlayErrorAccessingGroups": "Beim Zugriff auf die Gruppen ist ein Fehler aufgetreten.", "MessageSyncPlayLibraryAccessDenied": "Der Zugang zu diesem Inhalt ist beschränkt.", "MessageSyncPlayJoinGroupDenied": "Gruppenbeitritt fehlgeschlagen.", - "MessageSyncPlayCreateGroupDenied": "Zum Erstellen einer Gruppe ist eine Genehmigung erforderlich.", + "MessageSyncPlayCreateGroupDenied": "Berechtigung erforderlich für die Erstellung einer Gruppe.", "MessageSyncPlayGroupDoesNotExist": "Konnte der Gruppe nicht beitreten, da sie nicht existiert.", - "MessageSyncPlayPlaybackPermissionRequired": "Wiedergabegenehmigung erforderlich.", + "MessageSyncPlayPlaybackPermissionRequired": "Wiedergabeberechtigung erforderlich.", "MessageSyncPlayNoGroupsAvailable": "Keine Gruppen verfügbar. Fange an, etwas abzuspielen.", "MessageSyncPlayGroupWait": "{0} lädt…", "MessageSyncPlayUserLeft": "{0} hat die Gruppe verlassen.", @@ -1305,7 +1305,7 @@ "MessageSyncPlayDisabled": "SyncPlay deaktiviert.", "MessageSyncPlayEnabled": "SyncPlay aktiviert.", "LabelSyncPlayAccess": "SyncPlay-Zugriff", - "LabelSyncPlayAccessNone": "Deaktiviert für diesen Benutzer", + "LabelSyncPlayAccessNone": "Für diesen Benutzer deaktiviert", "LabelSyncPlayAccessJoinGroups": "Dem Benutzer erlauben, Gruppen beizutreten", "LabelSyncPlayAccessCreateAndJoinGroups": "Dem Benutzer erlauben, Gruppen zu erstellen und beizutreten", "LabelSyncPlayLeaveGroupDescription": "SyncPlay deaktivieren", @@ -1377,7 +1377,7 @@ "LabelColorSpace": "Farbraum", "MediaInfoColorSpace": "Farbraum", "VideoAudio": "Videoton", - "AllowTonemappingHelp": "Dynamikkompression kann den Dynamikumfang eines Videos von HDR nach SDR wandeln und dabei die für die Darstellung der Originalszene sehr wichtigen Bilddetails und Farben beibehalten. Dies funktioniert zurzeit nur bei 10-Bit-HDR10, HLG und DoVi-V und benötigt die entsprechende OpenCL- oder CUDA-Laufzeitumgebung.", + "AllowTonemappingHelp": "Tone-Mapping kann den Dynamikumfang eines Videos von HDR nach SDR wandeln und dabei die für die Darstellung der Originalszene sehr wichtigen Bilddetails und Farben beibehalten. Dies funktioniert zurzeit nur bei HDR10-, HLG- und Dolby-Vision-Videos und benötigt die entsprechende OpenCL- oder CUDA-Laufzeitumgebung.", "TonemappingRangeHelp": "Wähle den Ausgabefarbraum aus. Auto ist derselbe wie der Eingabefarbraum.", "TonemappingAlgorithmHelp": "Das Tone-Mapping kann fein abgestimmt werden. Wenn du mit diesen Optionen nicht vertraut bist, behalte einfach den Standardwert bei. Der empfohlene Wert ist \"BT.2390\".", "LabelTonemappingAlgorithm": "Wähle den zu verwendenden Tone-Mapping-Algorithmus aus", @@ -1396,27 +1396,27 @@ "LabelTonemappingRange": "Tone-Mapping-Bereich", "LabelColorPrimaries": "Primärfarben", "LabelColorTransfer": "Farbübertragung", - "LabelVideoRange": "Video-Bereich", + "LabelVideoRange": "Videobereich", "MediaInfoColorPrimaries": "Primärfarben", "MediaInfoColorTransfer": "Farbübertragung", "MediaInfoVideoRange": "Videobereich", "ThumbCard": "Miniaturansichtkarte", "QuickConnectNotActive": "Quick Connect ist auf diesem Server nicht aktiv", "QuickConnectNotAvailable": "Frag deinen Server-Administrator, ob er Quick Connect erlaubt", - "QuickConnectInvalidCode": "Quick Connect Code ungültig", - "QuickConnectDescription": "Um mit Quick Connect einzuloggen, wähle den Quick Connect-Knopf auf einem angemeldeten Gerät und gib den unten angezeigten Code ein.", - "QuickConnectDeactivated": "Quick Connect wurde deaktiviert bevor der Login verifiziert werden konnte", + "QuickConnectInvalidCode": "Quick Connect-Code ungültig", + "QuickConnectDescription": "Für das Einloggen mit Quick Connect wähle den 'Quick Connect'-Knopf auf deinem Gerät, mit dem du dich anmelden möchtest, und gib den unten angezeigten Code ein.", + "QuickConnectDeactivated": "Quick Connect wurde deaktiviert, bevor der Login verifiziert werden konnte", "QuickConnectAuthorizeFail": "Unbekannter Quick Connect-Code", "QuickConnectAuthorizeSuccess": "Anfrage autorisiert", "QuickConnectAuthorizeCode": "Login Code {0} eingeben", "QuickConnectActivationSuccessful": "Erfolgreich aktiviert", "EnableQuickConnect": "Quick Connect auf diesem Server aktivieren", - "QuickConnect": "Schnellverbindung", + "QuickConnect": "Quick Connect", "PosterCard": "Posterkarte", "LabelQuickConnectCode": "Quick Connect-Code", "LabelCurrentStatus": "Aktueller Status", "EnableAutoCast": "Als Standard festlegen", - "ButtonUseQuickConnect": "Quick Connect nutzen", + "ButtonUseQuickConnect": "Quick Connect verwenden", "ButtonActivate": "Aktivieren", "Authorize": "Autorisieren", "OptionMaxActiveSessionsHelp": "Ein Wert von 0 deaktiviert die Funktion.", @@ -1465,8 +1465,8 @@ "LabelSSDPTracingFilter": "SSDP-Filter", "LabelPublishedServerUriHelp": "Überschreibt die von Jellyfin genutzte URI auf Basis des Interfaces oder der IP-Adresse des Clients.", "LabelPublishedServerUri": "Veröffentlichte Server-URIs", - "LabelEnableSSDPTracingHelp": "Aktiviere detailreiche SSDP Netzwerkverfolgung-Logs.
WARNUNG: Dies wird erhebliche Einbüßungen in der Leistung hervorrufen.", - "LabelEnableSSDPTracing": "Aktiviere SSDP Verfolgung", + "LabelEnableSSDPTracingHelp": "Aktiviere detaillierte SSDP-Netzwerkverfolgungslogs.
WARNUNG: Dies wird erhebliche Leistungseinbußen verursachen.", + "LabelEnableSSDPTracing": "SSDP-Verfolgung aktivieren", "HeaderDebugging": "Fehlersuche und -verfolgung", "AllowHevcEncoding": "Kodierung im HEVC-Format zulassen", "PreferFmp4HlsContainerHelp": "fMP4 wird als Standardcontainer für HLS bevorzugt, damit HEVC-Inhalte direkt auf unterstützten Geräten abgespielt werden können.", @@ -1489,8 +1489,8 @@ "AspectRatioCover": "Bedecken", "AspectRatioFill": "Füllen", "SyncPlayGroupDefaultTitle": "{0}'s Gruppe", - "MessageSyncPlayIsDisabled": "Erlaubnis erforderlich um SyncPlay zu nutzen.", - "LabelSyncPlayTimeSyncOffset": "Zeitverschiebung", + "MessageSyncPlayIsDisabled": "Berechtigung erforderlich für die Nutzung von SyncPlay.", + "LabelSyncPlayTimeSyncOffset": "Zeitversatz", "LabelSyncPlayTimeSyncDevice": "Zeitsynchronisierung mit", "LabelSyncPlayResumePlaybackDescription": "Lokale Wiedergabe wiederherstellen", "LabelSyncPlayResumePlayback": "Lokale Wiedergabe fortsetzen", @@ -1508,7 +1508,7 @@ "DisablePlugin": "Deaktivieren", "EnablePlugin": "Aktivieren", "Framerate": "Bildrate", - "DirectPlayHelp": "Die Quelldatei ist vollständig mit diesem Client kompatibel, und die Sitzung empfängt die Datei ohne Änderungen.", + "DirectPlayHelp": "Die Quelldatei ist vollständig mit diesem Client kompatibel und die Sitzung empfängt die Datei ohne Änderungen.", "HeaderContinueReading": "Weiterlesen", "EnableGamepadHelp": "Auf Eingaben aller verbundenen Controller hören. (Erfordert: 'TV'-Anzeigemodus)", "LabelEnableGamepad": "Gamepad aktivieren", @@ -1546,7 +1546,7 @@ "OtherArtist": "Anderer Künstler", "Mixer": "Mischer", "Lyricist": "Texter", - "LabelSyncPlaySettingsDescription": "SyncPlay Einstellungen ändern", + "LabelSyncPlaySettingsDescription": "SyncPlay-Einstellungen ändern", "HeaderSyncPlayPlaybackSettings": "Wiedergabe", "HeaderSyncPlaySettings": "SyncPlay-Einstellungen", "Engineer": "Toningenieur", @@ -1555,7 +1555,7 @@ "LabelSyncPlaySettingsSyncCorrectionHelp": "Aktiviere die aktive Synchronisierung der Wiedergabe, indem du entweder die Abspielgeschwindigkeit der Medien erhöhst oder zur geschätzte Position vorspulst. Deaktiviere dies im Falle von starkem Stottern.", "LabelSyncPlaySettingsSyncCorrection": "Synchronisationskorrektur", "LabelSyncPlaySettingsExtraTimeOffsetHelp": "Stelle den Zeitversatz (in ms) manuell mit dem ausgewählten Gerät für die Zeitsynchronisation ein. Passe die Einstellung mit Bedacht an.", - "LabelOriginalName": "Originaler Name", + "LabelOriginalName": "Originalname", "HeaderSyncPlayTimeSyncSettings": "Zeitsynchronisierung", "AgeValue": "({0} Jahre alt)", "Track": "Titel", @@ -1568,14 +1568,14 @@ "LabelSyncPlaySettingsSkipToSync": "SkipToSync aktivieren", "LabelSyncPlaySettingsSpeedToSyncHelp": "Sync-Korrekturmethode, die darin besteht, die Wiedergabe zu beschleunigen. Sync-Korrektur muss aktiviert sein.", "LabelSyncPlaySettingsSpeedToSync": "SpeedToSync aktivieren", - "LabelSyncPlaySettingsMinDelaySkipToSyncHelp": "Minimale Abspielverzögerung (in ms) nach der SkipToSync versucht die Wiedergabeposition zu korrigieren.", - "LabelSyncPlaySettingsMinDelaySkipToSync": "SkipToSync minimale Verzögerung", - "LabelSyncPlaySettingsSpeedToSyncDurationHelp": "Anzahl der Millisekunden, die SpeedToSync zur Korrektur der Abspielposition verwendet.", + "LabelSyncPlaySettingsMinDelaySkipToSyncHelp": "Minimale Abspielverzögerung (in ms) nach der SkipToSync versucht, die Wiedergabeposition zu korrigieren.", + "LabelSyncPlaySettingsMinDelaySkipToSync": "Minimale SkipToSync-Verzögerung", + "LabelSyncPlaySettingsSpeedToSyncDurationHelp": "Anzahl der Millisekunden, die von SpeedToSync zur Korrektur der Abspielposition verwendet wird.", "LabelSyncPlaySettingsSpeedToSyncDuration": "SpeedToSync-Dauer", "LabelSyncPlaySettingsMaxDelaySpeedToSyncHelp": "Maximale Wiedergabeverzögerung (in ms) nach der SkipToSync anstatt SpeedToSync genutzt wird.", - "LabelSyncPlaySettingsMaxDelaySpeedToSync": "SpeedToSync maximale Verzögerung", - "LabelSyncPlaySettingsMinDelaySpeedToSyncHelp": "Minimale Abspielverzögerung (in ms) nach der SpeedToSync versucht die Wiedergabeposition zu korrigieren.", - "LabelSyncPlaySettingsMinDelaySpeedToSync": "SpeedToSync minimale Verzögerung", + "LabelSyncPlaySettingsMaxDelaySpeedToSync": "Maximale SpeedToSync-Verzögerung", + "LabelSyncPlaySettingsMinDelaySpeedToSyncHelp": "Minimale Abspielverzögerung (in ms) nach der SpeedToSync versucht, die Wiedergabeposition zu korrigieren.", + "LabelSyncPlaySettingsMinDelaySpeedToSync": "Minimale SpeedToSync-Verzögerung", "LabelSyncPlaySettingsExtraTimeOffset": "Zusätzlicher Zeitversatz", "LabelSortName": "Sortieren nach Name", "LabelMaxDaysForNextUpHelp": "Legt fest, wie viele Tage eine Sendung maximal in der Liste 'Als Nächstes' bleiben soll, ohne dass sie angesehen wird.", @@ -1634,14 +1634,14 @@ "MixedMoviesShows": "Gemischte Filme und Serien", "AddToFavorites": "Zu Favoriten hinzufügen", "GoogleCastUnsupported": "Google Cast nicht unterstützt", - "CopyFailed": "kopieren fehlgeschlagen", - "Copy": "kopieren", - "Copied": "kopiert", + "CopyFailed": "Kopieren fehlgeschlagen", + "Copy": "Kopieren", + "Copied": "Kopiert", "ButtonClose": "Schließen", "ButtonBackspace": "Rücktaste", "Localization": "Lokalisierung", "ItemDetails": "Details", - "EnableRewatchingNextUpHelp": "Schon angeschaute Folgen in 'Weiterschauen' anzeigen.", + "EnableRewatchingNextUpHelp": "Bereits angeschaute Folgen in 'Weiterschauen' anzeigen.", "EnableRewatchingNextUp": "Erneut Anschauen in Weiterschauen aktivieren", "ButtonSpace": "Leertaste", "Production": "Produktion", @@ -1654,53 +1654,53 @@ "Bold": "Fett", "LabelTextWeight": "Schriftstärke", "EnableSplashScreen": "Aktiviere den Splash Screen", - "MediaInfoRpuPresentFlag": "DV rpu Flag anwesend", - "MediaInfoDvLevel": "DV Level", - "MediaInfoDvProfile": "DV Profil", - "MediaInfoDvVersionMinor": "DV Nebenversion", - "MediaInfoDvVersionMajor": "DV Hauptversion", - "MediaInfoDoViTitle": "DV Titel", - "LabelVppTonemappingContrastHelp": "Kontrast der VPP-Dynamikkompression erhöhen. Der empfohlene und voreingestellte Wert sind beide 1.", - "LabelVppTonemappingContrast": "VPP Tonemapping Kontrast", - "LabelVppTonemappingBrightnessHelp": "Helligkeit der VPP-Dynamikkompression erhöhen. Der empfohlene Wert ist 16, der voreingestellte 0.", - "LabelVppTonemappingBrightness": "VPP Tonemapping Helligkeit", + "MediaInfoRpuPresentFlag": "Flag 'DV rpu' vorhanden", + "MediaInfoDvLevel": "DV-Level", + "MediaInfoDvProfile": "DV-Profil", + "MediaInfoDvVersionMinor": "DV-Nebenversion", + "MediaInfoDvVersionMajor": "DV-Hauptversion", + "MediaInfoDoViTitle": "DV-Titel", + "LabelVppTonemappingContrastHelp": "Kontrast für VPP-Tone-Mapping verstärken. Der empfohlene und voreingestellte Wert sind beide 1.", + "LabelVppTonemappingContrast": "VPP-Tone-Mapping-Kontrastverstärkung", + "LabelVppTonemappingBrightnessHelp": "Helligkeit für VPP-Tone-Mapping erhöhen. Der empfohlene Wert ist 16, der voreingestellte 0.", + "LabelVppTonemappingBrightness": "VPP-Tone-Mapping-Helligkeitsverstärkung", "ScreenResolution": "Bildschirmauflösung", "RememberSubtitleSelectionsHelp": "Versuche den zum letzten Video ähnlichsten Untertitel zu setzen.", "RememberSubtitleSelections": "Setze den Untertitel auf Basis des letzten Objekts", "RememberAudioSelectionsHelp": "Versuchen die ähnlichste Tonspur zum letzten Video zu setzen.", "RememberAudioSelections": "Tonspur auf Basis des letzten Objekt auswählen", - "LabelMaxVideoResolution": "Maximal erlaubte Video Transcodierungs-Auflösung", - "VideoRangeTypeNotSupported": "Dieses Video-Spektrum ist nicht unterstützt", - "MediaInfoDvBlSignalCompatibilityId": "Dolby Vision BL Signal-Kompatibilitäts-ID", - "MediaInfoBlPresentFlag": "DV BL verfügbar Marker", - "MediaInfoElPresentFlag": "DV el verfügbar Marker", - "MediaInfoVideoRangeType": "Spektrum", - "LabelVideoRangeType": "Spektrum", + "LabelMaxVideoResolution": "Maximal erlaubte Auflösung der Videotranskodierung", + "VideoRangeTypeNotSupported": "Der Dynamikumfang des Videos wird nicht unterstützt", + "MediaInfoDvBlSignalCompatibilityId": "DV bl Signalkompatibilitäts-ID", + "MediaInfoBlPresentFlag": "Flag 'DV bl' vorhanden", + "MediaInfoElPresentFlag": "Flag 'DV el' vorhanden", + "MediaInfoVideoRangeType": "Video-Dynamikumfang", + "LabelVideoRangeType": "Video-Dynamikumfang", "IgnoreDtsHelp": "Die Deaktivierung dieser Option könnte Probleme beheben, z. B. kein Ton auf Filmen mit getrennten Audio- und Video-Streams.", "OptionDateShowAdded": "Datum Serie hinzugefügt", "OptionDateEpisodeAdded": "Datum Episode hinzugefügt", "IgnoreDts": "DTS ignorieren (Dekodierungszeitstempel)", - "MessageRenameMediaFolder": "Beim umbenennen einer Medienbibliothek werden alle Metadaten gelöscht, mit Vorsicht fortfahren.", + "MessageRenameMediaFolder": "Beim Umbenennen einer Medienbibliothek werden alle Metadaten gelöscht, mit Vorsicht fortfahren.", "MessageNoItemsAvailable": "Es sind aktuell keine Einträge verfügbar.", "MessageNoFavoritesAvailable": "Es sind aktuell keine Favoriten verfügbar.", "Unreleased": "Noch nicht veröffentlicht", "EnableCardLayout": "Visuelle CardBox anzeigen", "DownloadAll": "Alle Herunterladen", - "LabelStereoDownmixAlgorithm": "Stereo Downmix Algorithmus", - "StereoDownmixAlgorithmHelp": "Algorithmus um Mehrkanal-Audio zu Stereo-Audio umzuwandeln.", + "LabelStereoDownmixAlgorithm": "Stereo-Downmix-Algorithmus", + "StereoDownmixAlgorithmHelp": "Algorithmus um Mehrkanal-Audio zu Stereo-Audio herunterzumischen.", "Experimental": "Experimentell", "SaveRecordingNFO": "Speichern der EPG-Metadaten in NFO", "SaveRecordingNFOHelp": "Speichern Sie Metadaten von EPG-Anbietern zusammen mit den Medien.", - "ResolutionMatchSource": "Quelle der Übereinstimmung", + "ResolutionMatchSource": "Selbe wie Quelle", "PreferEmbeddedExtrasTitlesOverFileNames": "Eingebettete Titel gegenüber Dateinamen für Extras bevorzugen", "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Extras haben oft denselben eingebetteten Namen wie die übergeordnete Datei. Aktivieren Sie diese Option, um trotzdem eingebettete Titel für sie zu verwenden.", - "SaveRecordingImages": "Speichern der EPG-Bilder der Aufnahme", - "SaveRecordingImagesHelp": "Speichern Sie Bilder von EPG-Anbietern zusammen mit den Medien.", + "SaveRecordingImages": "EPG-Bilder der Aufnahme speichern", + "SaveRecordingImagesHelp": "Speichere Bilder von EPG-Anbietern zusammen mit den Medien.", "SecondarySubtitles": "Sekundäre Untertitel", "HeaderDummyChapter": "Kapitel Bilder", "HeaderRecordingMetadataSaving": "Aufzeichnung von Metadaten", "LabelDummyChapterDuration": "Intervall", - "LabelDummyChapterDurationHelp": "Das Intervall für die Extraktion des Kapitelbildes in Sekunden.", + "LabelDummyChapterDurationHelp": "Das Intervall zwischen Dummy-Kapiteln. Auf 0 setzen, um die Erzeugung von Dummy-Kapiteln zu deaktivieren. Eine Änderung dieses Wertes hat keine Auswirkung auf bestehende Dummy-Kapitel.", "LabelDummyChapterCount": "Limit", "LabelDummyChapterCountHelp": "Die maximale Anzahl von Kapitelbildern, die für jede Mediendatei extrahiert werden.", "LabelChapterImageResolution": "Auflösung", @@ -1717,17 +1717,49 @@ "SubtitleYellow": "Gelb", "Featurette": "Featurette", "Short": "Kurzfilm", - "LabelParallelImageEncodingLimit": "Grenze der parallelen Bildkodierung", - "LabelParallelImageEncodingLimitHelp": "Maximale Anzahl von Bildkodierungen, die parallel ausgeführt werden können. Wenn Sie diesen Wert auf 0 setzen, wird eine Grenze auf der Grundlage Ihrer Systemspezifikationen gewählt.", + "LabelParallelImageEncodingLimit": "Limit der parallelen Bildkodierung", + "LabelParallelImageEncodingLimitHelp": "Maximale Anzahl von Bildkodierungen, die parallel ausgeführt werden können. Wenn dieser Wert auf 0 gesetzt wird, wird eine Obergrenze auf der Grundlage deiner Systemspezifikationen gewählt.", "HeaderPerformance": "Leistung", - "LabelEnableAudioVbr": "Aktiviere Audio-Konvertierung mit variabler Bitrate (VBR)", + "LabelEnableAudioVbr": "Aktiviere Audiokodierung mit variabler Bitrate (VBR)", "LabelEnableAudioVbrHelp": "Eine variable Bitrate bietet bessere Qualität im Vergleich zu einer konstanten, kann jedoch in manchen Fällen zu Problemen beim Buffering und der Kompatibilität führen.", "Select": "Auswählen", - "LabelTonemappingMode": "Dynamikkompressionsmodus", - "TonemappingModeHelp": "Dynamikkompressionsmodus auswählen. Falls etwas überbelichtet ist, versuche in den RGB-Modus zu wechseln.", + "LabelTonemappingMode": "Tone-Mapping-Modus", + "TonemappingModeHelp": "Tone-Mapping-Modus auswählen. Falls es zu Überbelichtungen kommt, versuche in den RGB-Modus zu wechseln.", "MenuOpen": "Menü öffnen", "MenuClose": "Menü schließen", "UserMenu": "Benutzermenü", "Studio": "Studio", - "AllowCollectionManagement": "Dieser Benutzer darf Sammlungen verwalten" + "AllowCollectionManagement": "Dieser Benutzer darf Sammlungen verwalten", + "PasswordRequiredForAdmin": "Für Admin Konten wird ein Passwort benötigt.", + "LabelEnableLUFSScan": "LUFS-Scan aktivieren", + "LabelSyncPlayNoGroups": "Keine Gruppen verfügbar", + "LabelEnableLUFSScanHelp": "Aktiviert den LUFS-Scan für Musik (Dies erfordert mehr Zeit und Ressourcen).", + "Notifications": "Benachrichtigungen", + "NotificationsMovedMessage": "Die Benachrichtigungsfunktion wurde zum Webhook Plugin verschoben.", + "EnableAudioNormalizationHelp": "Die Audionormalisierung fügt eine konstante Verstärkung hinzu, um den Durchschnitt auf einem gewünschten Pegel zu halten (-18 dB).", + "EnableAudioNormalization": "Audionormalisierung", + "GetThePlugin": "Plugin laden", + "HeaderConfirmRepositoryInstallation": "Plugin-Repository-Installation bestätigen", + "LabelDeveloper": "Entwickler", + "MessageRepositoryInstallDisclaimer": "WARNUNG: Eine Drittanbieterquelle für Plugins kann instabilen oder schadhaften Code beinhalten und kann sich zu jedem Zeitpunkt ändern. Bitte installiere nur Quellen von Autoren, denen du vertraust.", + "PleaseConfirmRepositoryInstallation": "Bitte klicke auf OK um zu bestätigen, dass du den obigen Text gelesen hast und mit Plugin-Quellen-Installation fortfahren willst.", + "Unknown": "Unbekannt", + "LabelDate": "Datum", + "LabelLevel": "Level", + "LabelMediaDetails": "Mediendetails", + "LabelSystem": "System", + "LogLevel.Trace": "Trace", + "LogLevel.Debug": "Debug", + "LogLevel.Information": "Information", + "LogLevel.Warning": "Warnung", + "LogLevel.Error": "Fehler", + "LogLevel.Critical": "Kritisch", + "LogLevel.None": "Nichts", + "HeaderEpisodesStatus": "Episodenstatus", + "AllowSegmentDeletion": "Segmente löschen", + "AllowSegmentDeletionHelp": "Alte Segmente löschen, nachdem sie zum Client gesendet wurden. Damit muss nicht die gesamte transkodierte Datei zwischengespeichert werden. Sollten Wiedergabeprobleme auftreten, kann diese Einstellung deaktiviert werden.", + "LabelThrottleDelaySeconds": "Limitieren nach", + "LabelThrottleDelaySecondsHelp": "Zeit, in Sekunden, nach der die Transkodierung limitiert wird. Muss groß genug sein um dem Client eine problemlose Wiedergabe zu ermöglichen. Funktioniert nur wenn \"Transkodierung drosseln\" aktiviert ist.", + "LabelSegmentKeepSeconds": "Zeit um Segmente zu behalten", + "LabelSegmentKeepSecondsHelp": "Zeit, in Sekunden, in der Segmente nicht überschrieben werden dürfen. Muss größer sein als \"Limitieren nach\". Funktioniert nur wenn \"Segmente löschen\" aktiviert ist." } diff --git a/src/strings/el.json b/src/strings/el.json index 5c767fb938..93c12cebf7 100644 --- a/src/strings/el.json +++ b/src/strings/el.json @@ -23,7 +23,7 @@ "AlwaysPlaySubtitlesHelp": "Οι υπότιτλοι που ταιριάζουν με τις προτιμήσεις γλώσσας θα φορτωθούν ανεξάρτητα από τη γλώσσα του ήχου.", "AnyLanguage": "Οποιαδήποτε γλώσσα", "Anytime": "Οποτεδήποτε", - "AroundTime": "Περίπου", + "AroundTime": "Περίπου {0}", "Art": "Τέχνη", "Artists": "Καλλιτέχνες", "AsManyAsPossible": "Οσο το δυνατον περισσοτερα", diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index c9827ad23b..6aeccaaaa0 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -584,12 +584,12 @@ "MinutesBefore": "minutes before", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content added going forward. To refresh existing content, open the detail screen and click the 'Refresh' button, or do bulk refreshes using the 'Metadata Manager'.", "MetadataManager": "Metadata Manager", - "MessagePluginInstallDisclaimer": "Plugins built by community members are a great way to enhance your experience with additional features and benefits. Before installing, please be aware of the effects they may have on your server, such as longer library scans, additional background processing, and decreased system stability.", + "MessagePluginInstallDisclaimer": "WARNING: Installing a third party plugin carries risks. It may contain unstable or malicious code, and may change at any time. Only install plugins from authors that you trust, and please be aware of the potential effects it may have, including external service queries, longer library scans, or additional background processing.", "MessagePluginConfigurationRequiresLocalAccess": "To configure this plugin please sign in to your local server directly.", "MessagePleaseWait": "Please wait. This may take a minute.", "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", "MessagePlayAccessRestricted": "Playback of this content is currently restricted. Please contact your server administrator for more information.", - "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the Easy PIN codes that were used to do the reset.", + "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the PIN codes that were used to do the reset.", "MessageNothingHere": "Nothing here.", "MessageNoTrailersFound": "Install the trailers channel to enhance your cinema experience by adding a library of internet trailers.", "MessageNoServersAvailable": "No servers have been found using the automatic server discovery.", @@ -1517,7 +1517,7 @@ "MessagePlaybackError": "There was an error playing this file on your Google Cast receiver.", "MessageChromecastConnectionError": "Your Google Cast receiver is unable to contact the Jellyfin server. Please check the connection and try again.", "Framerate": "Framerate", - "DirectPlayHelp": "The source file is entirely compatible with this client, and the session is receiving the file without modifications.", + "DirectPlayHelp": "The source file is entirely compatible with this client and the session is receiving the file without modifications.", "EnableGamepadHelp": "Listen for input from any connected controllers. (Requires: 'TV' Display Mode)", "LabelEnableGamepad": "Enable Gamepad", "Controls": "Controls", @@ -1737,5 +1737,29 @@ "GetThePlugin": "Get the Plugin", "Notifications": "Notifications", "NotificationsMovedMessage": "The notifications functionality has moved to the Webhook plugin.", - "PasswordRequiredForAdmin": "A password is required for admin accounts." + "PasswordRequiredForAdmin": "A password is required for admin accounts.", + "LabelSyncPlayNoGroups": "No groups available", + "HeaderConfirmRepositoryInstallation": "Confirm Plugin Repository Installation", + "LabelDeveloper": "Developer", + "MessageRepositoryInstallDisclaimer": "WARNING: Installing a third party plugin repository carries risks. It may contain unstable or malicious code, and may change at any time. Only install repositories from authors that you trust.", + "PleaseConfirmRepositoryInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin repository installation.", + "Unknown": "Unknown", + "LabelDate": "Date", + "LabelLevel": "Level", + "LabelMediaDetails": "Media details", + "LabelSystem": "System", + "LogLevel.Trace": "Trace", + "LogLevel.Debug": "Debug", + "LogLevel.Information": "Information", + "LogLevel.Error": "Error", + "LogLevel.Warning": "Warning", + "LogLevel.Critical": "Critical", + "LogLevel.None": "None", + "HeaderEpisodesStatus": "Episodes Status", + "AllowSegmentDeletion": "Delete segments", + "AllowSegmentDeletionHelp": "Delete old segments after they have been sent to the client. This prevents having to store the entire transcoded file on disk. Will only work with throttling enabled. Turn this off if you experience playback issues.", + "LabelThrottleDelaySeconds": "Throttle after", + "LabelThrottleDelaySecondsHelp": "Time in seconds after which the transcoder will be throttled. Must be large enough for the client to maintain a healthy buffer. Only works if throttling is enabled.", + "LabelSegmentKeepSeconds": "Time to keep segments", + "LabelSegmentKeepSecondsHelp": "Time in seconds for which segments should be kept before they are overwritten. Must be greater than \"Throttle after\". Only works if segment deletion is enabled." } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index d423c2ce32..983b7c8f8d 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -25,6 +25,12 @@ "AllowCollectionManagement": "Allow this user to manage collections", "AllowFfmpegThrottling": "Throttle Transcodes", "AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.", + "AllowSegmentDeletion": "Delete segments", + "AllowSegmentDeletionHelp": "Delete old segments after they have been sent to the client. This prevents having to store the entire transcoded file on disk. Will only work with throttling enabled. Turn this off if you experience playback issues.", + "LabelThrottleDelaySeconds": "Throttle after", + "LabelThrottleDelaySecondsHelp": "Time in seconds after which the transcoder will be throttled. Must be large enough for the client to maintain a healthy buffer. Only works if throttling is enabled.", + "LabelSegmentKeepSeconds": "Time to keep segments", + "LabelSegmentKeepSecondsHelp": "Time in seconds for which segments should be kept before they are overwritten. Must be greater than \"Throttle after\". Only works if segment deletion is enabled.", "AllowHWTranscodingHelp": "Allow the tuner to transcode streams on the fly. This may help reduce transcoding required by the server.", "AllowMediaConversion": "Allow media conversion", "AllowMediaConversionHelp": "Grant or deny access to the convert media feature.", @@ -99,7 +105,6 @@ "ButtonRefreshGuideData": "Refresh Guide Data", "ButtonRemove": "Remove", "ButtonRename": "Rename", - "ButtonResetEasyPassword": "Reset Easy PIN code", "ButtonResume": "Resume", "ButtonRevoke": "Revoke", "ButtonScanAllLibraries": "Scan All Libraries", @@ -189,7 +194,7 @@ "Director": "Director", "Directors": "Directors", "DirectPlaying": "Direct playing", - "DirectPlayHelp": "The source file is entirely compatible with this client, and the session is receiving the file without modifications.", + "DirectPlayHelp": "The source file is entirely compatible with this client and the session is receiving the file without modifications.", "DirectStreamHelp1": "The video stream is compatible with the device, but has an incompatible audio format (DTS, Dolby TrueHD, etc.) or number of audio channels. The video stream will be repackaged losslessly on the fly before being sent to the device. Only the audio stream will be transcoded.", "DirectStreamHelp2": "Power consumed by direct streaming usually depends on the audio profile. Only the video stream is lossless.", "DirectStreaming": "Direct streaming", @@ -211,7 +216,6 @@ "DownloadsValue": "{0} downloads", "DrmChannelsNotImported": "Channels with DRM will not be imported.", "DropShadow": "Drop Shadow", - "EasyPasswordHelp": "Your Easy PIN code is used for offline access on supported clients and can also be used for easy in-network sign in.", "Edit": "Edit", "EditImages": "Edit images", "EditMetadata": "Edit metadata", @@ -337,6 +341,7 @@ "HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct playback.", "HeaderConfigureRemoteAccess": "Set up Remote Access", "HeaderConfirmPluginInstallation": "Confirm Plugin Installation", + "HeaderConfirmRepositoryInstallation": "Confirm Plugin Repository Installation", "HeaderConfirmProfileDeletion": "Confirm Profile Deletion", "HeaderConfirmRevokeApiKey": "Revoke API Key", "HeaderConnectionFailure": "Connection Failure", @@ -348,7 +353,6 @@ "HeaderContinueReading": "Continue Reading", "HeaderCustomDlnaProfiles": "Custom Profiles", "HeaderDateIssued": "Date Issued", - "HeaderDebugging": "Debugging and Tracing", "HeaderDefaultRecordingSettings": "Default Recording Settings", "HeaderDeleteDevice": "Delete Device", "HeaderDeleteDevices": "Delete All Devices", @@ -365,10 +369,10 @@ "HeaderDownloadSync": "Download & Sync", "HeaderDummyChapter": "Chapter Images", "HeaderDVR": "DVR", - "HeaderEasyPinCode": "Easy PIN Code", "HeaderEditImages": "Edit Images", "HeaderEnabledFields": "Enabled Fields", "HeaderEnabledFieldsHelp": "Uncheck a field to lock it and prevent its data from being changed.", + "HeaderEpisodesStatus": "Episodes Status", "HeaderError": "Error", "HeaderExternalIds": "External IDs", "HeaderFeatureAccess": "Feature access", @@ -425,7 +429,6 @@ "HeaderPaths": "Paths", "HeaderPerformance": "Performance", "HeaderPhotoAlbums": "Photo Albums", - "HeaderPinCodeReset": "Reset Easy PIN Code", "HeaderPlayAll": "Play All", "HeaderPlayback": "Media playback", "HeaderPlaybackError": "Playback Error", @@ -558,8 +561,6 @@ "LabelAuthProvider": "Authentication Provider", "LabelAutomaticallyAddToCollection": "Automatically add to collection", "LabelAutomaticallyAddToCollectionHelp": "When at least 2 movies have the same collection name, they will be automatically added to the collection.", - "LabelAutoDiscoveryTracing": "Enable Auto Discovery tracing.", - "LabelAutoDiscoveryTracingHelp": "When enabled, packets received on the auto discovery port will be logged.", "LabelAutomaticallyRefreshInternetMetadataEvery": "Automatically refresh metadata from the internet", "LabelAutomaticDiscovery": "Enable Auto Discovery", "LabelAutomaticDiscoveryHelp": "Allow applications to automatically detect Jellyfin by using UDP port 7359.", @@ -602,6 +603,7 @@ "LabelCustomDeviceDisplayNameHelp": "Supply a custom display name or leave empty to use the name reported by the device.", "LabelCustomRating": "Custom rating", "LabelDashboardTheme": "Server Dashboard theme", + "LabelDate": "Date", "LabelDateAdded": "Date added", "LabelDateAddedBehavior": "Date added behavior for new content", "LabelDateAddedBehaviorHelp": "If a metadata value is present, it will always be used before either of these options.", @@ -612,6 +614,7 @@ "LabelDefaultUser": "Default user", "LabelDefaultUserHelp": "Determine which user library should be displayed on connected devices. This can be overridden for each device using profiles.", "LabelDeinterlaceMethod": "Deinterlacing method", + "LabelDeveloper": "Developer", "LabelDeviceDescription": "Device description", "LabelDidlMode": "DIDL mode", "LabelDisableCustomCss": "Disable custom CSS code for theming/branding provided from the server.", @@ -635,7 +638,6 @@ "LabelChapterImageResolution": "Resolution", "LabelChapterImageResolutionHelp": "The resolution of the extracted chapter images. Changing this will have no effect on existing dummy chapters.", "LabelDynamicExternalId": "{0} Id", - "LabelEasyPinCode": "Easy PIN code", "LabelEmbedAlbumArtDidl": "Embed album art in DIDL", "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for getting the album art. Others may fail to play with this option enabled.", "LabelEnableAudioVbr": "Enable VBR audio encoding", @@ -665,8 +667,6 @@ "LabelEnableRealtimeMonitorHelp": "Changes to files will be processed immediately on supported file systems.", "LabelEnableSingleImageInDidlLimit": "Limit to single embedded image", "LabelEnableSingleImageInDidlLimitHelp": "Some devices will not render properly if multiple images are embedded within DIDL.", - "LabelEnableSSDPTracing": "Enable SSDP Tracing", - "LabelEnableSSDPTracingHelp": "Enable details SSDP network tracing to be logged.
WARNING: This will cause serious performance degradation.", "LabelEncoderPreset": "Encoding preset", "LabelEndDate": "End date", "LabelEpisodeNumber": "Episode number", @@ -691,8 +691,6 @@ "LabelHardwareAccelerationType": "Hardware acceleration", "LabelHardwareAccelerationTypeHelp": "Hardware acceleration requires additional configuration.", "LabelHardwareEncoding": "Hardware encoding", - "LabelHDHomerunPortRange": "HDHomeRun port range", - "LabelHDHomerunPortRangeHelp": "Restricts the HDHomeRun UDP port range to this value. (Default is 1024 - 645535).", "LabelHomeNetworkQuality": "Home network quality", "LabelHomeScreenSectionValue": "Home screen section {0}", "LabelHttpsPort": "Local HTTPS port number", @@ -704,8 +702,6 @@ "LabelImageFetchersHelp": "Enable and rank your preferred image fetchers in order of priority.", "LabelImageType": "Image type", "LabelImportOnlyFavoriteChannels": "Restrict to channels marked as favorite", - "LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my Easy PIN code", - "LabelInNetworkSignInWithEasyPasswordHelp": "Use the Easy PIN code to sign in from clients within your local network. Your regular password will only be needed away from home. If the PIN code is left blank, you won't need a password within your home network.", "LabelInternetQuality": "Internet quality", "LabelIsForced": "Forced", "LabelKeepUpTo": "Keep up to", @@ -723,11 +719,13 @@ "LabelKodiMetadataUserHelp": "Save watch data to NFO files for other applications to use.", "LabelLanguage": "Language", "LabelLanNetworks": "LAN networks", + "LabelLevel": "Level", "LabelLibraryPageSize": "Library page size", "LabelLibraryPageSizeHelp": "Set the amount of items to show on a library page. Set to 0 in order to disable paging.", "LabelMaxDaysForNextUp": "Max days in 'Next Up'", "LabelMaxDaysForNextUpHelp": "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.", "LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution", + "LabelMediaDetails": "Media details", "LabelLineup": "Lineup", "LabelLocalCustomCss": "Custom CSS code for styling which applies to this client only. You may want to disable server custom CSS code.", "LabelLocalHttpServerPortNumber": "Local HTTP port number", @@ -882,8 +880,6 @@ "LabelSource": "Source", "LabelSpecialSeasonsDisplayName": "Special season display name", "LabelSportsCategories": "Sports categories", - "LabelSSDPTracingFilter": "SSDP Filter", - "LabelSSDPTracingFilterHelp": "Optional IP address upon which to filter the logged SSDP traffic.", "LabelStable": "Stable", "LabelStartWhenPossible": "Start when possible", "LabelStatus": "Status", @@ -929,6 +925,7 @@ "LabelSyncPlaySettingsSpeedToSyncHelp": "Sync correction method that consist in speeding up the playback. Sync Correction must be enabled.", "LabelSyncPlaySettingsSkipToSync": "Enable SkipToSync", "LabelSyncPlaySettingsSkipToSyncHelp": "Sync correction method that consist in seeking to the estimated position. Sync Correction must be enabled.", + "LabelSystem": "System", "LabelTag": "Tag", "LabelTagline": "Tagline", "LabelTextBackgroundColor": "Text background color", @@ -965,8 +962,6 @@ "LabelType": "Type", "LabelTypeMetadataDownloaders": "Metadata downloaders ({0})", "LabelTypeText": "Text", - "LabelUDPPortRange": "UDP Communication Range", - "LabelUDPPortRangeHelp": "Restrict Jellyfin to use this port range when making UDP connections. (Default is 1024 - 645535).
Note: Certain function require fixed ports that may be outside of this range.", "LabelUnstable": "Unstable", "LabelUser": "User", "LabelUserAgent": "User agent", @@ -1007,6 +1002,13 @@ "LiveBroadcasts": "Live broadcasts", "LiveTV": "Live TV", "Localization": "Localization", + "LogLevel.Trace": "Trace", + "LogLevel.Debug": "Debug", + "LogLevel.Information": "Information", + "LogLevel.Warning": "Warning", + "LogLevel.Error": "Error", + "LogLevel.Critical": "Critical", + "LogLevel.None": "None", "Logo": "Logo", "Lyricist": "Lyricist", "ManageLibrary": "Manage library", @@ -1094,16 +1096,17 @@ "MessageNoServersAvailable": "No servers have been found using the automatic server discovery.", "MessageNothingHere": "Nothing here.", "MessageNoTrailersFound": "Install the trailers channel to enhance your movie experience by adding a library of internet trailers.", - "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the Easy PIN codes that were used to do the reset.", + "MessagePasswordResetForUsers": "The following users have had their passwords reset. They can now sign in with the PIN codes that were used to do the reset.", "MessagePlayAccessRestricted": "Playback of this content is currently restricted. Please contact your server administrator for more information.", "MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.", "MessagePleaseWait": "Please wait. This may take a minute.", "MessagePluginConfigurationRequiresLocalAccess": "To set up this plugin please sign in to your local server directly.", - "MessagePluginInstallDisclaimer": "Plugins built by community members are a great way to enhance your experience with additional features and benefits. Before installing, please be aware of the effects they may have on your server, such as longer library scans, additional background processing, and decreased system stability.", + "MessagePluginInstallDisclaimer": "WARNING: Installing a third party plugin carries risks. It may contain unstable or malicious code, and may change at any time. Only install plugins from authors that you trust, and please be aware of the potential effects it may have, including external service queries, longer library scans, or additional background processing.", "MessagePluginInstalled": "The plugin has been successfully installed. The server will need to be restarted for changes to take effect.", "MessagePluginInstallError": "An error occurred while installing the plugin.", "MessageReenableUser": "See below to reenable", "MessageRenameMediaFolder": "Renaming a media library will cause all metadata to be lost, proceed with caution.", + "MessageRepositoryInstallDisclaimer": "WARNING: Installing a third party plugin repository carries risks. It may contain unstable or malicious code, and may change at any time. Only install repositories from authors that you trust.", "MessageSent": "Message sent.", "MessageSyncPlayCreateGroupDenied": "Permission required to create a group.", "MessageSyncPlayDisabled": "SyncPlay disabled.", @@ -1301,8 +1304,6 @@ "Photo": "Photo", "Photos": "Photos", "PictureInPicture": "Picture in picture", - "PinCodeResetComplete": "The Easy PIN code has been reset.", - "PinCodeResetConfirmation": "Are you sure you wish to reset the Easy PIN code?", "PlaceFavoriteChannelsAtBeginning": "Place favorite channels at the beginning", "Play": "Play", "PlayAllFromHere": "Play all from here", @@ -1318,6 +1319,7 @@ "PlayNextEpisodeAutomatically": "Play next episode automatically", "PleaseAddAtLeastOneFolder": "Please add at least one folder to this library by clicking the '+' button in 'Folders' section.", "PleaseConfirmPluginInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin installation.", + "PleaseConfirmRepositoryInstallation": "Please click OK to confirm you've read the above and wish to proceed with the plugin repository installation.", "PleaseEnterNameOrId": "Please enter a name or an external ID.", "PleaseRestartServerName": "Please restart Jellyfin on {0}.", "PleaseSelectTwoItems": "Please select at least two items.", @@ -1710,5 +1712,6 @@ "MediaInfoDvBlSignalCompatibilityId": "DV bl signal compatibility id", "Unreleased": "Not yet released", "LabelTonemappingMode": "Tone mapping mode", - "TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode." + "TonemappingModeHelp": "Select the tone mapping mode. If you experience blown out highlights try switching to the RGB mode.", + "Unknown": "Unknown" } diff --git a/src/strings/eo.json b/src/strings/eo.json index eefb15d5ac..39986599d3 100644 --- a/src/strings/eo.json +++ b/src/strings/eo.json @@ -181,7 +181,7 @@ "MessageNoNextUpItems": "Nenio estis trovita. Komencu spekti viajn seriojn!", "Shows": "Serioj", "Arranger": "Aranĝanto", - "AroundTime": "Ĉirkaŭe", + "AroundTime": "Ĉirkaŭe {0}", "Anytime": "Iam ajn", "AnyLanguage": "Ajna Lingvo", "AgeValue": "({0} jarojn aĝa)", diff --git a/src/strings/es-ar.json b/src/strings/es-ar.json index d7abbb58a5..3b18fb60e7 100644 --- a/src/strings/es-ar.json +++ b/src/strings/es-ar.json @@ -1658,7 +1658,7 @@ "LabelVppTonemappingContrast": "Ganancia de contraste en mapeo de tono VPP", "LabelVppTonemappingBrightness": "Ganancia de brillo en mapeo de tono VPP", "RememberSubtitleSelections": "Establecer subtitulo basado en el item anterior", - "RememberAudioSelectionsHelp": "Intentar establecer la pista de audio lo mas cercano al ultimo video.", + "RememberAudioSelectionsHelp": "Intentar seleccionar la pista de audio con la más parecida al anterior vídeo.", "RememberAudioSelections": "Establecer pista de audio basada en el item anterior", "MixedMoviesShows": "Peliculas y Series", "LabelMaxVideoResolution": "Maxima resolución de transcodeo permitida", @@ -1703,7 +1703,7 @@ "LabelEnableAudioVbr": "Habilitar la codificación de audio VBR", "LabelEnableAudioVbrHelp": "La tasa de bits variable ofrece una mejor relación entre calidad y tasa de bits promedio, pero en algunos casos raros puede causar problemas de almacenamiento en búfer y compatibilidad.", "LabelParallelImageEncodingLimit": "Límite de codificación de imágenes en paralelo", - "ResolutionMatchSource": "Fuente de coincidencia", + "ResolutionMatchSource": "Coincidir fuente", "HeaderPerformance": "Rendimiento", "Featurette": "Mediometraje", "SubtitleBlack": "Negro", diff --git a/src/strings/es-mx.json b/src/strings/es-mx.json index 2854d6ec4e..cff4b631aa 100644 --- a/src/strings/es-mx.json +++ b/src/strings/es-mx.json @@ -800,7 +800,7 @@ "MessagePleaseEnsureInternetMetadata": "Por favor, asegúrate de que la descarga de metadatos de Internet está habilitada.", "MessagePleaseWait": "Por favor, espera. Esto podría tomar un minuto.", "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento por favor, inicie sesión en el servidor local directamente.", - "MessagePluginInstallDisclaimer": "Los complementos desarrollados por miembros de la comunidad son una gran forma de mejorar tu experiencia con características y beneficios adicionales. Antes de instalar, por favor, conoce el impacto que pueden ocasionar en tu servidor, tales como escaneo más largo de bibliotecas, procesamiento en segundo plano adicional y reducción de la estabilidad del sistema.", + "MessagePluginInstallDisclaimer": "ADVERTENCIA: Instalar un complemento de terceros conlleva riesgos. Puede contener código inestable o malicioso, y puede cambiar en cualquier momento. Solo instale complementos de autores en los que confíe, y tenga en cuenta los posibles efectos que puede tener, incluyendo consultas a servicios externos, análisis de bibliotecas más largos, o procesamiento en segundo plano adicional.", "MessageReenableUser": "Ver abajo para volver a habilitar", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Las siguientes ubicaciones de medios se removerán de tu biblioteca", "MessageUnableToConnectToServer": "No podemos conectarnos al servidor seleccionado en este momento. Por favor, asegúrate de que está funcionando e inténtalo de nuevo.", @@ -1371,7 +1371,7 @@ "LabelTonemappingRange": "Rango del mapeo de tonos", "TonemappingAlgorithmHelp": "El mapeo de tonos se puede ajustar con precisión. Si no está familiarizado con estas opciones, mantenga las predeterminadas. El valor recomendado es Hable.", "LabelTonemappingAlgorithm": "Selecciona el algoritmo de mapeo de tonos", - "AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video de HDR a SDR mientras mantiene los detalles y colores de la imagen, que son información muy importante para representar la escena original. Actualmente solo funciona con vídeos HDR10 o HLG. Esto requiere la librería OpenCL o CUDA correspondiente.", + "AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video de HDR a SDR mientras mantiene los detalles y colores de la imagen, que son información muy importante para representar la escena original. Actualmente solo funciona con vídeos 10bit HDR10, HLG y DoVi. Esto requiere la librería OpenCL o CUDA correspondiente.", "EnableTonemapping": "Habilitar mapeo de tonos", "LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se utiliza para el mapeo de tonos. El lado izquierdo del punto es el número de plataforma y el lado derecho es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere el archivo de la aplicación FFmpeg que contiene el método de aceleración de hardware OpenCL.", "LabelOpenclDevice": "Dispositivo OpenCL", @@ -1508,7 +1508,7 @@ "LabelMaxAudiobookResumeHelp": "Se asume que los títulos están completamente reproducidos si se detienen cuando el tiempo restante es inferior a este valor.", "LabelMaxAudiobookResume": "Minutos restantes para reanudación de audiolibros", "Framerate": "Cuadros por segundo", - "DirectPlayHelp": "El archivo de origen es totalmente compatible con este cliente y la sesión recibe el archivo sin modificaciones.", + "DirectPlayHelp": "El archivo de origen es totalmente compatible con este cliente, y la sesión recibe el archivo sin modificaciones.", "HeaderContinueReading": "Continuar Leyendo", "VideoFramerateNotSupported": "La velocidad de fotogramas del video no está soportada", "VideoBitDepthNotSupported": "La profundidad de bits del video no está soportada", @@ -1638,5 +1638,119 @@ "MessageNoItemsAvailable": "No hay elementos disponibles.", "MessageNoFavoritesAvailable": "No hay favoritos disponibles.", "MessageRenameMediaFolder": "Renombrar una biblioteca resultará en la pérdida de metadatos, proceda con precaución.", - "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en éste momento. Por favor contacta a tu administrador del servidor para más información." + "MessageUnauthorizedUser": "No estás autorizado para acceder al servidor en éste momento. Por favor contacta a tu administrador del servidor para más información.", + "LabelEnableAudioVbrHelp": "La tasa de bits variable ofrece una mejor relación entre calidad y tasa de bits promedio, pero en algunos casos raros puede causar problemas de almacenamiento de búfer y compatibilidad.", + "IntelLowPowerEncHelp": "La codificación de bajo consumo puede mantener una sincronización CPU-GPU innecesaria. En Linux, deben deshabilitarse si el firmware i915 HuC no está configurado.", + "EnableAudioNormalization": "Normalización de audio", + "GetThePlugin": "Obtener el complemento", + "SelectAll": "Seleccionar Todo", + "Clip": "Mediometraje", + "Trailer": "Trailer", + "PasswordRequiredForAdmin": "Una contraseña es requerida para cuentas de administrador.", + "RememberAudioSelectionsHelp": "Intentar seleccionar la pista de audio con la más parecida al anterior vídeo.", + "SubtitleCyan": "Cian", + "StoryArc": "Arco argumental", + "DeletedScene": "Escena eliminada", + "Interview": "Entrevista", + "Studio": "Estudio", + "SubtitleLightGray": "Gris Claro", + "SubtitleMagenta": "Magenta", + "NotificationsMovedMessage": "La funcionalidad de notificaciones se ha movido al plugin Webhook.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Prefiere títulos incrustados sobre nombres de archivo para extras", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Los extras a menudo tienen el mismo nombre agregado que el origen, marque esto para usar títulos incrustados para ellos de todos modos.", + "EnableIntelLowPowerH264HwEncoder": "Habilitar el codificador hardware H.264 de bajo consumo de Intel", + "EnableIntelLowPowerHevcHwEncoder": "Habilitar el codificador hardware HEVC de bajo consumo de Intel", + "Scene": "Escena", + "EnableEnhancedNvdecDecoderHelp": "Implementación experimental de NVDEC, no habilite esta opción a menos que encuentre errores de decodificación.", + "LabelDummyChapterDuration": "Intervalo", + "LabelChapterImageResolution": "Resolución", + "LabelDate": "Fecha", + "LabelDeveloper": "Desarrollador", + "LabelLevel": "Nivel", + "LabelMediaDetails": "Detalles de medios", + "LabelSystem": "Sistema", + "LogLevel.Trace": "Rastreo", + "LogLevel.Debug": "Depurar", + "LogLevel.Information": "Información", + "LogLevel.Warning": "Advertencia", + "LogLevel.Error": "Error", + "LogLevel.Critical": "Crítico", + "LogLevel.None": "Ningún", + "StereoDownmixAlgorithmHelp": "Algoritmo utilizado para mezclar audio multicanal a estéreo.", + "SubtitleRed": "Rojo", + "LabelHardwareEncodingOptions": "Opciones de codificación por hardware", + "UnknownAudioStreamInfo": "La información de la pista de audio es desconocida", + "DirectPlayError": "Hubo un error al iniciar la reproducción directa", + "Featurette": "Reportaje extra", + "Short": "Corto", + "BehindTheScenes": "Detrás de cámaras", + "Unreleased": "Todavía no transmitido", + "Unknown": "Desconocido", + "LabelVppTonemappingBrightnessHelp": "Aplicar ganancia de brillo en mapeo de tonos VPP. Los valores recomendados y por defecto son 16 y 0.", + "LabelVppTonemappingBrightness": "Ganancia de brillo de mapeo de tonos VPP", + "VideoRangeTypeNotSupported": "El tipo de rango del video no es compatible", + "LabelVideoRangeType": "Tipo de rango de vídeo", + "MediaInfoDvBlSignalCompatibilityId": "ID de compatibilidad de señal DV bl", + "MediaInfoElPresentFlag": "Indicador preestablecido del el DV", + "LabelSyncPlayNoGroups": "No hay grupos disponibles", + "Notifications": "Notificaciones", + "SaveRecordingNFOHelp": "Guardar metadatos del proveedor de listados EPG junto con los archivos multimedia.", + "VideoBitrateNotSupported": "La taza de bits del vídeo no es compatible", + "UnknownVideoStreamInfo": "La información de transmisión de video es desconocida", + "EnableAudioNormalizationHelp": "La normalización de audio añadirá una ganancia constante para mantener la media en el nivel deseado (-18dB)", + "HeaderPerformance": "Rendimiento", + "LabelEnableLUFSScan": "Habilitar escaneó LUFS", + "LabelEnableLUFSScanHelp": "Habilitar escaneó LUFS para musica (Esto tomara más tiempo y usará más recursos).", + "MenuClose": "Cerrar Menu", + "MenuOpen": "Abrir Menu", + "SubtitleBlack": "Negro", + "UserMenu": "Menu de usuario", + "LabelVppTonemappingContrast": "Ganancia de contraste con el mapeado de tono VPP", + "Sample": "Muestra", + "MediaInfoDvLevel": "Nivel DV", + "LabelEnableAudioVbr": "Habilitar codificación de audio VBR", + "SubtitleYellow": "Amarillo", + "SubtitleWhite": "Blanco", + "MediaInfoBlPresentFlag": "Indicador preestablecido del bl DV", + "LabelParallelImageEncodingLimit": "Límite de codificación de imágenes en paralelo", + "SaveRecordingImagesHelp": "Guardar imágenes del proveedor de listados EPG junto con los archivos multimedia.", + "SaveRecordingImages": "Guardar grabación de imágenes EPG", + "SubtitleBlue": "Azúl", + "SubtitleGray": "Gris", + "SubtitleGreen": "Verde", + "ThemeVideo": "Video temático", + "Select": "Seleccionar", + "LabelTonemappingMode": "Modo de mapeo de tono", + "MediaInfoRpuPresentFlag": "Bandera preestablecida de DV rpu", + "ThemeSong": "Tema Musical", + "ScreenResolution": "Resolución de Pantalla", + "Experimental": "Experimental", + "AudioIsExternal": "La pista de audio es externa", + "MediaInfoDvVersionMajor": "Version principal DV", + "MediaInfoDoViTitle": "Título DV", + "LabelStereoDownmixAlgorithm": "Algoritmo de mezcla estéreo", + "ContainerBitrateExceedsLimit": "La taza de bits del vídeo supera el límite", + "PreferSystemNativeHwDecoder": "Preferir decodificadores de hardware DXVA o VA-API nativos del sistema operativo", + "MediaInfoDvProfile": "Perfil DV", + "HeaderRecordingMetadataSaving": "Grabando metadatos", + "EnableSplashScreen": "Habilitar pantalla de bienvenida", + "HeaderConfirmRepositoryInstallation": "Confirma la instalación del repositorio de complementos", + "HeaderDummyChapter": "Imágenes del capítulo", + "MessageRepositoryInstallDisclaimer": "ADVERTENCIA: Instalar un complemento de terceros conlleva riesgos. Puede contener código inestable o malicioso, y puede cambiar en cualquier momento. Solo instale complementos de autores en los que confíe.", + "PleaseConfirmRepositoryInstallation": "Por favor, haga clic en OK para confirmar que ha leído lo anterior y desea continuar con la instalación del repositorio de complementos.", + "RememberSubtitleSelections": "Establecer subtitulo basándose en el elemento anterior", + "RememberSubtitleSelectionsHelp": "Intenta establecer la pista de subtítulos en la coincidencia más cercana al último video.", + "RememberAudioSelections": "Establecer pista de audio basándose en el elemento anterior", + "AllowCollectionManagement": "Permitir que este usuario administre colecciones", + "DownloadAll": "Descargar Todo", + "LabelDummyChapterDurationHelp": "Intervalo entre capítulos dummy. Dejar a 0 para deshabilitar la generación de capítulos dummy. Cambiar esta opción no tendrá efecto para capítulos dummy ya existentes.", + "LabelChapterImageResolutionHelp": "Resolución de las imágenes extraídas de los capítulos. Cambiar esta opción no tendrá efecto sobre capítulos dummy ya existentes.", + "LabelParallelImageEncodingLimitHelp": "Cantidad máxima de codificaciones de imágenes que pueden ejecutarse en paralelo. Establecer esto en 0 elegirá un límite basado en las especificaciones de su sistema.", + "ResolutionMatchSource": "Coincidir fuente", + "SaveRecordingNFO": "Guardar grabación de metadatos EPG en NFO", + "SecondarySubtitles": "Subtítulos Secundarios", + "LabelVppTonemappingContrastHelp": "Aplicar ganancia de contraste en el mapeado de tonos VPP. Los valores recomendados y seleccionados por defecto son 1.", + "MediaInfoVideoRangeType": "Tipo de rango de vídeo", + "MediaInfoDvVersionMinor": "Versión DV menor", + "TonemappingModeHelp": "Seleccione el modo de mapeado de tono. Si experimenta sobreiluminación intente cambiar al modo RGB." } diff --git a/src/strings/es.json b/src/strings/es.json index 0ccb65dba3..07dedd09f8 100644 --- a/src/strings/es.json +++ b/src/strings/es.json @@ -725,11 +725,11 @@ "MessageNoPluginsInstalled": "No hay complementos instalados.", "MessageNoTrailersFound": "Instale el canal de tráilers para mejorar su experiencia cinematográfica agregando una biblioteca de tráilers de Internet.", "MessageNothingHere": "Nada aquí.", - "MessagePasswordResetForUsers": "Se ha restablecido las contraseñas a los siguientes usuarios. Ahora pueden iniciar sesión con los códigos PIN Fácil que se usaron para el restablecimiento.", + "MessagePasswordResetForUsers": "Se ha restablecido las contraseñas a los siguientes usuarios. Ahora pueden iniciar sesión con los códigos PIN que se usaron para el restablecimiento.", "MessagePleaseEnsureInternetMetadata": "Asegúrate de que la descarga de metadatos desde internet está activada.", "MessagePleaseWait": "Por favor, espere.", "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este complemento inicia sesión en tu servidor local directamente.", - "MessagePluginInstallDisclaimer": "Los complementos creados por los miembros de la comunidad son una buena forma de mejorar tu experiencia con características adicionales y otros beneficios. Antes de instalarlos considera los efectos que pueden tener en tu servidor, como escaneos de la biblioteca más largos, aumento del procesado en segundo plano o inestabilidad del sistema.", + "MessagePluginInstallDisclaimer": "ADVERTENCIA: Instalar un complemento de terceros conlleva riesgos. Puede contener código inestable o malicioso, y puede cambiar en cualquier momento. Solo instale complementos de autores en los que confíe, y tenga en cuenta los posibles efectos que puede tener, incluyendo consultas a servicios externos, análisis de bibliotecas más largos, o procesamiento en segundo plano adicional.", "MessageReenableUser": "Mira abajo para reactivarlo", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Se eliminarán las siguientes ubicaciones de medios de tu biblioteca", "MessageUnableToConnectToServer": "No podemos conectar con el servidor seleccionado ahora mismo. Por favor, asegúrate de que esta funcionando e inténtalo otra vez.", @@ -1049,7 +1049,7 @@ "Yes": "Sí", "Yesterday": "Ayer", "Absolute": "Absoluto", - "Actor": "Actor", + "Actor": "Interprete", "AddToPlayQueue": "Añadir a la cola de reproducción", "AirDate": "Fecha de emisión", "Aired": "Emitido", @@ -1570,7 +1570,7 @@ "TypeOptionPluralMovie": "Películas", "TypeOptionPluralEpisode": "Episodios", "TypeOptionPluralBook": "Libros", - "TypeOptionPluralAudio": "Audios", + "TypeOptionPluralAudio": "Pistas de audio", "Track": "Pista", "SetUsingLastTracks": "Establecer la pista de Audio/Subtítulos del Ítem Anterior", "Remixer": "Remezclador", @@ -1661,7 +1661,7 @@ "ScreenResolution": "Resolución de pantalla", "RememberSubtitleSelections": "Seleccionar la pista de subtítulos basado en el anterior ítem", "RememberAudioSelectionsHelp": "Intentar seleccionar la pista de audio con la más parecida al anterior vídeo.", - "RememberAudioSelections": "Seleccionar pista de audio basado en el anterior item", + "RememberAudioSelections": "Establecer pista de audio basándose en el elemento anterior", "LabelMaxVideoResolution": "Resolución máxima permitida para recodificar vídeo", "IgnoreDts": "Ignorar DTS (decoding timestamp)", "MessageNoItemsAvailable": "No hay elementos disponibles.", @@ -1671,7 +1671,7 @@ "OptionDateEpisodeAdded": "Fecha en que se añadió el episodio", "EnableCardLayout": "Mostrar CardBox visual", "OptionDateShowAdded": "Fecha en que se añadió el programa", - "Experimental": "Experimental", + "Experimental": "En pruebas", "DownloadAll": "Descargar todo", "RememberSubtitleSelectionsHelp": "Intentar establecer los subtítulos con coincidencia más cercana al último video.", "LabelStereoDownmixAlgorithm": "Algoritmo de mezcla estéreo", @@ -1695,7 +1695,7 @@ "SaveRecordingImages": "Guardar grabación de imágenes EPG", "SaveRecordingImagesHelp": "Guardar imágenes del proveedor de listados EPG junto con los archivos multimedia.", "LabelDummyChapterDuration": "Intervalo", - "LabelDummyChapterDurationHelp": "Intervalo de extracción de imágenes de los capítulos en segundos.", + "LabelDummyChapterDurationHelp": "Intervalo entre capítulos dummy. Dejar a 0 para deshabilitar la generación de capítulos dummy. Cambiar esta opción no tendrá efecto para capítulos dummy ya existentes.", "HeaderDummyChapter": "Imágenes de capítulos", "LabelDummyChapterCount": "Límite", "Featurette": "Reportaje extra", @@ -1704,12 +1704,12 @@ "SecondarySubtitles": "Subtítulos secundarios", "LabelParallelImageEncodingLimit": "Límite de codificación de imágenes en paralelo", "LabelEnableAudioVbr": "Habilitar codificación de audio VBR", - "LabelChapterImageResolutionHelp": "Resolución de las imágenes de los capítulos extraídos.", + "LabelChapterImageResolutionHelp": "Resolución de las imágenes extraídas de los capítulos. Cambiar esta opción no tendrá efecto sobre capítulos dummy ya existentes.", "PreferEmbeddedExtrasTitlesOverFileNames": "Prefiere títulos incrustados sobre nombres de archivo para extras", "LabelDummyChapterCountHelp": "Número máximo de imágenes de capítulos que se extraerán para cada archivo multimedia.", "LabelEnableAudioVbrHelp": "La tasa de bits variable ofrece una mejor relación entre calidad y tasa de bits promedio, pero en algunos casos raros puede causar problemas de almacenamiento de búfer y compatibilidad.", "LabelParallelImageEncodingLimitHelp": "Cantidad máxima de codificaciones de imágenes que pueden ejecutarse en paralelo. Establecer esto en 0 elegirá un límite basado en las especificaciones de su sistema.", - "ResolutionMatchSource": "Considera de fuente", + "ResolutionMatchSource": "Coincidir fuente", "SubtitleMagenta": "Magenta", "SubtitleWhite": "Blanco", "LabelChapterImageResolution": "Resolución", @@ -1729,5 +1729,37 @@ "MenuClose": "Cerrar Menú", "UserMenu": "Menú de Usuario", "Studio": "Estudio", - "AllowCollectionManagement": "Permitir que este usuario administre colecciones" + "AllowCollectionManagement": "Permitir que este usuario administre colecciones", + "EnableAudioNormalizationHelp": "La normalización de audio añadirá una ganancia constante para mantener la media en el nivel deseado (-18dB).", + "LabelEnableLUFSScan": "Habilitar escaneo LUFS", + "PasswordRequiredForAdmin": "Se requiere contraseña para las cuentas de administrador.", + "GetThePlugin": "Obtener el Plugin", + "NotificationsMovedMessage": "La funcionalidad de notificaciones se ha movido al plugin Webhook.", + "LabelSyncPlayNoGroups": "No hay grupos disponibles", + "Notifications": "Notificaciones", + "EnableAudioNormalization": "Normalización de audio", + "LabelEnableLUFSScanHelp": "Habilitar escaneo LUFS para música (Esto tardará más y consumirá más recursos).", + "LabelDate": "Fecha", + "LabelLevel": "Nivel", + "LabelMediaDetails": "Detalles de medios", + "LabelSystem": "Sistema", + "LogLevel.Trace": "Rastreo", + "LogLevel.Debug": "Depurar", + "LogLevel.Information": "Información", + "LogLevel.Warning": "Advertencia", + "LogLevel.Error": "Error", + "LogLevel.Critical": "Crítico", + "LogLevel.None": "Ningún", + "PleaseConfirmRepositoryInstallation": "Por favor, haga clic en OK para confirmar que ha leído lo anterior y desea continuar con la instalación del repositorio de complementos.", + "Unknown": "Desconocido", + "HeaderConfirmRepositoryInstallation": "Confirma la instalación del repositorio de complementos", + "LabelDeveloper": "Desarrollador", + "MessageRepositoryInstallDisclaimer": "ADVERTENCIA: Instalar un complemento de terceros conlleva riesgos. Puede contener código inestable o malicioso, y puede cambiar en cualquier momento. Solo instale complementos de autores en los que confíe.", + "HeaderEpisodesStatus": "Estado de los episodios", + "AllowSegmentDeletion": "No Permitir Ninguno", + "AllowSegmentDeletionHelp": "Elimine los segmentos antiguos después de que se hayan enviado al cliente. Esto evita tener que almacenar todo el archivo transcodificado en el disco. Solo funcionará con la limitación habilitada. Apáguelo si experimenta problemas de reproducción.", + "LabelThrottleDelaySecondsHelp": "Tiempo en segundos después del cual se acelerará el transcodificador. Debe ser lo suficientemente grande para que el cliente mantenga un búfer saludable. Solo funciona si el estrangulamiento \"throttling\" está habilitada.", + "LabelSegmentKeepSeconds": "Tiempo que se mantendrán los segmentos", + "LabelThrottleDelaySeconds": "Limitar trás", + "LabelSegmentKeepSecondsHelp": "Tiempo en segundos durante el cual se deben conservar los segmentos antes de que se sobrescriban. Debe ser mayor que \"Acelerar después\". Solo funciona si la eliminación de segmentos está habilitada." } diff --git a/src/strings/es_419.json b/src/strings/es_419.json index 6d7bec54e2..0a208c94e2 100644 --- a/src/strings/es_419.json +++ b/src/strings/es_419.json @@ -1298,7 +1298,7 @@ "AsManyAsPossible": "Tantos como sea posible", "Artist": "Artista", "Art": "Clearart", - "AroundTime": "Alrededor de", + "AroundTime": "Alrededor de {0}", "Anytime": "En cualquier momento", "AnyLanguage": "Cualquier idioma", "AlwaysPlaySubtitlesHelp": "Los subtítulos que coincidan con el idioma preferido serán cargados independientemente del idioma del audio.", diff --git a/src/strings/et.json b/src/strings/et.json index 810edcea6d..2f8a6bcf9d 100644 --- a/src/strings/et.json +++ b/src/strings/et.json @@ -614,7 +614,7 @@ "Ascending": "Kasvav", "Artist": "Esitaja", "Art": "Graafika", - "AroundTime": "Umbes", + "AroundTime": "Umbes {0}", "ApiKeysCaption": "Praegu lubatud API võtmete loend", "Anytime": "Igal ajal", "AnyLanguage": "Mis tahes keel", diff --git a/src/strings/fa.json b/src/strings/fa.json index a20a849ed0..e54ba0d234 100644 --- a/src/strings/fa.json +++ b/src/strings/fa.json @@ -141,7 +141,7 @@ "AskAdminToCreateLibrary": "از کاربر مدیر بخواهید که یک کتابخانه ایجاد کند.", "Ascending": "افزایشی", "AsManyAsPossible": "تا جای ممکن", - "AroundTime": "حدود", + "AroundTime": "حدود {0}", "Anytime": "هر زمانی", "AnyLanguage": "هر زبانی", "AlwaysPlaySubtitles": "همیشه پخش کن", @@ -1505,7 +1505,7 @@ "EnableAutoCast": "تنظیم به عنوان پیشفرض", "DisablePlugin": "غیرفعال کردن", "EnablePlugin": "فعال کردن", - "DirectPlayHelp": "فایل منبع کاملاً با این سرویس گیرنده سازگار است و جلسه بدون تغییر پرونده در حال دریافت فایل است.", + "DirectPlayHelp": "فایل منبع کاملاً با این سرویس گیرنده سازگار است و جلسه در حال دریافت فایل بدون تغییر است.", "DeleteDevicesConfirmation": "آیا مطمئن هستید که می خواهید همه دستگاه ها را حذف کنید؟ تمام جلسات دیگر از سیستم خارج می شوند. بار دیگر که کاربر وارد سیستم شود ، دستگاه ها دوباره ظاهر می شوند.", "Bwdif": "BWDIF", "ButtonUseQuickConnect": "از اتصال سریع استفاده کنید", @@ -1601,5 +1601,6 @@ "HeaderPerformance": "کارایی", "IgnoreDtsHelp": "غیر فعال کردن این گزینه ممکن است برخی اشکالات را رفع کند، مثل نبودن صدا بر روی کانال هایی که جریان صدا و تصویر جداگانه دارند.", "LabelDummyChapterDurationHelp": "وقفه استخراج تصاویر فصل به ثانیه.", - "HeaderDummyChapter": "تصاویر فصل" + "HeaderDummyChapter": "تصاویر فصل", + "EnableAudioNormalization": "معمول سازی صوت" } diff --git a/src/strings/fi.json b/src/strings/fi.json index 1f08725f9b..a2040703c5 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1259,7 +1259,7 @@ "Framerate": "Virkistystaajuus", "DisablePlugin": "Poista käytöstä", "EnablePlugin": "Ota käyttöön", - "DirectPlayHelp": "Lähdetiedosto on täysin yhteensopiva päätesovelluksen kanssa ja istunto vastaanottaa tiedoston ilman muuntoa.", + "DirectPlayHelp": "Lähdetiedosto on täysin yhteensopiva tämän päätesovelluksen kanssa ja istunto vastaanottaa tiedoston ilman muuntoa.", "LabelMaxStreamingBitrateHelp": "Määritä suoratoiston enimmäisbittinopeus.", "LabelMinAudiobookResumeHelp": "Kohteita pidetään toistamattomina, jos toisto keskeytetään ennen tätä aikaa.", "LabelMaxStreamingBitrate": "Suoratoiston enimmäislaatu", @@ -1375,9 +1375,9 @@ "MessageSent": "Viesti lähetetty.", "MessagePluginInstallError": "Asennettaessa lisäosaa tapahtui virhe.", "MessagePluginInstalled": "Lisäosan asennus onnistui. Palvelin on käynnistettävä uudelleen, jotta muutokset tulevat voimaan.", - "MessagePluginInstallDisclaimer": "Yhteisön kehittämät lisäosat ovat mainio tapa parantaa käyttökokemustasi lisäominaisuuksilla. Huomioi ennen lisäosien asennusta, että ne voivat vaikuttaa palvelimen toimintaan, mm. pidentämällä kirjastopäivityksiä ja lisäämällä taustakuormitusta, sekä aiheuttaa järjestelmän epävakautta.", + "MessagePluginInstallDisclaimer": "VAROITUS: Ulkopuolisten tahojen kehittämien lisäosien asennus on aina riskialtista ja ne voivat sisältää epävakaata tai haitallista koodia, ja muuttua koska tahansa. Asenna lisäosia vain kehittäjiltä, joihin luotat ja ymmärrä niiden mahdolliset vaikutukset, kuten yhteydenotot ulkoisiin palveluihin, pidentyvät kirjastotarkistukset tai erilaiset taustaprosessit.", "MessagePlayAccessRestricted": "Tämän sisällön toistoa on rajoitettu. Lisätietoja saat palvelimen ylläpidolta.", - "MessagePasswordResetForUsers": "Seuraavien käyttäjien salasanat on tyhjennetty ja he voivat nyt kirjautua käyttäen tyhjennykseen käytettäjä Helppo PIN -koodeja.", + "MessagePasswordResetForUsers": "Seuraavat käyttäjät ovat nollanneet salasanansa ja he voivat nyt kirjautua nollaukseen käyttämillään PIN-koodeilla.", "MessageNoTrailersFound": "Asenna trailerit-kanava parantaaksesi elokuvakokemusta lisäämällä internet-trailereiden kirjasto.", "MessageNoRepositories": "Ei ohjelmavarastoja.", "MessageNoNextUpItems": "Ei tuloksia. Aloita sarjojesi katselu!", @@ -1732,7 +1732,32 @@ "EnableAudioNormalization": "Äänen normalisointi", "LabelEnableLUFSScan": "Suorita LUFS-tarkistus", "LabelEnableLUFSScanHelp": "Käytä musiikin LUFS-tarkistusta (tämä vaatii enemmän aikaa ja resurseja).", - "GetThePlugin": "Hanki laajennus", + "GetThePlugin": "Hanki lisäosa", "Notifications": "Ilmoitukset", - "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-laajennukseen." + "NotificationsMovedMessage": "Ilmoitustoiminnallisuus on siirtynyt Webhook-lisäosaan.", + "PasswordRequiredForAdmin": "Ylläpitotileille on määritettävä salasana.", + "LabelSyncPlayNoGroups": "Ryhmiä ei ole käytettävissä", + "HeaderConfirmRepositoryInstallation": "Vahvista lisäosahakemiston asennus", + "LabelDeveloper": "Kehittäjä", + "MessageRepositoryInstallDisclaimer": "VAROITUS: Ulkopuolisten tahojen kehittämien lisäosien asennus on aina riskialtista ja ne voivat sisältää epävakaata tai haitallista koodia, ja muuttua koska tahansa. Asenna lisäosia vain kehittäjiltä, joihin luotat.", + "Unknown": "Tuntematon", + "PleaseConfirmRepositoryInstallation": "Vahvista lukeneesi yllä olevan tekstin ja jatkaaksesi lisäosan asennusta painamalla OK.", + "LabelDate": "Päiväys", + "LabelLevel": "Taso", + "LabelMediaDetails": "Median tiedot", + "LabelSystem": "Järjestelmä", + "LogLevel.Trace": "Jäljitys", + "LogLevel.Debug": "Vianselvitys", + "LogLevel.Information": "Tietoja", + "LogLevel.Warning": "Varoitus", + "LogLevel.Error": "Virhe", + "LogLevel.Critical": "Kriittinen", + "LogLevel.None": "Ei mitään", + "HeaderEpisodesStatus": "Jaksojen tila", + "AllowSegmentDeletionHelp": "Poista vanhat osiot kun ne on lähetetty päätteelle. Tämän ansiosta transkoodattua tiedostoa ei tarvitse säilyttää kokonaan. Toimii vain rauhoituksen ollessa käytössä. Poista käytöstä, jos kohtaat toisto-ongelmia.", + "AllowSegmentDeletion": "Poista osiot", + "LabelThrottleDelaySeconds": "Rauhoita kun on kulunut", + "LabelThrottleDelaySecondsHelp": "Aika sekunneissa, jonka kuluttua transkooderi rauhoitetaan. Tämän on oltava riittävän suuri, jotta päätelaite kykenee ylläpitämään reilua puskuria. Toimii vain rauhoituksen ollessa käytössä.", + "LabelSegmentKeepSeconds": "Osioiden säilytysaika", + "LabelSegmentKeepSecondsHelp": "Aika sekunteina, jonka osiot säilytetään ennen päällekirjoitusta. Oltava \"Rahoita kun on kulunut\" -aikaa suurempi. Toimii vain osioiden poiston ollessa käytössä." } diff --git a/src/strings/fr-ca.json b/src/strings/fr-ca.json index 062b1835bb..d123b4cdf6 100644 --- a/src/strings/fr-ca.json +++ b/src/strings/fr-ca.json @@ -110,7 +110,7 @@ "AlwaysPlaySubtitlesHelp": "Les sous-titres correspondant à la préférence linguistique seront chargés sans tenir compte de la langue de l'audio.", "AnyLanguage": "N'importe quelle langue", "Anytime": "N'importe quand", - "AroundTime": "Environ", + "AroundTime": "Environ {0}", "Art": "Illustrations avec transparence", "AsManyAsPossible": "Autant que possible", "Ascending": "Croissant", @@ -243,7 +243,7 @@ "EnableColorCodedBackgrounds": "Arrière-plans avec code de couleurs", "EnableCinemaMode": "Mode cinéma", "EnableBackdropsHelp": "Afficher des images à l'arrière-plan de certaines pages de la médiathèque.", - "EnableAutoCast": "Définir comme défaut", + "EnableAutoCast": "Définir par défaut", "EditSubtitles": "Modifier les sous-titres", "EditMetadata": "Modifier les métadonnées", "EditImages": "Modifier les images", @@ -370,7 +370,7 @@ "Descending": "Décroissant", "Depressed": "Diminuer", "DeleteDevicesConfirmation": "Voulez-vous vraiment supprimer tous les appareils ? Toutes les autres sessions seront déconnectées. Les appareils réapparaîtront la prochaine fois qu'un utilisateur se connectera.", - "AllowTonemappingHelp": "Le tone-mapping peut transformer la plage dynamique d’une vidéo de HDR à SDR tout en maintenant les détails et les couleurs de l’image, qui sont des informations très importantes pour représenter la scène originale. Fonctionne actuellement uniquement avec les vidéos HDR10 ou HLG. Cela nécessite l’environnement d’exécution OpenCL ou CUDA correspondant.", + "AllowTonemappingHelp": "Le tone-mapping peut transformer la plage dynamique d’une vidéo de HDR à SDR tout en maintenant les détails et les couleurs de l’image, qui sont des informations très importantes pour représenter la scène originale. Cette fonction prend en charge uniquement les vidéos HDR10, HLG ou DoVi et nécessite l’environnement d’exécution OpenCL ou CUDA correspondant.", "LabelEasyPinCode": "NIP facile", "LabelDynamicExternalId": "ID {0}", "LabelDownloadLanguages": "Téléchargement des langues", @@ -688,7 +688,7 @@ "Framerate": "Images par seconde", "DisablePlugin": "Désactiver", "EnablePlugin": "Activer", - "DirectPlayHelp": "Le fichier source est entièrement compatible avec ce client, et la session reçoit le fichier sans modifications.", + "DirectPlayHelp": "Le fichier source est entièrement compatible avec ce client et la session reçoit le fichier sans modifications.", "ShowAdvancedSettings": "Afficher les paramètres avancés", "SettingsWarning": "Le changement de ces valeurs peut causer une instabilité ou des erreurs de connexion. Si vous rencontrez des problèmes, nous vous recommandons de restaurer les valeurs par défaut.", "SettingsSaved": "Paramètres sauvegardés.", @@ -1020,7 +1020,7 @@ "LabelMaxParentalRating": "Classification parentale maximale", "SpecialFeatures": "Bonus", "Sort": "Trier", - "SortByValue": "Trier par", + "SortByValue": "Trier par {0}", "LabelMovieCategories": "Catégories de films", "LabelNewPassword": "Nouveau mot de passe", "LabelOriginalName": "Nom original", @@ -1046,5 +1046,188 @@ "HeaderRecordingMetadataSaving": "Enregistrement des métadonnées", "HeaderDummyChapter": "Images des chapitres", "LabelDummyChapterCountHelp": "Le nombre maximum d'images de chapitre qui seront extraites pour chaque fichier média.", - "LabelChapterImageResolutionHelp": "La résolution des images de chapitre." + "LabelChapterImageResolutionHelp": "La résolution des images de chapitre. Le changement de cette valeur n’aura aucun effet sur les chapitres fictifs existants.", + "LabelMetadataDownloadersHelp": "Activez et classez vos téléchargeurs de métadonnées en ordre de priorité. Les téléchargeurs de plus basse priorité seront utilisés uniquement pour compléter les infos manquantes.", + "LabelMetadataReadersHelp": "Classez vos sources de métadonnées locales en ordre de priorité. Le premier fichier correspondant sera pris en charge.", + "LabelProfileCodecs": "Codecs", + "LabelProtocolInfoHelp": "La valeur utilisée pour répondre aux requêtes « GetProtocolInfo » de l’appareil.", + "Bold": "Gras", + "LabelTonemappingDesatHelp": "Applique la desaturation pour les points points forts qui dépassent ce niveau de luminosité. Plus la valeur est élevée, plus les informations de couleur seront préservées. Ce paramètre permet d’empêcher les couleurs super saturées en les tournant graduellement au blanc afin de produire une image plus naturelle, au détriment de la fidélité de couleurs. Les valeurs recommandées sont 0 et 0.5.", + "LabelTonemappingParamHelp": "Paramètre pour régler l’algorithme de tone-mapping. La valeur par défaut et recommandée est vide.", + "LabelSyncPlayLeaveGroupDescription": "Désactiver SyncPlay", + "LabelSyncPlayNewGroup": "Nouveau groupe", + "LabelPostProcessorArgumentsHelp": "Utilisez {path} comme le chemin d’accès au fichier d’enregistrement.", + "LabelMovieRecordingPath": "Chemin d’enregistrement de films", + "LabelSaveLocalMetadata": "Enregistrer les images dans les dossiers médias", + "LabelScheduledTaskLastRan": "Dernière exécution: {0}, durée: {1}.", + "LabelRequireHttps": "Forcer HTTPS", + "LabelSubtitleFormatHelp": "Exemple: srt", + "LabelSkipIfGraphicalSubsPresent": "Passer si la vidéo contient déjà des sous-titres intégrés", + "LabelSkipIfGraphicalSubsPresentHelp": "Une version texte des sous-titres peut améliorer l’efficacité d’affichage et réduire la nécessité de transcodage vidéo.", + "LabelMinResumeDuration": "Minimum de temps écoulé pour la reprise de lecture", + "LabelRepositoryUrl": "URL du dépôt", + "LabelSkipIfAudioTrackPresent": "Passer si la piste audio par défaut correspond à la langue de téléchargement", + "LabelNumber": "Numéro", + "LabelSyncPlayAccessNone": "Désactivé pour cet utilisateur", + "LabelSyncPlaySettingsSpeedToSyncDurationHelp": "Nombre de millisecondes utilisés pour la correction de synchro en lecture accélérée", + "LabelTonemappingDesat": "Desaturation du tone-mapping", + "LabelSyncPlayNewGroupDescription": "Créer un nouveau groupe", + "LabelMinResumeDurationHelp": "Durée minimale de vidéo pour la prise en charge de sauvegarde et reprise de position de lecture.", + "LabelSaveLocalMetadataHelp": "L’enregistrement des images dans les dossiers médias permet l’accès facile pour leur modification.", + "LabelSyncPlayAccessCreateAndJoinGroups": "Permettre à l’utilisateur de créer et rejoindre des groupes SyncPlay", + "LabelMessageText": "Texte du message", + "LabelMessageTitle": "Titre du message", + "LabelMethod": "Méthode", + "LabelMinResumePercentage": "Pourcentage minimal pour la reprise de lecture", + "LabelModelName": "Non du modèle", + "LabelOpenclDevice": "Périphérique OpenCL", + "LabelPublicHttpPort": "Port HTTP public", + "LabelRefreshMode": "Mode d’actualisation", + "LabelSource": "Source", + "LabelSportsCategories": "Catégories sports", + "LabelSyncPlayPlaybackDiff": "Différence de temps de lecture", + "LabelProfileAudioCodecs": "Codecs audio", + "LabelParentalRating": "Classification parentale", + "LabelStopping": "Arrêt en cours", + "LabelProfileVideoCodecs": "Codecs vidéo", + "LabelScreensaver": "Écran de veille", + "LabelDate": "Date", + "LabelLevel": "Niveau", + "LabelMediaDetails": "Détails du média", + "LabelMetadataDownloadLanguage": "Langue de téléchargement préférée", + "LabelMetadataReaders": "Lecteurs de métadonnées", + "LabelModelUrl": "URL du modèle", + "LabelPlaceOfBirth": "Lieu de naissance", + "LabelPlayerDimensions": "Taille du lecteur", + "LabelProtocol": "Protocole", + "LabelPublicHttpPortHelp": "Le numéro de port public qui sera mappé vers le port local HTTP.", + "LabelPublicHttpsPort": "Port HTTPS public", + "LabelRecord": "Enregistrer", + "LabelRepositoryNameHelp": "Un nom unique pour identifier ce dépôt.", + "LabelSeasonNumber": "Numéro de saison", + "LabelSerialNumber": "Numéro de série", + "LabelSpecialSeasonsDisplayName": "Nom d’affichage pour saisons spéciales", + "LabelSyncPlayResumePlayback": "Reprendre la lecture locale", + "LabelSyncPlayTimeSyncDevice": "Synchronisation du temps avec", + "LabelSyncPlayTimeSyncOffset": "Décalage du temps", + "LabelSyncPlaySettingsExtraTimeOffsetHelp": "Ajuster manuellement la correction du temps de synchronisation (en ms) avec l’appareil sélectionné. Modifier avec soin.", + "LabelSyncPlaySettingsSyncCorrection": "Correction de la synchro", + "LabelSyncPlaySettingsMaxDelaySpeedToSyncHelp": "Délai maximal (en ms) après lequel un saut sera utilisé pour synchroniser plutôt que la lecture en accéléré.", + "LabelSyncPlaySettingsMinDelaySkipToSync": "Délai minimal pour correction de synchro en saut", + "LabelSyncPlaySettingsSpeedToSync": "Activer la correction de synchro par lecture accélérée", + "LabelSyncPlaySettingsSkipToSync": "Activer la correction de synchro par saut", + "LabelSyncPlaySettingsSkipToSyncHelp": "Correction de synchronisation de lecture en sautant vers la position estimée. La correction de synchronisation doit être activée pour que ce paramètre soit pris en charge.", + "LabelSystem": "Système", + "LabelTonemappingAlgorithm": "Sélectionner l’algorithme de tone-mapping", + "LabelSortBy": "Trier par", + "LabelMoviePrefix": "Préfixe de film", + "LabelSyncPlayNoGroups": "Aucun groupe disponible", + "LabelMaxResumePercentageHelp": "Les titres sont présumés lus jusqu’à la fin du arrêtés après ce temps écoulé.", + "LabelOptionalNetworkPath": "Dossier réseau partagé", + "LabelSortTitle": "Titre de tri", + "LabelModelNumber": "Numéro de modèle", + "LabelPublishedServerUriHelp": "Remplacer l’URI utilisé par Jellyfin selon l’interface où l’adresse IP du client.", + "LabelStopWhenPossible": "Arrêter lorsque possible", + "LabelSyncPlayAccessJoinGroups": "Permettre à l’utilisateur de rejoindre les groupes SyncPlay", + "LabelPostProcessorArguments": "Arguments de ligne de commande pour l’application de post-traitement", + "EnableAudioNormalizationHelp": "La normalisation audio ajoutera un gain constant pour maintenir un niveau désiré (-18dB).", + "EnableAudioNormalization": "Normalisation audio", + "LabelEnableLUFSScanHelp": "Activer le scan LUFS pour la musique (ceci prolongera la durée et demandera plus de ressources).", + "LabelParallelImageEncodingLimit": "Limite d’encodage d’image en parallèle", + "LabelProtocolInfo": "Infos protocole", + "LabelRecordingPath": "Chemin par défaut pour l’enregistrement", + "LabelProfileContainer": "Conteneur", + "LabelRuntimeMinutes": "Durée", + "LabelSimultaneousConnectionLimit": "Limite de flux simultanés", + "LabelSlowResponseTime": "Nombre de millisecondes pour qu’une réponse soit considérée lente", + "LabelSonyAggregationFlags": "Indicateurs d’agrégation Sony", + "LabelTonemappingParam": "Paramètre de tone-mapping", + "LabelSkipForwardLength": "Temps de saut en avant", + "LabelSortName": "Nom de tri", + "LabelDummyChapterDurationHelp": "L’intervalle entre les chapitres fictifs. Définir une valeur de 0 pour désactiver la génération de chapitres fictifs. Le changement de cette valeur n’aura aucun effet sur les chapitres fictifs existants.", + "LabelEnableLUFSScan": "Activer le scan LUFS", + "LabelOriginalAspectRatio": "Format d’image original", + "LabelPlayer": "Lecteur", + "LabelPlayMethod": "Méthode de lecture", + "LabelPreferredDisplayLanguage": "Langue d’affichage préférée", + "LabelPreferredSubtitleLanguage": "Langue de sous-titres préférée", + "LabelProfileCodecsHelp": "Liste de codecs séparés par virgule. Si le champ est vide, s’appliquera à tous les codecs.", + "LabelPublishedServerUri": "URIs publiés du serveur", + "LabelSeriesRecordingPath": "Chemin d’enregistrement de la série", + "LabelServerHost": "Hôte", + "LabelSubtitlePlaybackMode": "Mode de sous-titres", + "LabelStartWhenPossible": "Démarrer lorsque possible", + "LabelSupportedMediaTypes": "Types de médias pris en charge", + "LabelSyncPlaySettingsExtraTimeOffset": "Correction supplémentaire du temps", + "LabelSyncPlaySettingsMinDelaySpeedToSync": "Délai minimum pour accélérer à la synchro", + "LabelSyncPlaySettingsMaxDelaySpeedToSync": "Délai maximal pour la correction de synchro accélérée", + "LabelSyncPlaySettingsMinDelaySkipToSyncHelp": "Délai minimal (en ms) après lequel un saut sera utilisé pour corriger la synchronisation", + "LabelProfileContainersHelp": "Liste de conteneurs séparés par virgule. Si le champ est vide, s’applique à tous les conteneurs.", + "GetThePlugin": "Obtenir le plugin", + "LabelServerName": "Nom du serveur", + "LabelNumberOfGuideDaysHelp": "Le téléchargement de plus de jours de données de guide permet de définir les horaires plus loins dans le futur mais demandera plus de temps à télécharger. « Auto » sélectionnera automatiquement une valeur selon le nombre de chaînes.", + "LabelSonyAggregationFlagsHelp": "Détermine le contenu de l’élément 'aggregationFlags' dans le namespace 'urn:schemas-sonycom:av'.", + "LabelStable": "Stable", + "LabelSyncPlaySettingsMinDelaySpeedToSyncHelp": "Délai de synchronisation minimal pour l’activation de la correction de synchronisation accélérée", + "LabelEnableAudioVbr": "Activer l’encodage audio VBR", + "LabelMaxStreamingBitrateHelp": "Spécifie un taux d’échantillonnage (bitrate) maximal pendant le streaming.", + "LabelMetadataSaversHelp": "Choisissez les formats à utiliser lors de la sauvegarde de métadonnées.", + "LabelMinAudiobookResume": "Minimum de temps écoulé pour la reprise de lecture des audiobooks", + "LabelMinResumePercentageHelp": "Les titres sont présumés non lus si arrêtés avant ce temps de lecture.", + "LabelMinBackdropDownloadWidth": "Largeur minimale des images de fond téléchargées", + "LabelModelDescription": "Description du modèle", + "LabelMoviePrefixHelp": "Si un préfixe est appliqué aux titres de films, entrez-le ici pour qu’il soit pris en charge correctement par le serveur.", + "LabelMusicStreamingTranscodingBitrate": "Taux d’échantillonnage (bitrate) pour la musique", + "LabelNewsCategories": "Catégories d’actualités", + "LabelOptionalNetworkPathHelp": "Si ce dossier est partagé sur le réseau, l’entrée du chemin de partage ici peut permettre l’accès direct aux fichiers médias depuis des clients sur d’autres appareils. Par exemple {0} ou {1}.", + "LabelNumberOfGuideDays": "Nombre de jours de guide à télécharger", + "LabelOpenclDeviceHelp": "Le périphérique OpenCL qui sera utilisé pour le « tone mapping » HDR. Le chiffre a gauche du point est le numéro de plateforme, et celui de droite est le numéro de périphérique sur la plateforme. La valeur par défaut est 0.0. Le fichier d’application FFmpeg prenant en charge l’accélération OpenCL est requis.", + "LabelParentNumber": "Numéro parent", + "LabelParallelImageEncodingLimitHelp": "Nombre maximal d’encodages d’images qui peuvent être exécutés en parallèle. Une valeur de 0 entraînera une sélection automatique d’une limite selon les caractéristiques de votre système.", + "LabelPasswordResetProvider": "Fournisseur de récupération de mot de passe", + "LabelPlayDefaultAudioTrack": "Lire la piste audio par défaut peu importe la langue", + "LabelPostProcessor": "Application de post-traitement", + "LabelPublicHttpsPortHelp": "Le numéro de port public qui sera mappé vers le port local HTTPS.", + "LabelQuickConnectCode": "Code de connexion rapide (Quick Connect)", + "LabelRemoteClientBitrateLimit": "Limite de débit de streaming Internet (Mbps)", + "LabelReasonForTranscoding": "Raison pour le transcodage", + "LabelRepositoryName": "Nom du dépôt", + "LabelRepositoryUrlHelp": "L’emplacement du manifeste de dépôt à inclure.", + "LabelRemoteClientBitrateLimitHelp": "(Facultatif) Une limite de débit par flux pour tout appareil externe. Ceci est utile pour empêcher qu’un client externe demande un débit trop élevé pour votre connexion Internet. La définition d’une limite de débit pourrait engendrer une hausse d’utilisation de processeur sur le serveur afin d’effectuer le transcodage en direct vers un débit conforme à la limite définie.", + "LabelRequireHttpsHelp": "Si activé, le serveur redirigera toute connexion HTTP vers HTTPS. Ce paramètre n’a aucun effet si le serveur n’est pas configuré pour le HTTPS.", + "LabelSelectFolderGroupsHelp": "Les dossiers non-sélectionnés seront affichés dans leur propre vue.", + "LabelSelectFolderGroups": "Classer automatiquement le contenu des dossiers suivants dans des vues telles que « Films », « Musique », « Télé »", + "LabelSelectVersionToInstall": "Sélectionner la version à installer", + "LabelServerHostHelp": "192.168.1.100:8096 ou https://monserveur.com", + "LabelServerNameHelp": "Ce nom sera utilisé pour identifier le serveur. Par défaut, le nom d’hôte du serveur sera utilisé.", + "LabelSize": "Taille", + "LabelSlowResponseEnabled": "Ajouter un message d’avertissement au journal si le serveur a été lent à répondre", + "LabelSkipBackLength": "Temps de retour arrière", + "LabelSkipIfAudioTrackPresentHelp": "Décocher pour assurer que le téléchargement de sous-titres pour toutes les vidéos, peu importe la langue d’audio.", + "LabelSortOrder": "Ordre de tri", + "LabelSSDPTracingFilter": "Filtre SSDP", + "LabelSSDPTracingFilterHelp": "(Facultatif) Adresse IP pour filtrer le trafic SSDP.", + "LabelStatus": "Statut", + "LabelStreamType": "Type de flux", + "LabelSubtitleDownloaders": "Téléchargeurs de sous-titres", + "LabelSyncPlayHaltPlaybackDescription": "et ignorer les modifications courantes à la liste de lecture", + "LabelSyncPlayLeaveGroup": "Quitter le groupe", + "LabelSyncPlayAccess": "Accès SyncPlay", + "LabelSyncPlayResumePlaybackDescription": "Rejoindre à nouveau la lecture en groupe", + "LabelSyncPlaySyncMethod": "Méthode de synchro", + "LabelSyncPlaySettingsSyncCorrectionHelp": "Activer la synchronisation active de la lecture soit en accélérant la lecture du média ou en sautant vers la position estimée. Désactiver en cas de coupures du flux.", + "LabelSyncPlaySettingsSpeedToSyncDuration": "Durée de lecture en accéléré pour synchronisation", + "LabelSyncPlaySettingsSpeedToSyncHelp": "Correction de synchronisation de lecture en accélérant la vitesse de lecture. La correction de synchronisation doit être activée pour que ce paramètre soit pris en charge.", + "LabelTag": "Étiquette", + "LabelTextWeight": "Poids du texte", + "LabelReleaseDate": "Date de sortie", + "LabelOverview": "Survol", + "LabelRecordingPathHelp": "Spécifier l’emplacement par défaut pour les enregistrements. Si le champ est vide, le dossier de données de programme du serveur sera utilisé.", + "LabelMetadataPathHelp": "Spécifier un emplacement pour le téléchargement d’images et métadonnées.", + "LabelSyncPlayHaltPlayback": "Arrêter la lecture locale", + "HeaderConfirmRepositoryInstallation": "Confirmer l’installation du dépôt de plugins", + "LabelDeveloper": "Développeur", + "LabelMusicStreamingTranscodingBitrateHelp": "Spécifiez un taux d’échantillonnage (bitrate) maximal lors du streaming de musique.", + "AllowCollectionManagement": "Permettre à l’utilisateur de gérer les collections", + "LabelSyncPlaySettingsDescription": "Changer les préférences SyncPlay" } diff --git a/src/strings/fr.json b/src/strings/fr.json index 4544664cfd..5363c35b67 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -799,12 +799,12 @@ "MessageNoPluginsInstalled": "Vous n'avez aucune extension installée.", "MessageNoTrailersFound": "Installez la chaîne Trailers pour améliorer votre expérience cinéma, par l'ajout d'une médiathèque de bandes-annonces disponibles sur Internet.", "MessageNothingHere": "Il n'y a rien ici.", - "MessagePasswordResetForUsers": "Les mot de passes de ces utilisateurs ont été réinitialisés. Ils peuvent maintenant se connecter avec les codes Easy PIN utilisés pour la réinitialisation.", + "MessagePasswordResetForUsers": "Le mot de passe de cet utilisateur a été réinitialisé. Il peut désormais se connecter avec le code PIN utilisé pour la réinitialisation.", "MessagePlayAccessRestricted": "La lecture de ce contenu est actuellement restreinte. Contactez l'administrateur de votre serveur pour plus d'informations.", "MessagePleaseEnsureInternetMetadata": "Veuillez vous assurer que le téléchargement des métadonnées depuis Internet est activé.", "MessagePleaseWait": "Veuillez patienter. Ceci peut prendre quelques minutes.", "MessagePluginConfigurationRequiresLocalAccess": "Pour configurer cette extension, veuillez vous connecter directement à votre serveur local.", - "MessagePluginInstallDisclaimer": "Les extensions développées par les membres de la communauté sont une excellente manière d'améliorer votre expérience avec de nouvelles fonctionnalités. Avant toute installation, veuillez prendre connaissance de l'impact qu'elles peuvent avoir sur le serveur, comme l'augmentation de la durée d'actualisation de la médiathèque, de nouvelles tâches de fond, ou un système moins stable.", + "MessagePluginInstallDisclaimer": "ATTENTION : Installer une extension tierce comporte des risques. Celle-ci peut contenir du code instable ou malveillant et peut être modifiée à tout moment. N'installez que des extensions provenant d'auteurs en qui vous avez confiance et soyez conscient des effets potentiels que cela peut avoir, requête vers des services externes, augmentation de la durée d'actualisation de la médiathèque, ou tâches de fonds additionnelles.", "MessageReenableUser": "Voir ci-dessous pour le réactiver", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Ces emplacements de médias vont être supprimés de votre médiathèque", "MessageUnableToConnectToServer": "Nous sommes dans l'impossibilité de nous connecter au serveur sélectionné. Veuillez vérifier qu'il est opérationnel et réessayez.", @@ -1508,7 +1508,7 @@ "Framerate": "Images par seconde", "DisablePlugin": "Désactiver", "EnablePlugin": "Activer", - "DirectPlayHelp": "Le fichier source est entièrement compatible avec le client et la session reçoit le fichier sans modifications.", + "DirectPlayHelp": "Le fichier source est entièrement compatible avec ce client et la session reçoit le fichier sans modifications.", "HeaderContinueReading": "Reprendre la lecture", "TextSent": "Message envoyé.", "EnableGamepadHelp": "Détecter le signal d'entrée de toute manette connectée. (Nécessite le mode d’affichage 'TV'.)", @@ -1697,7 +1697,7 @@ "LabelDummyChapterDurationHelp": "Intervalle entre deux chapitres factices ou 0 pour désactiver la génération. Changer la valeur n’affectera pas les chapitres existants.", "LabelDummyChapterCount": "Limite", "LabelChapterImageResolution": "Résolution", - "LabelChapterImageResolutionHelp": "La résolution d'image des chapitre factices. Changer la valeur n’aura aucun effet sur les chapitres existants.", + "LabelChapterImageResolutionHelp": "La résolution d'image des chapitre factices. Changer la valeur n’affectera pas les chapitres existants.", "ResolutionMatchSource": "Résolution de la source", "HeaderDummyChapter": "Images des chapitres", "LabelDummyChapterDuration": "Intervalle", @@ -1734,8 +1734,32 @@ "EnableAudioNormalization": "Normalisation audio", "LabelEnableLUFSScan": "Activer l’analyse LUFS", "LabelEnableLUFSScanHelp": "Activer l’analyse LUFS pour la musique (cela prendra plus de temps et de ressources).", - "GetThePlugin": "Obtenir le plugin", + "GetThePlugin": "Obtenir l'extension", "Notifications": "Notifications", - "NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée au plugin Webhook.", - "PasswordRequiredForAdmin": "Un mot de passe est requis pour les comptes administrateur." + "NotificationsMovedMessage": "La fonctionnalité de notifications a été transférée à l'extension Webhook.", + "PasswordRequiredForAdmin": "Un mot de passe est requis pour les comptes administrateur.", + "LabelSyncPlayNoGroups": "Pas de groupes disponibles", + "HeaderConfirmRepositoryInstallation": "Confirmer l'installation du dépôt d'extensions", + "LabelDeveloper": "Développeur", + "Unknown": "Inconnu", + "MessageRepositoryInstallDisclaimer": "ATTENTION : Installer un dépôt d'extensions tierces comporte des risques. Celui-ci peut contenir du code instable ou malveillant et peut être modifié à tout moment. N'installez que des dépôts provenant d'auteurs en qui vous avez confiance.", + "PleaseConfirmRepositoryInstallation": "Veuillez cliquer sur OK pour confirmer que vous avez lu ce qui précède et que vous souhaitez poursuivre l'installation du dépôt d'extensions.", + "LabelDate": "Date", + "LabelLevel": "Niveau", + "LabelMediaDetails": "Détails du média", + "LabelSystem": "Système", + "LogLevel.Trace": "Trace", + "LogLevel.Debug": "Debug", + "LogLevel.Information": "Information", + "LogLevel.Warning": "Avertissement", + "LogLevel.Error": "Erreur", + "LogLevel.Critical": "Critique", + "LogLevel.None": "Aucun", + "HeaderEpisodesStatus": "Statut des épisodes", + "AllowSegmentDeletionHelp": "Les segments sont supprimés dès le transfert au client complété évitant ainsi d’avoir à stocker l’intégralité du fichier transcodé sur le disque. L'option requiert l'activation de la vitesse dynamique de transcodage. Désactivez la si vous rencontrez des problèmes de lecture.", + "LabelThrottleDelaySeconds": "Ajuster la vitesse après", + "AllowSegmentDeletion": "Supprimer les segments", + "LabelThrottleDelaySecondsHelp": "Durée en secondes après laquelle le débit du transcodage sera ajusté. Doit être suffisamment grande pour que le client puisse conserver une mémoire tampon saine. Ne fonctionne que si l'adaptation de la vitesse de transcodage est activée.", + "LabelSegmentKeepSeconds": "Durée de conservation des segments", + "LabelSegmentKeepSecondsHelp": "Durée en secondes de conservation des segments avant écrasement. La valeur doit être supérieure au délai d'ajustement. Ne fonctionne que si la suppression des segments est activée." } diff --git a/src/strings/gl.json b/src/strings/gl.json index 3be445eef1..fe98b9f1b2 100644 --- a/src/strings/gl.json +++ b/src/strings/gl.json @@ -359,7 +359,7 @@ "HDPrograms": "Programas en HD", "HardwareAccelerationWarning": "Habilitar a aceleración por hardware pode causar inestabilidade nalgúns entornos. Verifique que o seu sistema operativo e drivers de vídeo estean actualizados. Se tiver dificultades durante a reprodución após a súa habilitación, precisará cambiar a configuración novamente a Ningunha.", "EnableDecodingColorDepth10Vp9": "Habilitar decodificación en hardware de 10 bits para VP9", - "EnableFasterAnimationsHelp": "Usar animacións e transicións máis rápidas", + "EnableFasterAnimationsHelp": "Usar animacións e transicións máis rápidas.", "GoogleCastUnsupported": "Google Cast non soportado", "HeaderActiveDevices": "Dispositivos activos", "HeaderConfirmProfileDeletion": "Configurar Borrado de Perfil", @@ -462,7 +462,7 @@ "Profile": "Perfil", "LabelTheme": "Tema", "MediaInfoSize": "Tamaño", - "LabelSubtitleVerticalPosition": "Posición veritcal", + "LabelSubtitleVerticalPosition": "Posición vertical", "LabelSyncPlayAccessNone": "Deshabilitado por este usuario", "MoveRight": "Mover á dereita", "LabelH264Crf": "CRF da codificación H.264", @@ -570,5 +570,358 @@ "LabelUserLibrary": "Biblioteca do usuario", "LearnHowYouCanContribute": "Descubra como vostede pode contribuír.", "MediaInfoPixelFormat": "Formato de píxeles", - "MediaInfoCodecTag": "Etiqueta do codec" + "MediaInfoCodecTag": "Etiqueta do codec", + "ThemeVideo": "Vídeo temático", + "HeaderSyncPlayTimeSyncSettings": "Sincronización temporal", + "HeaderXmlSettings": "Configuracións de XML", + "OptionPremiereDate": "Data de estrea", + "HeaderSystemDlnaProfiles": "Perfís de sistema", + "LabelAudioChannels": "Canles de audio", + "LabelChromecastVersion": "Versión de Google Cast", + "HeaderSeriesStatus": "Estado das series", + "HeaderStopRecording": "Parar gravación", + "ReplaceAllMetadata": "Substituír todos os metadatos", + "Wednesday": "Mércores", + "ThemeSong": "Tema principal", + "Trailer": "Trailer", + "HeaderUploadSubtitle": "Cargar subtítulo", + "SelectServer": "Seleccione servidor", + "Never": "Nunca", + "HeaderTranscodingProfile": "Perfil de transcodificación", + "HeaderTaskTriggers": "Tarefas axendadas", + "ShowTitle": "Ver título", + "SmallCaps": "Letras minúsculas", + "ValueSeconds": "{0} segundos", + "Large": "Grande", + "Larger": "Máis grande", + "Logo": "Logo", + "LabelYoureDone": "Listo!", + "HeaderSyncPlaySelectGroup": "Xuntarse a un grupo", + "LabelColorSpace": "Espazo de cor", + "Localization": "Localización", + "HeaderServerSettings": "Configuracións do servidor", + "ShowYear": "Ver ano", + "SubtitleGray": "Gris", + "ValueCodec": "Códec: {0}", + "ValueOneEpisode": "1 episodio", + "ValueOneSong": "1 canción", + "ValueOneSeries": "1 serie", + "Play": "Reproducir", + "SelectAll": "Seleccionar todo", + "Sort": "Ordenar", + "HeaderSpecialEpisodeInfo": "Información do episodio especial", + "MessageSyncPlayDisabled": "SyncPlay deshabilitado.", + "HeaderUploadImage": "Subir imaxe", + "New": "Novo", + "HeaderSyncPlayEnabled": "SyncPlay habilitado", + "Name": "Nome", + "Mobile": "Móbil", + "LabelPlayDefaultAudioTrack": "Reproducir a faixa de audio por defecto independentemente do idioma", + "HeaderStartNow": "Iniciar agora", + "Sports": "Deportes", + "OptionWeekdays": "Días da semana", + "LastSeen": "Última vez visto en {0}", + "MessageReenableUser": "Vexa abaixo como reactivalo", + "LabelLanguage": "Idioma", + "Yes": "Si", + "LabelProfileAudioCodecs": "Códecs de audio", + "LabelAlbumArtMaxWidth": "Largura máxima da arte do álbum", + "LabelAudioLanguagePreference": "Idioma de audio preferido", + "LabelPostProcessor": "Aplicación de post-procesamento", + "LabelAudioSampleRate": "Taxa de amostraxe de audio", + "LabelPlaylist": "Lista de reprodución", + "LabelPlayMethod": "Método de reprodución", + "LabelProfileCodecs": "Códecs", + "LabelSeasonNumber": "Número de tempada", + "OptionParentalRating": "Clasificación parental", + "LabelCurrentPassword": "Contrasinal actual", + "Mixer": "Mesturador", + "MySubtitles": "Os meus subtítulos", + "HeaderSubtitleProfiles": "Perfís de subtítulos", + "HeaderSubtitleDownloads": "Descarregamentos de subtítulos", + "HeaderThisUserIsCurrentlyDisabled": "Este usuario está actualmente deshabilitado", + "MinutesAfter": "minutos despois", + "LiveBroadcasts": "Emisións en directo", + "LabelScreensaver": "Salvapantallas", + "Studios": "Estudios", + "NextTrack": "Saltar ao seguinte", + "ValueAlbumCount": "{0} álbums", + "TV": "Televisión", + "MessageItemSaved": "Elemento engadido.", + "Live": "En directo", + "LabelAlbumArtists": "Artistas do álbum", + "LabelDate": "Data", + "LabelLevel": "Nivel", + "LabelLibraryPageSize": "Tamaño da páxina da biblioteca", + "LabelProfileContainer": "Contedor", + "LabelProfileVideoCodecs": "Códecs de vídeo", + "LabelSource": "Fonte", + "Art": "Clearart", + "LogLevel.Debug": "Debug", + "LogLevel.Information": "Información", + "LogLevel.Warning": "Perigo", + "LogLevel.Error": "Erro", + "LogLevel.Critical": "Crítico", + "MessageConfirmAppExit": "Quere saír?", + "MessageSyncPlayEnabled": "SyncPlay habilitado.", + "Metadata": "Metadatos", + "MoreFromValue": "Máis de {0}", + "LabelSortBy": "Ordenar por", + "Other": "Outros", + "PerfectMatch": "Coincidencia perfecta", + "AllowEmbeddedSubtitlesAllowNoneOption": "Non permitir", + "AllowEmbeddedSubtitlesAllowTextOption": "Permitir texto", + "Search": "Procurar", + "Smaller": "Máis pequeno", + "Smart": "Intelixente", + "SubtitleRed": "Vermello", + "SubtitleWhite": "Branco", + "Suggestions": "Suxestións", + "TabAccess": "Acceso", + "ValueMinutes": "{0} min", + "TypeOptionPluralSeason": "Tempadas", + "UserMenu": "Menú de usuario", + "ValueConditions": "Condicións: {0}", + "ValueMusicVideoCount": "{0} vídeos musicais", + "ValueOneAlbum": "1 álbum", + "ValueOneMovie": "1 filme", + "ValueOneMusicVideo": "1 vídeo musical", + "Vertical": "Vertical", + "Unknown": "Descoñecido", + "LabelCurrentStatus": "Estado actual", + "LabelAudioCodec": "Códec de audio", + "LabelCustomCertificatePathHelp": "Localización do ficheiro PKCS #12 que contén o certificado e a clave privada para habilitar o soporte TLS do dominio personalizado.", + "ParentalRating": "Clasificación parental", + "LabelPlaceOfBirth": "Lugar de nacemento", + "MessagePleaseWait": "Por favor espere. Isto pode tomar un minuto.", + "Sunday": "Domingo", + "HeaderSendMessage": "Enviar mensaxe", + "HeaderSortBy": "Ordenar Por", + "Label3DFormat": "Formato 3D", + "LabelDisplayMode": "Modo de visualización", + "MessageNoItemsAvailable": "Non hai elementos dispoñíbeis.", + "OptionSpecialEpisode": "Especiais", + "StopRecording": "Para gravación", + "TabAdvanced": "Avanzado", + "HeaderSubtitleAppearance": "Aparencia dos subtítulos", + "HeaderVideoTypes": "Tipos de vídeos", + "LabelArtistsHelp": "Separar múltiples artistas usando un punto e coma.", + "PlayAllFromHere": "Reproducir todo dende aquí", + "LabelPersonRole": "Rol", + "Played": "Reproducido", + "PlayFromBeginning": "Reproducir dende o principio", + "Repeat": "Repetir", + "LabelDeathDate": "Data de falecemento", + "LabelAllowHWTranscoding": "Permitir transcodificación por hardware", + "ValueEpisodeCount": "{0} episodios", + "AllowEmbeddedSubtitlesAllowAllOption": "Permitir todo", + "AllowEmbeddedSubtitlesAllowImageOption": "Permitir imaxes", + "LabelCustomCertificatePath": "Localización personalizada do certificado SSL", + "LabelAudioBitrate": "Taxa de bits de audio", + "RepeatAll": "Repetir todo", + "Restart": "Reiniciar", + "LabelPasswordConfirm": "Contrasinal (confirmar)", + "Season": "Tempada", + "Series": "Series", + "MessageItemsAdded": "Elementos engadidos.", + "HeaderSeriesOptions": "Opcións de series", + "HeaderSyncPlayPlaybackSettings": "Reprodución", + "ShowLess": "Ver menos", + "HeaderTypeText": "Introducir texto", + "SortName": "Ordenar por nome", + "SpecialFeatures": "Funcionalidades especiais", + "LatestFromLibrary": "Engadido recentemente en {0}", + "TabCatalog": "Catálogo", + "TypeOptionPluralVideo": "Vídeos", + "ValueAudioCodec": "Códec de audio: {0}", + "HomeVideosPhotos": "Vídeos e fotos", + "Whitelist": "Lista branca", + "HeaderSubtitleProfile": "Perfil de subtítulo", + "HeaderSortOrder": "Orde", + "PlayCount": "Número de reproducións", + "HeaderTranscodingProfileHelp": "Engadir perfís de transcodificación que indiquen os formatos a usar cando transcodificación sexa necesaria.", + "LabelAbortedByServerShutdown": "(Abortado polo feche do servidor)", + "LabelAppNameExample": "Exemplo: Sickbeard, Sonarr", + "LabelDownloadLanguages": "Idiomas de descarregamento", + "HeaderTypeImageFetchers": "Buscadores de imaxes ({0})", + "HeaderUpcomingOnTV": "Proximamente na televisión", + "HeaderVideoQuality": "Calidade do vídeo", + "HeaderVideoType": "Tipo de vídeo", + "HeaderXmlDocumentAttribute": "Atributo do Documento XML", + "HeaderXmlDocumentAttributes": "Atributos do Documento XML", + "LabelAccessStart": "Hora de inicio", + "LabelAccessEnd": "Hora de finalización", + "LabelAlbumArtMaxHeight": "Altura máxima do arte do álbum", + "LabelAllowedRemoteAddresses": "Filtro de enderezos IP remotos", + "LabelAllowedRemoteAddressesMode": "Modo do filtro de enderezos IP remotos", + "LabelAudioBitDepth": "Profundidade de bits de audio", + "LabelAuthProvider": "Provedor de autenticación", + "LabelAutomaticallyAddToCollection": "Engadir automaticamente á colección", + "LabelBaseUrl": "URL base", + "LabelBaseUrlHelp": "Engada un subdirectorio personalizado ao URL do servidor. Por exemplo: http://example.com/<baseurl>", + "LabelCertificatePassword": "Contrasinal de certificado", + "LabelCustomRating": "Avaliación personalizada", + "LabelDateAdded": "Engadido na data", + "LabelDateAddedBehavior": "Comportamento da data de adición para novo contido", + "LabelDashboardTheme": "Tema do panel de control", + "LabelDay": "Día da semana", + "LabelDidlMode": "Modo DIDL", + "LabelDeinterlaceMethod": "Método de desentrelazamento", + "LabelDeviceDescription": "Descrición do dispositivo", + "LabelDefaultUser": "Usuario por defecto", + "LabelDisplayLanguageHelp": "A tradución de Jellyfin é un proxecto en curso.", + "LabelDummyChapterDuration": "Intervalo", + "LabelChapterImageResolution": "Resolución", + "LabelDynamicExternalId": "{0} id", + "LabelPath": "Localización", + "LabelPreferredDisplayLanguage": "Idioma preferido de visualización", + "LabelPublicHttpPort": "Número do porto HTTP público", + "LabelSortTitle": "Clasificar título", + "LabelOverview": "Resumo", + "LabelPlayer": "Reprodutor", + "LabelPasswordRecoveryPinCode": "Código PIN", + "LabelPlayerDimensions": "Dimensións do reprodutor", + "LabelProtocol": "Protocolo", + "LabelSelectVersionToInstall": "Seleccione a versión a instalar", + "LabelYear": "Ano", + "LiveTV": "Televisión en directo", + "OptionNew": "Novo…", + "LabelFont": "Fonte", + "OptionTrackName": "Nome da pista", + "OptionTvdbRating": "Avaliación de TheTVDB", + "Premiere": "Estrea", + "RepeatEpisodes": "Repetir episodios", + "RepeatOne": "Repetir unha vez", + "SeriesYearToPresent": "{0} - Presente", + "Small": "Pequeno", + "SortByValue": "Ordenar por {0}", + "Subtitle": "Subtítulo", + "ChangingMetadataImageSettingsNewContent": "Cambios nos metadatos ou na configuración de descarregamento da arte só será aplicada a novos contidos da biblioteca. Para aplicar os cambios a títulos existentes deberá actualizar os metadatos manualmente.", + "LabelParentalRating": "Clasificación parental", + "LabelOriginalTitle": "Título orixinal", + "LabelPassword": "Contrasinal", + "LabelProfileCodecsHelp": "Separados por coma. Pode ser deixado en branco para usar todos os códecs.", + "LabelPreferredSubtitleLanguage": "Idioma preferido de subtítulos", + "LabelSize": "Tamaño", + "MessageSyncPlayUserJoined": "{0} xuntouse ao grupo.", + "MessageSyncPlayUserLeft": "{0} abandonou o grupo.", + "MetadataManager": "Xestor de metadatos", + "Mute": "Silenciar", + "NewCollection": "Nova colección", + "NewCollectionNameExample": "Exemplo: Colección de Star Wars", + "NextChapter": "Seguinte capítulo", + "Normal": "Normal", + "Notifications": "Notificacións", + "NumLocationsValue": "{0} cartafoles", + "RepeatMode": "Modo de reprodución", + "ResumeAt": "Retomar a partir de {0}", + "Rewind": "Retroceder", + "Runtime": "Tempo de execución", + "ShowMore": "Ver máis", + "SortChannelsBy": "Ordenar canles por", + "Studio": "Estudio", + "SubtitleGreen": "Verde", + "SubtitleYellow": "Amarelo", + "TypeOptionPluralMovie": "Filmes", + "TypeOptionPluralMusicArtist": "Artistas de música", + "TypeOptionPluralBook": "Libros", + "Video": "Vídeo", + "ViewAlbum": "Ver álbum", + "Watched": "Visto", + "WelcomeToProject": "Benvido a Jellyfin!", + "DeletedScene": "Escena eliminada", + "Unreleased": "Aínda non publicado", + "LabelPersonRoleHelp": "Exemplo: Condutor de camión de xeados", + "ListPaging": "{0}-{1} de {2}", + "OptionImdbRating": "Avaliación IMDb", + "OptionIsSD": "SD", + "OptionMax": "Máximo", + "OptionWeekly": "Semanalmente", + "OriginalAirDate": "Data de emisión orixinal", + "OptionWeekends": "Fins de semana", + "People": "Xente", + "PlaybackRate": "Velocidade de reprodución", + "Premieres": "Estreas", + "MessageSent": "Mensaxe enviada.", + "SubtitleBlue": "Azul", + "SubtitleCyan": "Cian", + "SubtitleBlack": "Negro", + "TypeOptionPluralMusicAlbum": "Álbums de música", + "TypeOptionPluralMusicVideo": "Vídeos musicais", + "No": "Non", + "OnApplicationStartup": "Ao iniciar o servidor", + "OneChannel": "Unha canle", + "OnlyForcedSubtitles": "Só subtítulos forzados", + "Option3D": "3D", + "ValueSongCount": "{0} cancións", + "Monday": "Luns", + "MinutesBefore": "minutos antes", + "News": "Noticias", + "NewEpisodesOnly": "Só novos episodios", + "Next": "Seguinte", + "OptionIsHD": "HD", + "OptionPlayCount": "Número de reproducións", + "Overview": "Resumo", + "OtherArtist": "Outro artista", + "SeriesCancelled": "Serie cancelada.", + "SeriesSettings": "Configuración das series", + "StopPlayback": "Deter a reprodución", + "TabCodecs": "Códecs", + "Writers": "Escritores", + "Writer": "Escritor", + "Interview": "Entrevista", + "Scene": "Escena", + "Sample": "Mostra", + "BehindTheScenes": "Detrás das cámaras", + "IgnoreDts": "Ignorar DTS (decoding timestamp)", + "MediaInfoBitDepth": "Profundidade de bits", + "MessageConfirmRestart": "Está seguro de que desexa reiniciar Jellyfin?", + "EnableSplashScreen": "Habilitar a pantalla de benvida", + "LabelDateTimeLocale": "Formato da data e hora local", + "NewCollectionHelp": "As coleccións permiten crear grupos personalizados de filmes e outros contidos da biblioteca.", + "NewEpisodes": "Novos episodios", + "SendMessage": "Enviar mensaxe", + "TypeOptionPluralSeries": "Series de televisión", + "ValueDiscNumber": "Disco {0}", + "ValueMovieCount": "{0} filmes", + "List": "Lista", + "HeaderSelectTranscodingPathHelp": "Navegue ou introduza a localización para o transcodificador de ficheiros. O cartafol debe ter permisos de escritura.", + "HeaderServerAddressSettings": "Configuración do enderezo do servidor", + "HeaderSetupLibrary": "Configurar as súas bibliotecas multimedia", + "HeaderSubtitleProfilesHelp": "Os perfís de subtítulos describen os formatos soportados polo dispositivo.", + "HeaderSyncPlaySettings": "Configuración de SyncPlay", + "HeaderUninstallPlugin": "Desinstalar Plugin", + "LabelAccessDay": "Día da semana", + "LabelAlbumArtMaxResHelp": "Resolución máxima da arte do álbum exposta por medio da propiedade 'upnp:albumArtURI'.", + "LabelAppName": "Nome da aplicación", + "LabelColorPrimaries": "Cores primarias", + "LabelColorTransfer": "Transferencia de cor", + "LabelDeveloper": "Desenvolvedor", + "LabelCommunityRating": "Avaliación da comunidade", + "LabelDiscNumber": "Número de disco", + "LabelDisplayLanguage": "Idioma", + "LabelContentType": "Tipo de contido", + "LabelCreateHttpPortMap": "Habilitar automaticamente o mapeamento de portos para tráfico HTTP e HTTPS.", + "LabelCriticRating": "Avaliación dos críticos", + "LabelCustomCss": "Código CSS personalizado", + "LabelDefaultScreen": "Pantalla por defecto", + "LabelPostProcessorArguments": "Argumentos da liña de comandos de post-procesamento", + "SubtitleLightGray": "Gris claro", + "SubtitleMagenta": "Maxenta", + "Subtitles": "Subtítulos", + "LabelProtocolInfo": "Información do protocolo", + "LabelRuntimeMinutes": "Tempo de execución", + "LabelSaveLocalMetadata": "Gardar imaxes e metadatos nos cartafoles multimedia", + "Tuesday": "Martes", + "TypeOptionPluralAudio": "Audios", + "TypeOptionPluralEpisode": "Episodios", + "ValueSeriesCount": "{0} series", + "MoveLeft": "Mover á esquerda", + "LabelLanNetworks": "Redes locais", + "LabelScheduledTaskLastRan": "Ultima execución {0}, tomando {1}.", + "LabelSerialNumber": "Número de serie", + "LabelSortOrder": "Orde", + "MillisecondsUnit": "ms", + "SecondarySubtitles": "Subtítulos secundarios", + "Yesterday": "Onte" } diff --git a/src/strings/gsw.json b/src/strings/gsw.json index 0a2daf054c..d7a004e911 100644 --- a/src/strings/gsw.json +++ b/src/strings/gsw.json @@ -100,7 +100,7 @@ "AccessRestrictedTryAgainLater": "Zuegriff isch momentan beschränkt. Probiers bitte spöter nomol.", "ButtonSyncPlay": "SyncPlay", "AdditionalNotificationServices": "Durchsuech de Plugin Katalog zum zuesätzlichi Benochrichtigungsdienst zinstalliere.", - "AddedOnValue": "hinzuegfüegt", + "AddedOnValue": "hinzuegfüegt {0}", "AddToPlaylist": "Zur Playlist hinzuefüege", "AddToPlayQueue": "Zur Warteschlange hinzuefüege", "AddToCollection": "Zur Collection hinzuefüege", diff --git a/src/strings/he.json b/src/strings/he.json index e4020ec76b..9825cc17e6 100644 --- a/src/strings/he.json +++ b/src/strings/he.json @@ -5,12 +5,12 @@ "AddToPlayQueue": "הוסף לתור ברשימת ניגון", "AddToPlaylist": "הוסף לרשימת ניגון", "AdditionalNotificationServices": "עיין ברשימת התוספים להתקנת שירותי התראות נוספים.", - "All": "הכל", + "All": "הכול", "AllChannels": "כל הערוצים", "AllEpisodes": "כל הפרקים", "AllLibraries": "כל הספריות", "Anytime": "בכל עת", - "AroundTime": "בסביבות", + "AroundTime": "בסביבות {0}", "AsManyAsPossible": "כמה שיותר", "Backdrops": "תמונות רקע", "BirthLocation": "מיקום לידה", @@ -60,7 +60,7 @@ "FileReadCancelled": "קריאת הקובץ בוטלה.", "FileReadError": "חלה שגיאה בקריאת הקובץ.", "Friday": "שישי", - "Genres": "ז'אנרים", + "Genres": "ז׳אנרים", "GroupVersions": "גרסאות קבוצתיות", "GuestStar": "כוכב אורח", "HDPrograms": "תוכניות HD", @@ -363,7 +363,7 @@ "Studios": "אולפני", "Subtitles": "כתוביות", "Sunday": "ראשון", - "Sync": "סנכרן", + "Sync": "סנכרון", "SystemDlnaProfilesHelp": "פרופלי מערכת הם לקריאה בלבד. שינויים בפרופילי מערכת ישמרו לפרופיל מוצאם אישית חדש.", "TabAccess": "גישה", "TabAdvanced": "מתקדם", @@ -440,7 +440,7 @@ "Folders": "תיקיות", "Collections": "אוספים", "Channels": "ערוצים", - "HeaderContinueWatching": "המשך לצפות", + "HeaderContinueWatching": "להמשיך לצפות", "AllowOnTheFlySubtitleExtraction": "אפשר חילוץ כתוביות בזמן אמת", "AllowHWTranscodingHelp": "אפשר למקלט לקודד הזרמות בזמן אמת. זה עשוי לעזור בהפחתת הקידוד שנעשה ע\"י השרת.", "AllComplexFormats": "כל הפורמטים המורכבים (ASS, SSA, VobSub, PGS, SUB, IDX, …)", @@ -605,7 +605,7 @@ "ShowYear": "הצג שנה", "ShowTitle": "הצג כותרת", "DropShadow": "צייר צל", - "Playlists": "רשימות הפעלה", + "Playlists": "רשימות נגינה", "Raised": "מורם", "LabelSpecialSeasonsDisplayName": "שם תצוגת \"עונה מיוחדת\"", "LabelSource": "מקור", @@ -832,7 +832,7 @@ "Authorize": "הרשה", "EnableStreamLoopingHelp": "הפעל הגדרה זו אם שידורים חיים מכילים רק מספר שניות של מידע ומצריכים בקשות חוזרות. הפעלת הגדרה זו ללא צורך עלולה לגרום לבעיות.", "EnableQuickConnect": "אפשר חיבור מהיר בשרת זה", - "EnableAutoCast": "הגדר כברירת מחדל", + "EnableAutoCast": "הגדרה כברירת מחדל", "EasyPasswordHelp": "הקוד הסודי הפשוט שלך משמש לגישה לא מקוונת במכשירים נתמכים ויכול לשמש לחיבור פשוט בתוך הרשת.", "DirectStreamHelp2": "צריכת החשמל של ניגון ישיר בד\"כ תלויה בפרופיל השמע. רק זרם הוידאו הוא lossless.", "DirectStreamHelp1": "זרם הווידאו תואם את המכשיר, אך יש לו פורמט שמע לא תואם (DTS, Dolby TrueHD, וכו'...) או מספר ערוצי שמע. זרם הווידאו ייארז מחדש ללא הפסד תוך כדי תנועה לפני שישלח למכשיר. רק זרם השמע יקודד.", @@ -946,7 +946,7 @@ "DisablePlugin": "השבת", "DisableCustomCss": "השבת CSS מותאם אישית המסופק על ידי השרת", "EnablePlugin": "אפשר", - "DirectPlayHelp": "קובץ המקור תואם לחלוטין עם קליינט זה, הסשן מקבל את הקובץ ללא שינויים.", + "DirectPlayHelp": "קובץ המקור תואם לחלוטין ללקוח זה וההפעלה מקבלת את הקובץ ללא שינויים.", "DeviceAccessHelp": "זה חל רק על מכשירים הניתנים לזיהוי ייחודי ולא ימנעו גישה לדפדפן. סינון גישה למכשירי משתמש ימנע מהם להשתמש במכשירים חדשים עד לאישורם כאן.", "Conductor": "מנצח", "Arranger": "מעבד מוזיקלי", @@ -1030,7 +1030,7 @@ "LabelAudioSampleRate": "קצב דגימת שמע", "LabelAlbumArtHelp": "אלבום", "LabelBlockContentWithTags": "חסום פריטים עם תגיות", - "LabelCustomCertificatePathHelp": "נתיב", + "LabelCustomCertificatePathHelp": "נתיב לקובץ PKCS #12 שמכיל אישור ומפתח פרטי כדי להפעיל תמיכה ב־TLS בשם תחום מותאם אישית.", "IgnoreDtsHelp": "השבתת אפשרות זו עשויה לפתור בעיות מסוימות, למשל. אודיו חסר בערוצים עם זרמי אודיו ווידאו נפרדים.", "HeaderContainerProfileHelp": "פרופילי קונטיינר מציינים את המגבלות של מכשיר בעת הפעלת קידודים ספציפיים. אם חלה מגבלה אז המדיה תקודד מחדש, גם אם הקידוד מוגדר להפעלה ישירה.", "LabelAutoDiscoveryTracingHelp": "כאשר מופעל, חבילות רשת המתקבלות בפורט הגילוי האוטומטי יתועדו.", @@ -1048,7 +1048,7 @@ "LabelCreateHttpPortMap": "אפשר מיפוי פורטים אוטומטי לכל תעבורת ה-HTTP וגם ה-HTTPS.", "DownloadAll": "הורד הכל", "Experimental": "ניסיוני", - "LabelDisableCustomCss": "אפשר קוד css מותאם אישית בשביל עיצובים מהשרת", + "LabelDisableCustomCss": "השבתת קוד CSS לעיצוב/מיתוג שסופקו על ידי השרת.", "LabelEasyPinCode": "קוד PIN קל", "EnableCardLayout": "הצג מערך ארגז חזותי", "LabelCachePathHelp": "בחר מיקום עבור קבצי מטמון לשרת כגון תמונות. השאר ריק בכדי להשתמש במיקום ברירת מחדל.", @@ -1056,5 +1056,125 @@ "HeaderPerformance": "ביצועים", "HeaderRecordingMetadataSaving": "מטאדאטה של הקלטות", "HeaderDummyChapter": "תמונות פרק", - "Unreleased": "לא יצא עדיין" + "Unreleased": "לא יצא עדיין", + "AllowCollectionManagement": "הרשה למשתמש זה לנהל אוספים", + "LabelEnableIP4": "הפעלת IPv4", + "Yes": "כן", + "LabelLanNetworks": "רשתות מקומיות", + "LabelLibraryPageSize": "גודל עמוד ספרייה", + "LabelLevel": "רמה", + "HeaderEpisodesStatus": "מצב פרקים", + "EnableAudioNormalization": "נרמול עצמת שמע", + "GetThePlugin": "קבלת התוסף", + "LabelDateTimeLocale": "תבנית תאריך שעה", + "LabelDateAddedBehaviorHelp": "אם קיים ערך נתוני־על, תמיד ייעשה בו שימוש לפני כל אחת מהאפשרויות האלה.", + "LabelEnableIP6Help": "הפעלת יכולות IPv6.", + "LabelEnableSingleImageInDidlLimit": "הגבלה לתמונה מוטמעת יחידה", + "HeaderConfirmRepositoryInstallation": "אישור התקנת מאגר תוסף", + "LabelDeveloper": "פיתוח", + "LabelHardwareEncoding": "קידוד חומרה", + "LabelDate": "תאריך", + "LabelDroppedFrames": "תמוניות מושמטות", + "LabelDropShadow": "הטלת צל", + "LabelChapterImageResolution": "רזולוציה", + "LabelHardwareAccelerationType": "האצת חומרה", + "LabelPlayerDimensions": "ממדי נגן", + "LabelQuickConnectCode": "קוד התחברות מהירה", + "LabelReasonForTranscoding": "סיבה להתמרה", + "MediaInfoBitrate": "קצב סיביות", + "MediaInfoCodec": "מפענח", + "MediaInfoContainer": "מכולה", + "LabelLocalHttpServerPortNumberHelp": "מספר פתחת ה־TCP של שרת ה־HTTP.", + "Typewriter": "מכונת כתיבה", + "Upload": "העלאה", + "TabRepositories": "מאגרים", + "Trailer": "קדימון", + "TitlePlayback": "נגינה", + "Never": "אף פעם", + "TabOther": "אחר", + "Unmute": "ביטול השתקה", + "Vertical": "אנכי", + "Schedule": "לו״ז", + "Played": "התנגן", + "Recordings": "הקלטות", + "MillisecondsUnit": "מ״ש", + "LabelMessageText": "טקסט ההודעה", + "LabelSelectAudioChannels": "ערוצים", + "LabelSelectMono": "מונו", + "LabelPasswordResetProvider": "ספק אימות סיסמה", + "LabelProtocolInfo": "פרטי פרוטוקול", + "MediaInfoChannels": "ערוצים", + "MediaInfoDefault": "ברירת מחדל", + "LabelLoginDisclaimer": "כתב ויתור בכניסה", + "LabelMaxMuxingQueueSize": "גודל תור ריבוב מרבי", + "LabelMoviePrefix": "קידומת סרט", + "LabelPlayMethod": "שיטת נגינה", + "LabelUsername": "שם משתמש", + "Scene": "סצנה", + "TV": "טלוויזיה", + "LabelScreensaver": "שומר מסך", + "MediaInfoSize": "גודל", + "LabelMediaDetails": "פרטי מדיה", + "LabelProtocol": "פרוטוקול", + "TypeOptionPluralMovie": "סרטים", + "LabelMatchType": "סוג התאמה", + "OptionProtocolHttp": "HTTP", + "Photo": "תמונה", + "Thumb": "תמונה ממוזערת", + "TypeOptionPluralVideo": "סרטונים", + "LabelTypeText": "טקסט", + "MediaInfoExternal": "חיצוני", + "OptionEquals": "שווה", + "Quality": "איכות", + "TypeOptionPluralBook": "ספרים", + "TypeOptionPluralAudio": "קטעי שמע", + "Video": "סרטון", + "Yadif": "YADIF", + "LabelMaxChromecastBitrate": "איכות הזרמת Google Cast", + "LabelMethod": "שיטה", + "LabelNewsCategories": "קטגוריות חדשות", + "LabelTag": "תגית", + "Programs": "תוכניות", + "LabelManufacturer": "יצרן", + "LabelOptionalNetworkPath": "תיקייה רשת משותפת", + "LabelStable": "יציב", + "LabelSelectStereo": "סטריאו", + "LabelModelNumber": "מספר דגם", + "LabelServerHost": "מארח", + "LabelStopping": "נעצר", + "MediaInfoForced": "כפוי", + "MediaInfoResolution": "רזולוציה", + "Movie": "סרט", + "New": "חדש", + "Preview": "תצוגה מקדימה", + "Previous": "הקודם", + "Poster": "כרזה", + "Trailers": "קדימונים", + "LabelMovieCategories": "קטגוריות סרטים", + "LabelLibraryPageSizeHelp": "הגדרת כמות הפריטים להצגה בעמוד ספרייה. 0 משבית דפדוף.", + "TabContainers": "מכולות", + "Transcoding": "התמרה", + "LabelMoviePrefixHelp": "אם חלה קידומת על כותרות סרטים, יש למלא אותה כאן כדי שהשרת יטפל בה כראוי.", + "TabResponses": "תגובות", + "LabelNewName": "שם חדש", + "LabelSize": "גודל", + "MediaInfoTimestamp": "חותמת זמן", + "LabelManufacturerUrl": "כתובת יצרן", + "LabelModelDescription": "תיאור דגם", + "LabelModelName": "שם דגם", + "LabelMovieRecordingPath": "נתיב הקלטת סרטים", + "LabelOpenclDevice": "התקן OpenCL", + "LabelValue": "ערך", + "Rate": "דירוג", + "TypeOptionPluralSeason": "עונות", + "LabelVersion": "גרסה", + "Track": "רצועה", + "TypeOptionPluralEpisode": "פרקים", + "Controls": "פקדים", + "LabelProfileContainer": "מכולה", + "LabelLoginDisclaimerHelp": "הודעה שתופיע בתחתית עמוד הכניסה.", + "LabelModelUrl": "כתובת דגם", + "Restart": "הפעלה מחדש", + "TabStreaming": "הזרמה", + "Metadata": "נתוני על" } diff --git a/src/strings/hi-in.json b/src/strings/hi-in.json index 2607740cfe..1de37264f2 100644 --- a/src/strings/hi-in.json +++ b/src/strings/hi-in.json @@ -27,7 +27,7 @@ "Albums": "एल्बम", "Aired": "प्रसारित हो चुका है", "AdditionalNotificationServices": "दी गयी सूची में से प्लगिन इनस्टॉल करें|", - "AddedOnValue": "जोड़ दिया", + "AddedOnValue": "जोड़ दिया {0}", "AddToPlaylist": "प्लेलिस्ट में जोड़ें", "AllowMediaConversionHelp": "मीडिया परिवर्तन के लिये अनुमति दें या इनकार करें.", "AllowMediaConversion": "मीडिया रूपांतरण की अनुमति दें", @@ -75,7 +75,7 @@ "Artists": "कलाकारों", "Artist": "कलाकार", "Art": "कला", - "AroundTime": "लगभग", + "AroundTime": "लगभग {0}", "Anytime": "किसी भी समय", "AnyLanguage": "कोई भी भाषा", "AlwaysPlaySubtitlesHelp": "भाषा की वरीयता से मेल खाने वाले उपशीर्षक ऑडियो भाषा की परवाह किए बिना लोड किए जाएंगे।", diff --git a/src/strings/hr.json b/src/strings/hr.json index b2c7cf2e61..dd9a518e09 100644 --- a/src/strings/hr.json +++ b/src/strings/hr.json @@ -8,7 +8,7 @@ "AllEpisodes": "Sve epizode", "AllowHWTranscodingHelp": "Dopusti tuneru konverziju strujanja tokom reprodukcije. Smanjuje broj konverzija koje obavlja server.", "Anytime": "Bilo kada", - "AroundTime": "Oko", + "AroundTime": "Oko {0}", "AsManyAsPossible": "Što je više moguće", "Backdrops": "Pozadine", "BirthDateValue": "Rođen: {0}", @@ -1361,5 +1361,14 @@ "LabelAutomaticallyAddToCollectionHelp": "Kada najmanje 2 filma imaju isti naziv kolekcije, automatski će se dodati u kolekciju.", "Suggestions": "Prijedlozi", "Trailers": "Najave", - "Small": "Malo" + "Small": "Malo", + "HeaderDummyChapter": "Slike Poglavlja", + "EnableAudioNormalization": "Normalizacija zvuka", + "Experimental": "Eksperimentalno", + "HeaderPerformance": "Performanse", + "DownloadAll": "Preuzmi sve", + "HeaderRecordingMetadataSaving": "Snimanje metapodataka", + "LabelChapterImageResolution": "Rezolucija", + "AllowCollectionManagement": "Dozvoli ovom korisniku da upravlja kolekcijama", + "LabelDummyChapterDuration": "Interval" } diff --git a/src/strings/hu.json b/src/strings/hu.json index 3f4df91467..3b80d251b8 100644 --- a/src/strings/hu.json +++ b/src/strings/hu.json @@ -388,7 +388,7 @@ "Subtitles": "Feliratok", "Suggestions": "Javaslatok", "Sunday": "Vasárnap", - "Sync": "Szinkronizál", + "Sync": "Szinkronizálás", "TabAccess": "Hozzáférés", "TabAdvanced": "Speciális", "TabCatalog": "Katalógus", @@ -454,7 +454,7 @@ "AlwaysPlaySubtitles": "Mindig jelenjen meg", "AnyLanguage": "Bármelyik nyelv", "Anytime": "Bármikor", - "AroundTime": "kb", + "AroundTime": "Körülbelül {0}", "AsManyAsPossible": "Amennyi lehetséges", "AspectRatio": "Képarány", "Auto": "Auto", @@ -563,7 +563,7 @@ "HeaderActivity": "Tevékenységek", "HeaderAdditionalParts": "További részek", "HeaderAdmin": "Felügyelet", - "HeaderAlbumArtists": "Album előadó(k)", + "HeaderAlbumArtists": "Albumelőadók", "HeaderAlert": "Figyelem", "HeaderAllowMediaDeletionFrom": "Médiatörlés engedélyezése innen", "HeaderApiKey": "API kulcs", @@ -638,8 +638,8 @@ "Photos": "Fényképek", "Playlists": "Lejátszási listák", "Shows": "Sorozatok", - "Songs": "Dalok", - "ValueSpecialEpisodeName": "Special - {0}", + "Songs": "Számok", + "ValueSpecialEpisodeName": "Különkiadás – {0}", "EnableThemeVideosHelp": "Témavideókat játszhat a háttérben a könyvtár böngészése közben.", "HeaderBlockItemsWithNoRating": "Blokkolja azokat az elemeket amelyek tiltott, vagy nem felismerhető minősítésűek", "HeaderSeriesStatus": "Sorozat állapot", @@ -777,7 +777,7 @@ "LabelNewName": "Új név", "LabelNewsCategories": "Hírek kategóriái", "LabelNumber": "Szám", - "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a kliensek más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz. Például: {0{ vagy {1}.", + "LabelOptionalNetworkPathHelp": "Ha ez a mappa meg van osztva a hálózaton, a hálózati megosztási útvonal megadása lehetővé teszi, hogy a kliensek más eszközökön közvetlenül hozzáférjenek a médiafájlokhoz. Például: {0} vagy {1}.", "LabelPasswordConfirm": "Jelszó (megerősítés)", "LabelPlaceOfBirth": "Születési hely", "LabelPostProcessor": "A feldolgozás utáni alkalmazás", @@ -892,11 +892,11 @@ "MessageDirectoryPickerLinuxInstruction": "Az Arch Linux, CentOS, Debian, Fedora, openSUSE vagy Ubuntu Linux operációs rendszereken a Jellyfin szolgáltatás felhasználójának legalább olvasási hozzáférést kell biztosítania a tárolóhelyekhez.", "MessageForgotPasswordInNetworkRequired": "Kérlek próbáld meg újra a jelszó visszaállítási folyamatot az otthoni hálózatban.", "MessageNoMovieSuggestionsAvailable": "Jelenleg nincsenek filmajánlatok. Kezdj el nézni és értékelni a filmeket, majd térj vissza, hogy megtekinthesd az ajánlásokat.", - "MessagePasswordResetForUsers": "A következő felhasználók jelszavai visszaálltak. Most már bejelentkezhetnek a visszaállításhoz használt PIN kódokkal.", + "MessagePasswordResetForUsers": "A következő felhasználók jelszavai visszaállításra kerültek. Most már bejelentkezhetnek a visszaállításhoz használt PIN kódokkal.", "MessagePlayAccessRestricted": "A tartalom lejátszása jelenleg korlátozott. További információért fordulj a Szerver üzemeltetőjéhez.", "MessagePleaseWait": "Kérlek várj. Ez eltarthat egy percet.", "MessagePluginConfigurationRequiresLocalAccess": "A bővítmény beállításához jelentkezz be közvetlenül a helyi szerverre.", - "MessagePluginInstallDisclaimer": "A közösség tagjai által készített bővítmények nagyszerű módot adnak a felhasználói élmény bővítéséhez. Telepítés előtt kérlek vedd figyelembe a szerverre gyakorolt hatásokat, mint például a hosszabb könyvtárvizsgálatokat, a további háttérfeldolgozást, vagy akár a rendszer stabilitásának csökkenését.", + "MessagePluginInstallDisclaimer": "FIGYELEM: A harmadik féltől származó bővítmények telepítése veszélyeket rejthet, instabil vagy kártékony kódot tartalmazhat és bármikor módosulhat. Csak megbízható készítők bővítményeit telepítsd és vedd figyelembe a szerverre gyakorolt lehetséges hatásokat, mint például a külső szerverre való adatküldés, a további háttérfeldolgozás, vagy a rendszer stabilitás csökkenésének veszélyeit.", "MessageReenableUser": "Az újra engedélyezéshez lásd lentebb", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "A következő médiahelyek eltávolításra kerülnek a könyvtáradból", "MessageUnableToConnectToServer": "Jelenleg nem tudunk csatlakozni a kiválasztott szerverhez. Győződj meg róla, hogy fut és próbáld meg újra.", @@ -1049,7 +1049,7 @@ "TV": "TV", "TabDirectPlay": "Közvetlen lejátszás", "TabResponses": "Válaszok", - "TabStreaming": "Streaming", + "TabStreaming": "Streamelés", "TagsValue": "Címkék: {0}", "ThemeSongs": "Téma dalok", "ThemeVideos": "Téma videók", @@ -1397,7 +1397,7 @@ "LabelTonemappingRange": "Tónusleképezés tartomány", "TonemappingAlgorithmHelp": "A tónusleképezés finomhangolható. Ha még nem ismered ezeket az opciókat, tartsd meg az alapértelmezett értéket. Az ajánlott érték 'BT.2390'.", "LabelTonemappingAlgorithm": "Válaszd ki a használni kívánt tónusleképezési algoritmust", - "AllowTonemappingHelp": "A tónusleképezés átalakíthatja a videó dinamikatartományát HDR-ről SDR-re, miközben megőrzi a kép részleteit és színeit, amelyek nagyon fontos információk az eredeti jelenet ábrázolásához. Jelenleg csak HDR10 vagy HLG videókkal működik. Ehhez a megfelelő OpenCL vagy CUDA futási környezet szükséges.", + "AllowTonemappingHelp": "A tónusleképezés átalakíthatja a videó dinamikatartományát HDR-ről SDR-re, miközben megőrzi a kép részleteit és színeit, amelyek nagyon fontos információk az eredeti jelenet ábrázolásához. Jelenleg csak 10 bites HDR10, HLG vagy DoVi videókkal használható. Működéséhez a megfelelő OpenCL vagy CUDA futási környezet szükséges.", "EnableTonemapping": "Tónusleképezés engedélyezése", "LabelOpenclDeviceHelp": "Ez az OpenCL eszköz, amelyet a tónusleképezéshez használnak. A pont bal oldala a platform száma, a jobb oldala pedig a platformon található eszköz száma. Az alapértelmezett érték 0.0. Az OpenCL hardveres gyorsítási módszert tartalmazó FFmpeg alkalmazásfájl szükséges.", "LabelOpenclDevice": "OpenCL eszköz", @@ -1508,7 +1508,7 @@ "DisablePlugin": "Letiltás", "EnablePlugin": "Engedélyezés", "Framerate": "Képkockasebesség", - "DirectPlayHelp": "A forrásfájl teljes mértékben kompatibilis ezzel az klienssel, és a munkamenet módosítások nélkül fogadja a fájlt.", + "DirectPlayHelp": "A forrásfájl teljes mértékben kompatibilis ezzel a klienssel, és a munkamenet módosítások nélkül fogadja a fájlt.", "HeaderContinueReading": "Olvasás folytatása", "EnableGamepadHelp": "Figyeljen bármilyen csatlakoztatott bemenetre. (Szükséges: TV megjelenítési mód)", "LabelEnableGamepad": "Engedélyezze a Gamepad-ot", @@ -1662,7 +1662,7 @@ "MediaInfoVideoRangeType": "Videó tartomány típusa", "LabelVideoRangeType": "Videó tartomány típusa", "VideoRangeTypeNotSupported": "A videó tartománytípusa nem támogatott", - "LabelVppTonemappingContrastHelp": "Kontraszt növelésének mértéke VPP tónusleképezés használatakor. Az ajánlott és az alapértelmezett érték 1,2 és 1.", + "LabelVppTonemappingContrastHelp": "Kontraszt növelésének mértéke VPP tónusleképezés használatakor. Az ajánlott és az alapértelmezett érték 1.", "LabelVppTonemappingContrast": "VPP tónusleképezés kontrasztszintje", "LabelVppTonemappingBrightnessHelp": "Fényerő növelésének mértéke VPP tónusleképezés használatakor. Az ajánlott és az alapértelmezett érték 0.", "LabelVppTonemappingBrightness": "VPP tónusleképezés fényerőszintje", @@ -1710,11 +1710,56 @@ "LabelDummyChapterDuration": "Intervallum", "LabelDummyChapterCount": "Határvonal", "LabelChapterImageResolution": "Felbontás", - "LabelChapterImageResolutionHelp": "A kinyert fejezetképek felbontása.", + "LabelChapterImageResolutionHelp": "A kinyert fejezetképek felbontása. Megváltoztatása nincs hatással a már kinyert képekre.", "HeaderPerformance": "Teljesítmény", "Short": "Rövidfilm", "Featurette": "Mellékfilm", "LabelParallelImageEncodingLimitHelp": "Maximálisan engedélyezett egyidejűleg futó képkódolások száma. 0-ra állítva a rendszer specifikációja alapján lesz kiválasztva a limit.", "ResolutionMatchSource": "Forrásnak megfelelő", - "LabelParallelImageEncodingLimit": "Egyidejű képkódolási limit" + "LabelParallelImageEncodingLimit": "Egyidejű képkódolási limit", + "AllowCollectionManagement": "Ez a felhasználó módosíthatja a gyűjteményeket", + "AllowSegmentDeletion": "Szegmensek törlése", + "EnableAudioNormalization": "hang normalizálás", + "AllowSegmentDeletionHelp": "Régi szegmensek törlése miután a Kliens számára elküldésre kerültek. Ez megakadályozza, hogy a teljes, átkódolt fájlt szükséges legyen a merevlemezen tárolni. Csak akkor működik ha a visszafogás funkció be van kapcsolva. Kapcsold ki ha visszajátszási problémákba ütközöl.", + "LabelThrottleDelaySecondsHelp": "Másodpercben megadott érték melyet követően az átkódoló visszfogásra kerül. Elég nagy értéket szükséges megadni, hogy a kliens számára egy egészséges puffer maradjon. Csak akkor működik ha a visszafogás funkció be van kapcsolva.", + "LabelSegmentKeepSeconds": "Szegmensek megtartása", + "LabelThrottleDelaySeconds": "Visszafogás", + "LabelSegmentKeepSecondsHelp": "Másodpercben megadott érték melyet követőeg a szegmensek felülírásra kerülnek. Nagyobbnak kell lennie mint a visszafogás időkorlátja. Csak akkor működik ha a szegmensek törlése funkció be van kapcsolva.", + "LabelDummyChapterDurationHelp": "Az üres fejezetek közötti intervallum. Állítsa 0-ra az álfejezet generálásának letiltásához. Ennek megváltoztatása nincs hatással a meglévő álfejezetekre.", + "PasswordRequiredForAdmin": "Az adminisztrátori fiókokhoz jelszó szükséges.", + "UserMenu": "Felhasználói Menü", + "Studio": "Stúdió", + "LabelTonemappingMode": "Tónus leképezési mód", + "Select": "Választás", + "LabelDate": "Dátum", + "LabelSystem": "Rendszer", + "LabelSyncPlayNoGroups": "Nincsenek elérhető csoportok", + "LogLevel.Trace": "Nyom", + "LogLevel.Information": "Információ", + "HeaderEpisodesStatus": "Epizódok állapota", + "LabelLevel": "Szint", + "PleaseConfirmRepositoryInstallation": "Kattintson az OK gombra, hogy megerősítse, hogy elolvasta a fentieket, és folytatni kívánja a bővítménytár telepítését.", + "SaveRecordingNFOHelp": "Mentse el az EPG-listaszolgáltató metaadatait a médiával együtt.", + "EnableAudioNormalizationHelp": "A hang normalizálása állandó erősítést ad hozzá, hogy az átlagot a kívánt szinten tartsa (-18 dB).", + "GetThePlugin": "Bővítmény beszerzése", + "LabelEnableLUFSScanHelp": "LUFS-keresés engedélyezése a zenék számára (Ez tovább tarthat és több erőforrást igényel).", + "Notifications": "Értesítések", + "NotificationsMovedMessage": "Az értesítési funkció átkerült a Webhook beépülő modulba.", + "MenuClose": "Menü Bezárása", + "MenuOpen": "Menü Megnyitása", + "LabelEnableAudioVbr": "VBR hangkódolás engedélyezése", + "LabelEnableAudioVbrHelp": "A változó bitsebesség jobb minőséget kínál az átlagos bitráta arányhoz, de néhány ritka esetben pufferelési és kompatibilitási problémákat okozhat.", + "SaveRecordingImagesHelp": "Mentse el az EPG-lista szolgáltatójának képeit a médiával együtt.", + "HeaderConfirmRepositoryInstallation": "Erősítse meg a bővítmény tároló telepítését", + "LabelEnableLUFSScan": "LUFS-keresés engedélyezése", + "LabelDeveloper": "Fejlesztő", + "LabelMediaDetails": "Média részletei", + "LogLevel.Debug": "Hibakeresés", + "LogLevel.Warning": "Figyelmeztetés", + "LogLevel.Error": "Hiba", + "LogLevel.Critical": "Kritikus", + "LogLevel.None": "Egyik sem", + "MessageRepositoryInstallDisclaimer": "FIGYELMEZTETÉS: Harmadik féltől származó beépülő modulok tárolójának telepítése kockázatokkal jár. Instabil vagy rosszindulatú kódot tartalmazhat, és bármikor megváltozhat. Csak olyan szerzők tárolóit telepítse, akikben megbízik.", + "TonemappingModeHelp": "Válassza ki a tónus leképezési módot. Ha kiégett fénypontokat tapasztal, próbáljon átváltani RGB módra.", + "Unknown": "Ismeretlen" } diff --git a/src/strings/hy.json b/src/strings/hy.json new file mode 100644 index 0000000000..367a98d082 --- /dev/null +++ b/src/strings/hy.json @@ -0,0 +1,3 @@ +{ + "Absolute": "բացարձակ" +} diff --git a/src/strings/id.json b/src/strings/id.json index 4691c5054f..34a2cbe270 100644 --- a/src/strings/id.json +++ b/src/strings/id.json @@ -123,7 +123,7 @@ "Ascending": "Urutan naik", "AsManyAsPossible": "Sebanyak mungkin", "Art": "Clearart", - "AroundTime": "Sekitar", + "AroundTime": "Sekitar {0}", "Anytime": "Kapanpun", "AnyLanguage": "Bahasa apapun", "AlwaysPlaySubtitlesHelp": "Subtitle yang cocok dengan preferensi bahasa akan dimuat terlepas dari bahasa audionya.", diff --git a/src/strings/it.json b/src/strings/it.json index e018038d97..a36744d9f4 100644 --- a/src/strings/it.json +++ b/src/strings/it.json @@ -27,7 +27,7 @@ "AlwaysPlaySubtitlesHelp": "I sottotitoli corrispondenti alla lingua preferita saranno caricati a prescindere dalla lingua dell'audio.", "AnyLanguage": "Qualsiasi lingua", "Anytime": "In qualsiasi momento", - "AroundTime": "Circa", + "AroundTime": "Circa {0}", "Artists": "Artisti", "AsManyAsPossible": "Tutto il possibile", "Ascending": "Crescente", @@ -763,12 +763,12 @@ "MessageNoPluginsInstalled": "Non hai plugin installati.", "MessageNoTrailersFound": "Installa il canale dei trailer per migliorare la tua esperienza cinematografica aggiungendo una libreria di trailer da internet.", "MessageNothingHere": "Non c'è niente qui.", - "MessagePasswordResetForUsers": "I seguenti utenti hanno avuto le loro password resettate. Possono accedere con i codici PIN Semplificati che sono stati utilizzati per eseguire il reset.", + "MessagePasswordResetForUsers": "I seguenti utenti hanno avuto le loro password resettate. Ora possono accedere con i codici PIN che sono stati utilizzati per eseguire il reset.", "MessagePlayAccessRestricted": "Le riproduzione di questi contenuti è bloccata. Per favore contatta il tuo amministratore del server per maggiori informazioni.", "MessagePleaseEnsureInternetMetadata": "Assicurarsi che il download dei metadati Internet sia abilitato.", "MessagePleaseWait": "Per favore attendi. La procedura potrebbe impiegare qualche minuto.", "MessagePluginConfigurationRequiresLocalAccess": "Per configurare questo plugin si prega di accedere al proprio server locale direttamente.", - "MessagePluginInstallDisclaimer": "I plugin creati dai membri della comunità sono un ottimo modo per migliorare l'esperienza con funzionalità e vantaggi aggiuntivi. Prima di installarli, si prega di notare gli effetti che possono avere sul tuo Server, come le scansioni più lunghe della libreria, l'elaborazione di sfondo aggiuntiva e la stabilità del sistema diminuita.", + "MessagePluginInstallDisclaimer": "ATTENZIONE: L'installazione di plugin di terze parti può portare dei rischi. Può contenere codice instabile o malevolo e può cambiare in qualsiasi momento. Installa solo plugin degli autori di cui ti fidi e tieni presente gli effetti collaterali che potrebbe avere, inclusi query esterne, scan più lunghi or processi in background aggiuntivi.", "MessageReenableUser": "Guarda in basso per ri-abilitare", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "I seguenti percorsi ai file multimediali saranno rimossi dalla tua libreria", "MessageUnableToConnectToServer": "Non siamo in grado di connettersi al server selezionato al momento. Per favore assicurati che sia in esecuzione e riprova.", @@ -1731,5 +1731,28 @@ "Studio": "Studio", "GetThePlugin": "Ottieni il Plugin", "Notifications": "Notifiche", - "AllowCollectionManagement": "Permetti a questo utente di gestire le collezioni" + "AllowCollectionManagement": "Permetti a questo utente di gestire le collezioni", + "PasswordRequiredForAdmin": "La password è obbligatoria per gli account amministratori.", + "LabelSyncPlayNoGroups": "Nessun gruppo disponibile", + "NotificationsMovedMessage": "Le notifiche sono state spostate nel plugin Webhook.", + "EnableAudioNormalizationHelp": "La normalizzazione dell'audio aggiunge un guadagno per mantenerlo ad un livello desiderato (-18dB).", + "EnableAudioNormalization": "Normalizzazione Audio", + "PleaseConfirmRepositoryInstallation": "Clicca OK per confermare di aver letto sopra e desideri procedere con l'installazione del repository del plugin.", + "HeaderConfirmRepositoryInstallation": "Conferma dell'installazione del repository dei plugin", + "LabelDeveloper": "Sviluppatore", + "LabelEnableLUFSScan": "Abilita LUFS scan", + "LabelEnableLUFSScanHelp": "Abilita LUFS scan per la musica (Impiegherà più tempo e più risorse).", + "MessageRepositoryInstallDisclaimer": "ATTENZIONE: L'installazione di repository di plugin di terze parti può portare dei rischi. Può contenere codice instabile o malevolo e può cambiare in qualsiasi momento. Installa solo plugin degli autori di cui ti fidi.", + "Unknown": "Sconosciuto", + "LabelDate": "Data", + "LabelLevel": "Livello", + "LabelMediaDetails": "Dettagli Media", + "LabelSystem": "Sistema", + "LogLevel.Debug": "Debug", + "LogLevel.Trace": "Trace", + "LogLevel.Information": "Information", + "LogLevel.Warning": "Warning", + "LogLevel.Error": "Error", + "LogLevel.Critical": "Critical", + "LogLevel.None": "None" } diff --git a/src/strings/ja.json b/src/strings/ja.json index 2f69db4268..c7e4f90eec 100644 --- a/src/strings/ja.json +++ b/src/strings/ja.json @@ -30,7 +30,7 @@ "AlwaysPlaySubtitlesHelp": "言語に合った字幕が音声言語に関係なく読み込まれます。", "AnyLanguage": "任意の言語", "Anytime": "いつでも", - "AroundTime": "だいたい", + "AroundTime": "だいたい {0}", "Art": "アート", "Artists": "アーティスト", "AsManyAsPossible": "できるだけ多く", diff --git a/src/strings/kk.json b/src/strings/kk.json index d29bf080a8..76ec1b60b3 100644 --- a/src/strings/kk.json +++ b/src/strings/kk.json @@ -30,7 +30,7 @@ "AlwaysPlaySubtitlesHelp": "Tıl teñşelımıne säikes kelgen subtitrler dybys tılıne qatyssyz jükteledı.", "AnyLanguage": "Kez kelgen tıl", "Anytime": "Ärkezde", - "AroundTime": "Şamamen", + "AroundTime": "Şamamen {0}", "Art": "Qiyndy", "Artists": "Oryndauşylar", "AsManyAsPossible": "Mümkındıgınşe köp", diff --git a/src/strings/ko.json b/src/strings/ko.json index f72ea61c91..9a0c21eb24 100644 --- a/src/strings/ko.json +++ b/src/strings/ko.json @@ -652,7 +652,7 @@ "AlwaysPlaySubtitles": "항상 표시", "AlwaysPlaySubtitlesHelp": "오디오 언어를 불문하고 언어 설정에 적합한 자막을 불러옵니다.", "AnyLanguage": "모든 언어", - "AroundTime": "대략", + "AroundTime": "대략 {0}", "Art": "클리어아트", "AsManyAsPossible": "최대한 많이", "Ascending": "오름차순", diff --git a/src/strings/mg.json b/src/strings/mg.json index 9cb6c466f8..e4df7760c4 100644 --- a/src/strings/mg.json +++ b/src/strings/mg.json @@ -13,7 +13,7 @@ "Absolute": "Feno", "AccessRestrictedTryAgainLater": "Voafetra ny fidirana amin'izao fotoana izao. Andramo indray afaka fotoana fohy.", "Actor": "Mpilalao", - "AddedOnValue": "Nampiana", + "AddedOnValue": "Nampiana {0}", "AdditionalNotificationServices": "Tsidiho ny katalaogin'ny plugin raha hanampy serivisy fampahafantarana fanampiny.", "AddToCollection": "Ampiana ao amin'ny fitahirizana", "AddToFavorites": "Ampiana ao amin'ireo ankafihizina", diff --git a/src/strings/ml.json b/src/strings/ml.json index 651c0222c9..ada97d9bcf 100644 --- a/src/strings/ml.json +++ b/src/strings/ml.json @@ -71,7 +71,7 @@ "Artists": "കലാകാരന്മാർ", "Artist": "ആർട്ടിസ്റ്റ്", "Art": "വ്യക്തമായ കല", - "AroundTime": "ചുറ്റും", + "AroundTime": "ചുറ്റും {0}", "ApiKeysCaption": "നിലവിൽ പ്രവർത്തനക്ഷമമാക്കിയ API കീകളുടെ പട്ടിക", "Anytime": "ഏതുസമയത്തും", "AnyLanguage": "ഏതെങ്കിലും ഭാഷ", diff --git a/src/strings/ms.json b/src/strings/ms.json index 377349babd..cd1d725c91 100644 --- a/src/strings/ms.json +++ b/src/strings/ms.json @@ -102,7 +102,7 @@ "AskAdminToCreateLibrary": "Minta pentadbir untuk membuat perpustakaan.", "Artist": "Artis", "ApiKeysCaption": "Senarai kunci API yang diaktifkan sekarang", - "AllowTonemappingHelp": "Pemetaan nada dapat mengubah rentang dinamik video dari HDR ke SDR sambil mengekalkan perincian dan warna gambar, yang merupakan maklumat yang sangat penting untuk mewakili pemandangan asal. Pada masa ini ia hanya berfungsi dengan video HDR10 atau HLG. Ini memerlukan pemadanan dengan OpenCL atau CUDA semasa.", + "AllowTonemappingHelp": "Pemetaan-tone dapat mengubah dinamik video daripada HDR ke SDR sambil mengekalkan perincian dan warna gambar, yang merupakan maklumat yang sangat penting untuk mewakili pemandangan asal. Pada masa ini ia hanya berfungsi dengan video 10bit HDR10, HLG dan Dolby Visoion. Ini memerlukan pemadanan bersesuian dengan OpenCL atau CUDA runtime.", "Songs": "Lagu-lagu", "Playlists": "Senarai ulangmain", "Photos": "Gambar-gambar", @@ -240,5 +240,11 @@ "ButtonExitApp": "Tamatkan aplikasi", "ButtonClose": "Tutup", "AgeValue": "({0} tahun)", - "AddToFavorites": "Tambah ke kegemaran" + "AddToFavorites": "Tambah ke kegemaran", + "Arranger": "Penyusunan", + "DefaultMetadataLangaugeDescription": "Ini adalah tetapan umum dan boleh diubahsuai mengikut koleksi.", + "EnableAudioNormalizationHelp": "Normilasi audio akan meletakkan gain yang konstant agar purata di tahap yang dikehendaki (-18dB).", + "Console": "konsol", + "DeathDateValue": "Died: {0} , program error", + "AllowCollectionManagement": "Benarkan pengguna ini meguruskan koleksi" } diff --git a/src/strings/mt.json b/src/strings/mt.json new file mode 100644 index 0000000000..d00dc1217b --- /dev/null +++ b/src/strings/mt.json @@ -0,0 +1,22 @@ +{ + "Absolute": "Assolut", + "AddToPlaylist": "Żid fil-playlist", + "AddToPlayQueue": "Żid fil-play queue", + "Album": "Album", + "AllEpisodes": "L-episodji kollha", + "AllLanguages": "Il-lingwi kollha", + "AllLibraries": "Il-libreriji kollha", + "AccessRestrictedTryAgainLater": "Bħalissa l-aċċess huwa ristrett. Jekk jogħgbok erġa' prova iktar tard.", + "Actor": "Attur", + "Add": "Żid", + "AddedOnValue": "{0} Miżjuda", + "AddToCollection": "Żid fil-kollezzjoni", + "AddToFavorites": "Żid fil-lista tal-favoriti", + "AgeValue": "({0} snin)", + "AirDate": "Data tax-xandir", + "Aired": "Imxandar", + "Albums": "Albums", + "All": "Kollox", + "AllChannels": "L-istazzjonijiet kollha", + "AllComplexFormats": "Il-formats ikkumplikati kollha (ASS, SSA, VobSub, PGS, SUB, IDX, …)" +} diff --git a/src/strings/nb.json b/src/strings/nb.json index 5e62ebfff4..8df422c37d 100644 --- a/src/strings/nb.json +++ b/src/strings/nb.json @@ -1418,7 +1418,7 @@ "LabelTonemappingRange": "Tonekartlegging-område", "TonemappingAlgorithmHelp": "Tonekartlegging kan finjusteres. Hvis du ikke er kjent med disse alternativene er det bare å beholde standardinnstillingen. Anbefalt verdi er BT.2390.", "LabelTonemappingAlgorithm": "Velg algoritmen som skal brukes for tonekartlegging", - "AllowTonemappingHelp": "Tonekartlegging kan forvandle det dynamiske området på en video fra HDR til SDR samtidig som bildedetaljer og farger opprettholdes, noe som er veldig viktig informasjon for å representere den opprinnelige scenen. Fungerer for øyeblikket bare med HDR10 eller HLG videoer. Dette krever korresponderende OpenCL eller CUDA runtime.", + "AllowTonemappingHelp": "Tonekartlegging kan forvandle det dynamiske området på en video fra HDR til SDR samtidig som bildedetaljer og farger opprettholdes, noe som er veldig viktig informasjon for å representere den opprinnelige scenen. Fungerer for øyeblikket bare med HDR10-, HLG- og DoVi-videoer. Dette krever korresponderende OpenCL eller CUDA runtime.", "OptionMaxActiveSessionsHelp": "En verdi på 0 skrur av denne funksjonen.", "OptionMaxActiveSessions": "Sett maksimalt antall tilgjengelige brukerøkter.", "LabelUserMaxActiveSessions": "Maksimalt antall samtidige brukerøkter", @@ -1663,7 +1663,7 @@ "IgnoreDtsHelp": "Deaktiviering av dette valget kan løse noen problemer, f.eks. manglende lyd på kanaler med seperat lyd og videospor.", "IgnoreDts": "Ignorer DTS (dekoding tidsmerke)", "MessageNoFavoritesAvailable": "Ingen favoritter er tilgjengelige for øyeblikket.", - "LabelVppTonemappingBrightnessHelp": "Bruk lysstyrkeforsterkning i VPP-tonemapping. Både anbefalte og standardverdier er 0.", + "LabelVppTonemappingBrightnessHelp": "Bruk lysstyrkeforsterkning i VPP-tonemapping. Både anbefalte og standardverdier er 16 og 0.", "MediaInfoDoViTitle": "DV tittel", "MediaInfoElPresentFlag": "DV el forhåndsinnstilt flagg", "Unreleased": "Ennå ikke utgitt", @@ -1672,7 +1672,7 @@ "MediaInfoDvLevel": "DV nivå", "MediaInfoRpuPresentFlag": "DV rpu forhåndsinnstilt flagg", "LabelVppTonemappingContrast": "VPP-tonemapping kontrastforsterkning", - "LabelVppTonemappingContrastHelp": "Bruk kontrastforsterkning i VPP-tonemapping. De anbefalte og standardverdiene er 1.2 og 1.", + "LabelVppTonemappingContrastHelp": "Bruk kontrastforsterkning i VPP-tonemapping. Den anbefalte standardverdien er 1.", "VideoRangeTypeNotSupported": "Videoens områdetype støttes ikke", "LabelVideoRangeType": "Video områdetype", "MediaInfoVideoRangeType": "Video områdetype", @@ -1694,7 +1694,7 @@ "LabelDummyChapterDuration": "Intervall", "HeaderRecordingMetadataSaving": "Opptak metadata", "HeaderPerformance": "Ytelse", - "LabelDummyChapterDurationHelp": "Tid mellom innsamling av kapittelbilder i sekunder.", + "LabelDummyChapterDurationHelp": "Tid mellom dummykapitler. Sett til 0 for å slå av dummykapittelgenerering. Endring av dette vil ha ingen effekt på eksiterende dummykapitler.", "LabelEnableAudioVbrHelp": "Variabel bithastighet tilbyr bedre forhold mellom kvalitet og gjennomsnittlig bithastighet, men kan i visse tilfeller forårsake buffering og problemer med kompatibilitet.", "LabelEnableAudioVbr": "Aktiver VBR lydkoding", "SubtitleBlack": "Svart", @@ -1710,5 +1710,32 @@ "SubtitleRed": "Rød", "SubtitleYellow": "Gul", "SubtitleWhite": "Hvit", - "Select": "Velg" + "Select": "Velg", + "SaveRecordingImages": "Lagre EPG-opptaksbilder", + "PasswordRequiredForAdmin": "Et passord et påkrevd for administratorkontoer.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Foretrekk innbakte titler foran filnavn for ekstramateriell", + "SaveRecordingNFO": "Lagre EPG-opptaksdata i NFO", + "SaveRecordingImagesHelp": "Lagre bilder fra EPG-listekilde sammen med media.", + "StereoDownmixAlgorithmHelp": "Algoritme benyttet til å mikse ned multikanals lyd til stereo.", + "Studio": "Studio", + "SubtitleCyan": "Turkis", + "UserMenu": "Brukermenyen", + "Featurette": "Featurette", + "LabelTonemappingMode": "Tonemappingsmodus", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Ekstramateriale har ofte det samme innebygde navnet som det opprinnelige materialet. Kryss av for denne for å bruke den innebygde tittelen likevel.", + "LabelSyncPlayNoGroups": "Ingen grupper tilgjengelig", + "NotificationsMovedMessage": "Varslingsfunksjonaliteten er blitt flyttet til Webhook-programtillegget.", + "EnableAudioNormalization": "Lynormalisering", + "GetThePlugin": "Skaff deg programtillegget", + "Notifications": "Varsler", + "TonemappingModeHelp": "Velg tonemappingsmodus. Hvis du merker utvaskede høylysområder, prøv å bytte til RGB-modus.", + "EnableAudioNormalizationHelp": "Lydnormalisering vil legge på en konstant lydforsterkning for å holde gjennomsnittet på ønsket nivå (-18db).", + "LabelEnableLUFSScan": "Slå på LUFS-skanning", + "LabelParallelImageEncodingLimitHelp": "Høyeste antall bildeenkodinger som tillates å kjøre parallelt. Å sette denne til 0 vil velge en grensse basert på systemspesifikasjonene dine.", + "LabelChapterImageResolutionHelp": "Oppløsningen til kapittelbildene. Enring av dette vil ikke ha noen effekt på eksisterende kapittelbilder.", + "LabelParallelImageEncodingLimit": "Parallell bildeenkodingsgrense", + "LabelEnableLUFSScanHelp": "Slå på LUFS-skanning for musikk (dette vil ta lengre tid og mere ressurser).", + "ResolutionMatchSource": "Bruk kildetreff", + "SaveRecordingNFOHelp": "Lagre metadata fra EPG-listekilde sammen med media.", + "AllowCollectionManagement": "La denne brukeren organisere samlinger" } diff --git a/src/strings/nl.json b/src/strings/nl.json index 3b4b777ffb..ad97f07b58 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -754,12 +754,12 @@ "MessageNoPluginsInstalled": "U heeft geen plug-ins geïnstalleerd.", "MessageNoTrailersFound": "Installeer het trailerkanaal om een bibliotheek met internettrailers toe te voegen en zo je filmervaring te verbeteren.", "MessageNothingHere": "Lijst is leeg.", - "MessagePasswordResetForUsers": "De wachtwoorden van de volgende gebruikers zijn hersteld. Zij kunnen nu inloggen met de Easy-pincodes die voor het herstellen zijn gebruikt.", + "MessagePasswordResetForUsers": "De wachtwoorden van de volgende gebruikers zijn hersteld. Zij kunnen zich nu aanmelden met de pincodes die voor het herstellen zijn gebruikt.", "MessagePlayAccessRestricted": "Het afspelen van deze inhoud wordt momenteel beperkt. Neem contact op met je serverbeheerder voor meer informatie.", "MessagePleaseEnsureInternetMetadata": "Zorg ervoor dat het downloaden van internet-metadata is ingeschakeld.", "MessagePleaseWait": "Even geduld. Dit kan even duren.", "MessagePluginConfigurationRequiresLocalAccess": "Om deze plug-in te configuren moet je je direct op de lokale server aanmelden.", - "MessagePluginInstallDisclaimer": "Plug-ins ontwikkeld door leden van de gemeenschap zijn een geweldige manier om uw ervaring met extra functies en voordelen te verbeteren. Wees voor het installeren bewust van de effecten die zij op uw server kunnen hebben, zoals langere bibliotheekscans, meer achtergrondverwerking en een verminderde stabiliteit van het systeem.", + "MessagePluginInstallDisclaimer": "WAARSCHUWING: het installeren van een plug-in van derden brengt risico's met zich mee. De plug-in kan ieder moment veranderen en instabiele of kwaadaardige code bevatten. Installeer alleen plug-ins van auteurs die je vertrouwt en wees je bewust van de mogelijke gevolgen, zoals verbindingen met externe diensten, langer durende bibliotheekscans of meer achtergrondverwerking.", "MessageReenableUser": "Zie hieronder hoe opnieuw in te schakelen", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "De volgende medialocaties worden verwijderd uit je bibliotheek", "MessageUnableToConnectToServer": "Het is momenteel niet mogelijk met de geselecteerde server te verbinden. Controleer of deze draait en probeer het opnieuw.", @@ -1736,5 +1736,29 @@ "EnableAudioNormalizationHelp": "Geluidsnormalisatie past een constante versterking toe om het gemiddelde op een gewenst niveau (-18dB) te houden.", "LabelEnableLUFSScan": "LUFS-scan inschakelen", "LabelEnableLUFSScanHelp": "LUFS-scan voor muziek inschakelen. Dit duurt langer en is systeemintensief.", - "PasswordRequiredForAdmin": "Voor beheerdersaccounts is een wachtwoord vereist." + "PasswordRequiredForAdmin": "Voor beheerdersaccounts is een wachtwoord vereist.", + "LabelSyncPlayNoGroups": "Geen groepen beschikbaar", + "HeaderConfirmRepositoryInstallation": "Installatie plug-in-repository bevestigen", + "LabelDeveloper": "Ontwikkelaar", + "MessageRepositoryInstallDisclaimer": "WAARSCHUWING: het installeren van een plug-in-repository van derden brengt risico's met zich mee. De repository kan ieder moment veranderen en instabiele of kwaadaardige code bevatten. Installeer alleen repository's van auteurs die je vertrouwt.", + "PleaseConfirmRepositoryInstallation": "Druk op OK als je bovenstaande gelezen hebt en wenst door te gaan met de installatie van de plug-in-repository.", + "Unknown": "Onbekend", + "LabelDate": "Datum", + "LabelLevel": "Niveau", + "LabelMediaDetails": "Mediadetails", + "LabelSystem": "Systeem", + "LogLevel.Debug": "Foutopsporing", + "LogLevel.Trace": "Spoor", + "LogLevel.Information": "Informatie", + "LogLevel.Warning": "Waarschuwing", + "LogLevel.Error": "Fout", + "LogLevel.Critical": "Kritiek", + "LogLevel.None": "Geen", + "AllowSegmentDeletionHelp": "Oude segmenten verwijderen nadat deze naar de cliënt zijn verzonden. Zo hoeft niet het gehele getranscodeerde bestand op de schijf te worden opgeslagen. Werkt alleen als afknijpen is ingeschakeld. Schakel dit uit als je afspeelproblemen ondervindt.", + "LabelSegmentKeepSecondsHelp": "Tijd in seconden die segmenten moeten worden bewaard voordat ze worden overschreven. Moet langer zijn dan \"Afknijpen na\". Werkt alleen als segmenten verwijderen is ingeschakeld.", + "HeaderEpisodesStatus": "Status afleveringen", + "AllowSegmentDeletion": "Segmenten verwijderen", + "LabelThrottleDelaySeconds": "Afknijpen na", + "LabelThrottleDelaySecondsHelp": "Tijd in seconden waarna de transcoder wordt afgeknepen. Deze tijd moet voldoende lang zijn zodat de cliënt een gezonde buffer in stand kan houden. Werkt alleen als afknijpen is ingeschakeld.", + "LabelSegmentKeepSeconds": "Bewaartijd segmenten" } diff --git a/src/strings/nn.json b/src/strings/nn.json index 940bf317ee..1987e01560 100644 --- a/src/strings/nn.json +++ b/src/strings/nn.json @@ -596,7 +596,7 @@ "Artists": "Artistar", "Artist": "Artist", "Art": "Omslagbilete", - "AroundTime": "Rundt", + "AroundTime": "Rundt {0}", "ApiKeysCaption": "Liste over aktive API-nøklar", "Anytime": "Når som helst", "AnyLanguage": "Kva som helst språk", diff --git a/src/strings/pl.json b/src/strings/pl.json index e2fb9e1ecf..299c4c7670 100644 --- a/src/strings/pl.json +++ b/src/strings/pl.json @@ -1693,10 +1693,10 @@ "SaveRecordingNFOHelp": "Zapisz metadane od dostawcy list EPG wraz z mediami.", "HeaderDummyChapter": "Obrazy rozdziału", "LabelDummyChapterDuration": "Interwał", - "LabelDummyChapterDurationHelp": "Interwał ekstrakcji obrazu rozdziału w sekundach.", + "LabelDummyChapterDurationHelp": "Odstęp między fikcyjnymi rozdziałami. Ustaw na 0, aby wyłączyć generowanie fikcyjnych rozdziałów. Zmiana tego nie będzie miała wpływu na istniejące fikcyjne rozdziały.", "LabelDummyChapterCount": "Ograniczenie", "LabelChapterImageResolution": "Rozdzielczość", - "LabelChapterImageResolutionHelp": "Rozdzielczość wyodrębnionych obrazów rozdziałów.", + "LabelChapterImageResolutionHelp": "Rozdzielczość wyodrębnionych obrazów rozdziałów. Zmiana tego ustawienia nie ma wpływu na istniejące rozdziały.", "ResolutionMatchSource": "Źródło dopasowania", "SaveRecordingNFO": "Zapisz nagrane metadane EPG w NFO", "SaveRecordingImages": "Zapisywanie obrazów EPG", @@ -1734,5 +1734,14 @@ "EnableAudioNormalization": "Normalizacja dźwięku", "LabelEnableLUFSScan": "Włącz skanowanie głośności dźwięku", "LabelEnableLUFSScanHelp": "Włącz skanowanie głośności dźwięku dla muzyki (To zajmie więcej czasu i więcej zasobów).", - "Notifications": "Powiadomienia" + "Notifications": "Powiadomienia", + "PasswordRequiredForAdmin": "Hasło jest wymagane do kont administratorów.", + "LabelSyncPlayNoGroups": "Brak dostępnych grup", + "GetThePlugin": "Pobierz wtyczkę", + "NotificationsMovedMessage": "Funkcjonalność powiadomień została przeniesiona do wtyczki Webhook.", + "HeaderConfirmRepositoryInstallation": "Potwierdź instalację repozytorium wtyczek", + "LabelDeveloper": "Programista", + "MessageRepositoryInstallDisclaimer": "OSTRZEŻENIE: Instalacja repozytorium wtyczek stron trzecich wiąże się z ryzykiem. Może ono zawierać niestabilny lub złośliwy kod i może ulec zmianie w dowolnym momencie. Instaluj tylko repozytoria od zaufanych autorów.", + "PleaseConfirmRepositoryInstallation": "Kliknij OK, aby potwierdzić, że przeczytałeś powyższe informacje i chcesz kontynuować instalację repozytorium wtyczek.", + "Unknown": "Nieznane" } diff --git a/src/strings/pt-br.json b/src/strings/pt-br.json index d8bceeefd2..5239d9d3c1 100644 --- a/src/strings/pt-br.json +++ b/src/strings/pt-br.json @@ -786,7 +786,7 @@ "MessagePleaseEnsureInternetMetadata": "Por favor, verifique se o download de metadados da internet está ativado.", "MessagePleaseWait": "Por favor, aguarde. Isto pode demorar um pouco.", "MessagePluginConfigurationRequiresLocalAccess": "Para configurar este plugin, entre diretamente em seu servidor local.", - "MessagePluginInstallDisclaimer": "Plugins feitos por membros da comunidade são uma grande forma de melhorar sua experiência com funcionalidades e benefícios adicionais. Antes de instalar, por favor certifique-se de conhecer os efeitos que podem causar no seu servidor, tais como rastreamentos de biblioteca mais demorados, processamento adicional e diminuição na estabilidade do sistema.", + "MessagePluginInstallDisclaimer": "AVISO: A instalação de um plug-in de terceiros acarreta riscos. Ele pode conter código instável ou malicioso e pode mudar a qualquer momento. Instale apenas plug-ins de autores em quem você confia e esteja ciente dos possíveis efeitos que eles podem ter, incluindo consultas de serviços externos, verificações de biblioteca mais longas ou processamento adicional em segundo plano.", "MessageReenableUser": "Veja abaixo para reativar", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "As seguintes localizações de mídia serão excluídas de sua biblioteca", "MessageUnableToConnectToServer": "Não foi possível conectar ao servidor selecionado. Por favor, verifique se está sendo executado e tente novamente.", @@ -1182,7 +1182,7 @@ "OptionLoginAttemptsBeforeLockoutHelp": "Um valor zero significa herdar o padrão de três tentativas para usuários normais e cinco para administradores. Definir como -1 desativará o recurso.", "OptionProtocolHls": "Transmissão ao Vivo por HTTP (HLS)", "OptionProtocolHttp": "HTTP", - "OptionRegex": "Regex", + "OptionRegex": "Expressão Regular", "OptionSubstring": "Substring", "PasswordResetProviderHelp": "Escolha um provedor de reset de senha a ser usado quando este usuário solicitar uma redefinição de senha.", "PictureInPicture": "vídeo destacado", @@ -1564,7 +1564,7 @@ "Remixer": "Remixar", "ReleaseGroup": "Grupo de Realease", "PlaybackErrorPlaceHolder": "Este é um espaço reservado para mídia física que o Jellyfin não pode reproduzir. Insira o disco a ser reproduzido.", - "Mixer": "Mixer", + "Mixer": "Mixador", "LabelSyncPlaySettingsSkipToSyncHelp": "Método de correção de sincronismo que consiste em buscar a posição estimada. A correção de sincronização deve estar habilitada.", "LabelSyncPlaySettingsSkipToSync": "Habilitar SkipToSync", "LabelSyncPlaySettingsSpeedToSyncHelp": "Método de correção de sincronização que consiste em acelerar a reprodução. A correção de sincronização deve estar habilitada.", @@ -1600,7 +1600,7 @@ "Cursive": "Cursiva", "LabelAutomaticallyAddToCollection": "Adicionar automaticamente à coleção", "Console": "Console", - "Casual": "Casual", + "Casual": "Ocasional", "SelectAll": "Selecionar Tudo", "DirectPlayError": "Ocorreu um erro ao iniciar a reprodução direta", "UnknownAudioStreamInfo": "As informações do stream de audio são desconhecidas", @@ -1734,5 +1734,22 @@ "TonemappingModeHelp": "Selecione o modo de mapeamento de tom. Se você experimentar realces estourados, tente mudar para o modo RGB.", "HeaderRecordingMetadataSaving": "Salvando os metadados", "AllowCollectionManagement": "Permitir que este usuário administre as coleções", - "PasswordRequiredForAdmin": "Uma senha é necessária para contas de administrador." + "PasswordRequiredForAdmin": "Uma senha é necessária para contas de administrador.", + "LabelSyncPlayNoGroups": "Não há grupos disponíveis", + "LabelDate": "Data", + "LabelLevel": "Nível", + "LabelMediaDetails": "Detalhes da Mídia", + "LabelSystem": "Sistema", + "LogLevel.Trace": "Rastrear", + "LogLevel.Debug": "Depurar", + "LogLevel.Information": "Informar", + "LogLevel.Warning": "Avisar", + "LogLevel.Error": "Erros", + "LogLevel.Critical": "Críticos", + "LogLevel.None": "Nenhum", + "MessageRepositoryInstallDisclaimer": "AVISO: A instalação de um repositório de plugins de terceiros acarreta riscos. Ele pode conter código instável ou malicioso e pode mudar a qualquer momento. Instale apenas repositórios de autores nos quais você confia.", + "Unknown": "Desconhecido", + "HeaderConfirmRepositoryInstallation": "Confirme a instalação do repositório de plug-ins", + "LabelDeveloper": "Desenvolvedor", + "PleaseConfirmRepositoryInstallation": "Por favor, clique em OK para confirmar que você leu o acima e deseja prosseguir com a instalação do repositório de plug-ins." } diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index dccfd8975e..8e65c2eb1d 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -607,7 +607,7 @@ "XmlDocumentAttributeListHelp": "Estes atributos são aplicados ao elemento principal de cada resposta XML.", "AccessRestrictedTryAgainLater": "O acesso está atualmente restrito. Por favor, tente mais tarde.", "AddToCollection": "Adicionar à coleção", - "AddToPlayQueue": "Adicionar à fila de reprodução", + "AddToPlayQueue": "Adicionar à lista de reprodução", "AddedOnValue": "Adicionado {0}", "AirDate": "Data de estreia", "Albums": "Álbuns", @@ -1525,7 +1525,7 @@ "DirectPlayHelp": "O ficheiro de origem é totalmente compatível com este cliente e a sessão está a receber o ficheiro sem modificações.", "Conductor": "Maestro", "Arranger": "Organizador", - "AgeValue": "({0} anos de idade)", + "AgeValue": "({0} anos)", "LabelSyncPlaySettingsSpeedToSync": "Ativar SpeedToSync", "LabelAutomaticallyAddToCollection": "Adicionar automaticamente à colecção", "ItemDetails": "Detalhes", @@ -1677,5 +1677,53 @@ "BehindTheScenes": "Nos bastidores", "DownloadAll": "Transferir Todas", "MessageNoItemsAvailable": "Nenhum item disponível atualmente.", - "MessageNoFavoritesAvailable": "Nenhum favorito disponível atualmente." + "MessageNoFavoritesAvailable": "Nenhum favorito disponível atualmente.", + "SubtitleBlack": "Preto", + "Short": "Curta-metragem", + "PasswordRequiredForAdmin": "A password é obrigatória para contas de administrador.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Prefira títulos incorporados em vez de nomes de ficheiros para extras", + "SubtitleGreen": "Verde", + "SubtitleLightGray": "Cinzento Claro", + "SubtitleMagenta": "Magenta", + "SubtitleRed": "Vermelho", + "SubtitleYellow": "Amarelo", + "Featurette": "Featurette", + "Studio": "Estúdio", + "StereoDownmixAlgorithmHelp": "Algoritmo usado para fazer downmix de áudio multicanal para estéreo.", + "Unreleased": "Ainda não foi lançado", + "SubtitleBlue": "Azul", + "SubtitleCyan": "Ciano", + "LabelSyncPlayNoGroups": "Não há grupos disponíveis", + "Select": "Selecciona", + "AllowCollectionManagement": "Permitir este utilizador gerir as coleções", + "SecondarySubtitles": "Legendas secundárias", + "LabelDummyChapterDuration": "Intervalo", + "LabelDummyChapterDurationHelp": "O intervalo entre os capítulos fictícios. Defina como 0 para desativar a criação de capítulo fictício. Alterar isso não terá efeito nos capítulos fictícios existentes.", + "LabelChapterImageResolution": "Resolução", + "LabelChapterImageResolutionHelp": "A resolução das imagens do capítulo extraídas. Alterar isso não terá efeito nos capítulos fictícios existentes.", + "LabelParallelImageEncodingLimit": "Limite de codificação paralela de imagens", + "LabelParallelImageEncodingLimitHelp": "Quantidade máxima de codificações de imagem permitidas para execução em paralelo. Definir como 0 escolherá um limite com base nas especificações do sistema.", + "GetThePlugin": "Obter Plugin", + "Notifications": "Notificações", + "NotificationsMovedMessage": "A funcionalidade de notificações foi movida para o plugin Webhook.", + "LabelEnableAudioVbr": "Ativar codificação do áudio VBR", + "ResolutionMatchSource": "Correspondente ao original", + "MenuOpen": "Abrir menu", + "MenuClose": "Fechar menu", + "SubtitleGray": "Cinzento", + "SubtitleWhite": "Branco", + "UserMenu": "Menu de utilizador", + "HeaderPerformance": "Desempenho", + "EnableAudioNormalizationHelp": "A normalização do áudio adicionará um ganho constante para manter a média no nível desejado (-18dB).", + "EnableAudioNormalization": "Normalização de áudio", + "HeaderRecordingMetadataSaving": "Gravar metadados", + "OptionDateEpisodeAdded": "Data de adição do episódio", + "LabelEnableLUFSScanHelp": "Ative a verificação LUFS para música (isso levará mais tempo e mais recursos).", + "LabelEnableLUFSScan": "Ative a verificação LUFS", + "HeaderDummyChapter": "Imagens dos Capítulos", + "LabelEnableAudioVbrHelp": "A taxa de bits variável oferece melhor qualidade relativamente à taxa de bits média, mas, em alguns casos raros, pode causar problemas de ‘buffer’ e compatibilidade.", + "Experimental": "Experimental", + "MessageRenameMediaFolder": "Renomear uma biblioteca de multimédia fará com que todos os metadados sejam perdidos, proceda com cautela.", + "EnableCardLayout": "Mostrar em formato mosaico", + "OptionDateShowAdded": "Data de adição da Série" } diff --git a/src/strings/pt.json b/src/strings/pt.json index cde1cea2d9..93fe7dcd17 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -420,7 +420,7 @@ "Items": "Itens", "ItemCount": "{0} itens", "InstantMix": "Mistura instântanea", - "InstallingPackage": "A instalar {0}", + "InstallingPackage": "A instalar {0} (version {1})", "ImportFavoriteChannelsHelp": "Apenas os canais marcados como favoritos no dispositivo sintonizador serão importados.", "Images": "Imagens", "Identify": "Identificar", diff --git a/src/strings/ro.json b/src/strings/ro.json index f8e70f175a..ecd87b94b6 100644 --- a/src/strings/ro.json +++ b/src/strings/ro.json @@ -176,7 +176,7 @@ "Anytime": "Oricând", "Art": "Artă", "AlwaysPlaySubtitlesHelp": "Subtitrările care se potrivesc cu preferințele limbii utilizate vor fi încărcate indiferent de limba audio.", - "AroundTime": "Împrejur", + "AroundTime": "Împrejur {0}", "AsManyAsPossible": "Cât mai mulți cu putință", "Ascending": "Ascendent", "AspectRatio": "Raportul aspectului", diff --git a/src/strings/ru.json b/src/strings/ru.json index 8e1e7efcea..2922123c52 100644 --- a/src/strings/ru.json +++ b/src/strings/ru.json @@ -815,12 +815,12 @@ "MessageNoPluginsInstalled": "Нет установленных плагинов.", "MessageNoTrailersFound": "Установите канал трейлеров, чтобы повысить своё впечатление от фильма путём добавления собрания интернет-трейлеров.", "MessageNothingHere": "Здесь ничего нет.", - "MessagePasswordResetForUsers": "Следующие пользователи сбросили свои пароли. Теперь они могут войти с помощью простых PIN-кодов, которые использовались для сброса.", + "MessagePasswordResetForUsers": "Следующие пользователи сбросили свои пароли. Теперь они могут войти с помощью PIN-кодов, которые использовались для сброса.", "MessagePlayAccessRestricted": "Воспроизведение данного содержания в настоящее время ограничено. За дополнительными сведениями обратитесь к администратору сервера.", "MessagePleaseEnsureInternetMetadata": "Убедитесь, что включена загрузка метаданных из Интернета.", "MessagePleaseWait": "Подождите. Это может занять минуту.", "MessagePluginConfigurationRequiresLocalAccess": "Чтобы настроить данный плагин, войдите непосредственно в свой локальный сервер.", - "MessagePluginInstallDisclaimer": "Плагины, созданные членами сообщества являются отличным способом повышения эффективности с помощью дополнительных функций и компонентов. Перед установкой примите во внимание влияние, которое они могут оказать на сервер, например, длительные сканирования медиатеки, дополнительную фоновую обработку и снижение системной стабильности.", + "MessagePluginInstallDisclaimer": "ПРЕДУПРЕЖДЕНИЕ: Установка сторонних плагинов несет определенные риски. Они могут содержать нестабильный или вредоносный код и могут изменяться в любое время. Устанавливайте плагины только от авторов, которым вы доверяете, и, пожалуйста, помните о потенциальных последствиях, которые они могут иметь, в том числе внешние запросы к службам, более долгое сканирование библиотек или дополнительную фоновую обработку.", "MessageReenableUser": "См. ниже для разблокировки", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Из вашей медиатеки будут изъяты следующие расположения медиаданных", "MessageUnableToConnectToServer": "Мы не можем подсоединиться к выбранному серверу в данный момент. Убедитесь, что он запущен и повторите попытку.", @@ -1635,7 +1635,7 @@ "ShowParentImages": "Показывать рисунки сериала", "NextUpRewatching": "Пересматривание", "MixedMoviesShows": "Смешанные фильмы и передачи", - "AddToFavorites": "Добавить в Избранное", + "AddToFavorites": "Добавить в избранное", "GoogleCastUnsupported": "Google Cast не поддерживается", "ButtonBackspace": "Откатить", "ButtonSpace": "Пробел", @@ -1667,13 +1667,13 @@ "RememberSubtitleSelections": "Задать дорожку субтитров на основе предыдущего элемента", "RememberAudioSelections": "Задать аудиодорожку на основе предыдущего элемента", "LabelMaxVideoResolution": "Максимально допустимое разрешение перекодирующегося видео", - "LabelVppTonemappingContrastHelp": "Применяется усиление контрастности при VPP-тонмаппинге. Значения рекомендованные и по умолчанию равны 1.2 и 1.", + "LabelVppTonemappingContrastHelp": "Применяется усиление контрастности при VPP-тонмаппинге. Рекомендованное значение равно 1 (по умолчанию).", "MediaInfoDvBlSignalCompatibilityId": "ID совместимости сигнала DV bl", "MediaInfoBlPresentFlag": "Флаг предустановки DV bl", "MediaInfoElPresentFlag": "Флаг предустановки DV el", "MediaInfoRpuPresentFlag": "Флаг предустановки DV rpu", "VideoRangeTypeNotSupported": "Тип диапазона видео не поддерживается", - "LabelVppTonemappingBrightnessHelp": "Применяется усиление яркости при VPP-тонмаппинге. Значения рекомендованные и по умолчанию равны 0.", + "LabelVppTonemappingBrightnessHelp": "Применяется усиление яркости при VPP-тонмаппинге. Рекомендуемое значение 16, значение по умолчанию 0.", "LabelVppTonemappingContrast": "Усиление контрастности VPP-тонмаппинга", "LabelVppTonemappingBrightness": "Усиление яркости VPP-тонмаппинга", "IgnoreDtsHelp": "Отключение этой опции может исправить некоторые проблемы, например, отсутствие звука на каналах с раздельными потоками аудио и видео.", @@ -1701,15 +1701,15 @@ "LabelParallelImageEncodingLimit": "Ограничение на параллельное кодирование изображений", "LabelParallelImageEncodingLimitHelp": "Максимальное количество кодировок изображений, которые разрешено выполнять параллельно. Установив это значение равным 0, вы выберете ограничение, основанное на характеристиках вашей системы.", "HeaderPerformance": "Производительность", - "LabelDummyChapterDurationHelp": "Интервал извлечения изображения главы в секундах.", - "LabelChapterImageResolutionHelp": "Разрешение извлеченных изображений глав.", + "LabelDummyChapterDurationHelp": "Интервал между изображениями разделов. Установите значение 0, чтобы отключить генерацию изображений разделов. Изменение этого параметра не повлияет на существующие изображения разделов.", + "LabelChapterImageResolutionHelp": "Разрешение созданных изображений разделов. Изменение этого параметра не повлияет на существующие изображения разделов.", "ResolutionMatchSource": "Соответствовать источнику", "MenuOpen": "Открыть Меню", "MenuClose": "Закрыть Меню", "AllowCollectionManagement": "Разрешить этому пользователю управлять настройками коллекций", "EnableAudioNormalization": "Нормализация звука", "SubtitleGreen": "Зелёный", - "Featurette": "Короткометражка", + "Featurette": "Среднеметражка", "Short": "Короткометражка", "Studio": "Студия", "SubtitleGray": "Серый", @@ -1723,5 +1723,43 @@ "SecondarySubtitles": "Дополнительные субтитры", "SubtitleLightGray": "Светло-серый", "UserMenu": "Меню пользователя", - "SubtitleMagenta": "Пурпурный" + "SubtitleMagenta": "Пурпурный", + "LabelSegmentKeepSecondsHelp": "Время в секундах, в течение которого сегменты должны храниться перед перезаписью. Должно быть больше \"Ограничить после\". Работает только если включено удаление сегментов.", + "LabelTonemappingMode": "Режим тонмаппинга", + "PasswordRequiredForAdmin": "Для учетных записей администратора требуется пароль.", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Допматериалы часто имеют такое же встроенное имя, что и родительское, отметьте это, чтобы все равно использовать для них встроенные заголовки.", + "TonemappingModeHelp": "Выберите режим тонмаппинга. Если вы наблюдаете выгорание ярких участков, попробуйте переключиться в режим RGB.", + "SaveRecordingNFO": "Сохранять записи метаданных телегида (EPG) в NFO", + "SaveRecordingImages": "Сохранять изображения записи телегида (EPG)", + "LabelSyncPlayNoGroups": "Нет доступных групп", + "LabelDate": "Дата", + "LabelEnableLUFSScan": "Включить сканирование LUFS", + "LabelMediaDetails": "Информация о медиа", + "LabelSystem": "Система", + "LogLevel.Trace": "Отслеживание", + "LogLevel.Information": "Информация", + "LogLevel.Warning": "Предупреждение", + "LogLevel.Critical": "Критично", + "LogLevel.None": "Ничего", + "HeaderEpisodesStatus": "Статус эпизодов", + "LabelEnableLUFSScanHelp": "Включите сканирование LUFS для музыки (это займет больше времени и ресурсов).", + "LabelLevel": "Уровень", + "LogLevel.Debug": "Отладка", + "MessageRepositoryInstallDisclaimer": "ПРЕДУПРЕЖДЕНИЕ: Установка стороннего репозитория плагинов несет определенные риски. Он может содержать нестабильный или вредоносный код и может изменяться в любое время. Устанавливайте репозитории только от авторов, которым вы доверяете.", + "PleaseConfirmRepositoryInstallation": "Пожалуйста, нажмите OK, чтобы подтвердить, что вы прочитали вышеуказанное и хотите продолжить установку репозитория плагинов.", + "SaveRecordingImagesHelp": "Сохранять изображения из списка провайдера телегида (EPG) вместе с медиафайлами.", + "GetThePlugin": "Загрузить плагин", + "Notifications": "Уведомления", + "NotificationsMovedMessage": "Функционал уведомлений перемещен в плагин Webhook.", + "EnableAudioNormalizationHelp": "Нормализация звука добавит постоянный коэффициент усиления, чтобы удержать средний уровень на нужном уровне (-18 дБ).", + "SaveRecordingNFOHelp": "Сохранять метаданные от поставщика списков телегида (EPG) вместе с медиаданными.", + "HeaderConfirmRepositoryInstallation": "Подтвердить установку репозитория плагинов", + "Unknown": "Неизвестно", + "LabelDeveloper": "Разработчик", + "AllowSegmentDeletion": "Удалить сегменты", + "AllowSegmentDeletionHelp": "Удаление старых сегментов после их отправки в клиент. Это позволяет избежать необходимости хранить весь транскодированный файл на диске. Работает только при включенном троттлинге. Отключите эту функцию, если у вас возникают проблемы с воспроизведением.", + "LabelThrottleDelaySeconds": "Ограничить после", + "LabelThrottleDelaySecondsHelp": "Время в секундах, после которого транскодер будет приторможен. Должно быть достаточно большим, чтобы клиент мог поддерживать работоспособный буфер. Работает только если ограничение включено.", + "LabelSegmentKeepSeconds": "Время сохранения сегментов", + "LogLevel.Error": "Ошибка" } diff --git a/src/strings/sq.json b/src/strings/sq.json index 0143456101..e2e5c11d39 100644 --- a/src/strings/sq.json +++ b/src/strings/sq.json @@ -154,7 +154,7 @@ "Ascending": "Duke u ngjitur", "Artist": "Artisti", "Art": "Arti", - "AroundTime": "Përreth", + "AroundTime": "Përreth {0}", "ApiKeysCaption": "Lista e çelësave API aktualisht të aktivizuar", "Anytime": "Kurdo", "AnyLanguage": "Çfarëdo Gjuhë", diff --git a/src/strings/sr.json b/src/strings/sr.json index e9bc2945c8..f2817b7f3f 100644 --- a/src/strings/sr.json +++ b/src/strings/sr.json @@ -56,7 +56,7 @@ "BirthLocation": "Место рођења", "BirthDateValue": "Рођен: {0}", "Audio": "Звук", - "AroundTime": "Око", + "AroundTime": "Око {0}", "AnyLanguage": "Било који језик", "AlwaysPlaySubtitles": "Увек покрени", "AllowRemoteAccess": "Дозволи удаљено конектовање на сервер", diff --git a/src/strings/sv.json b/src/strings/sv.json index e9bfb0187c..c2e023e4f4 100644 --- a/src/strings/sv.json +++ b/src/strings/sv.json @@ -25,7 +25,7 @@ "AlwaysPlaySubtitlesHelp": "Undertexter på det önskade språket kommer att laddas oavsett ljudspårets språk.", "AnyLanguage": "Alla språk", "Anytime": "När som helst", - "AroundTime": "Runt", + "AroundTime": "Runt {0}", "Art": "Grafik", "Artists": "Artister", "AsManyAsPossible": "Så många som möjligt", diff --git a/src/strings/ta.json b/src/strings/ta.json index 803171e6f1..97cf213cc0 100644 --- a/src/strings/ta.json +++ b/src/strings/ta.json @@ -13,7 +13,7 @@ "Artists": "கலைஞர்கள்", "Artist": "கலைஞர்", "Art": "தெளிவான கலை", - "AroundTime": "சுற்றி", + "AroundTime": "சுற்றி {0}", "Anytime": "எப்போது வேண்டுமானாலும்", "AnyLanguage": "எந்த மொழியும்", "AlwaysPlaySubtitlesHelp": "ஆடியோ மொழியைப் பொருட்படுத்தாமல் மொழி விருப்பத்துடன் பொருந்தக்கூடிய வசன வரிகள் ஏற்றப்படும்.", diff --git a/src/strings/te.json b/src/strings/te.json index 8612819f86..c413119a4a 100644 --- a/src/strings/te.json +++ b/src/strings/te.json @@ -1472,7 +1472,7 @@ "Artists": "కళాకారులు", "Artist": "ఆర్టిస్ట్", "Art": "కళ", - "AroundTime": "చుట్టూ", + "AroundTime": "చుట్టూ {0}", "ApiKeysCaption": "ప్రస్తుతం ప్రారంభించబడిన API కీల జాబితా", "Anytime": "ఎప్పుడైనా", "AnyLanguage": "ఏదైనా భాష", diff --git a/src/strings/th.json b/src/strings/th.json index aa2dfc9dd7..ac330f32b9 100644 --- a/src/strings/th.json +++ b/src/strings/th.json @@ -29,7 +29,7 @@ "AlbumArtist": "อัลบั้มศิลปิน", "Album": "อัลบั้ม", "AirDate": "วันที่ออกอากาศ", - "AddedOnValue": "เพิ่มแล้ว", + "AddedOnValue": "เพิ่มแล้ว {0}", "Framerate": "เฟรมเรต", "Folders": "โฟลเดอร์", "FileNotFound": "ไม่พบไฟล์", diff --git a/src/strings/tr.json b/src/strings/tr.json index cf91a1e594..d5d3eee054 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -1,6 +1,6 @@ { "Add": "Ekle", - "All": "Hepsi", + "All": "Tümü", "AllLibraries": "Bütün kütüphaneler", "AllowRemoteAccess": "Bu sunucuya uzaktan bağlantılara izin verin", "AllowRemoteAccessHelp": "Eğer işaretlenmemişse, bütün uzak bağlantılar engellenecek.", @@ -269,7 +269,7 @@ "AlwaysPlaySubtitlesHelp": "Dil tercihi ile eşleşen altyazılar, ses diline bakılmaksızın yüklenir.", "AnyLanguage": "Herhangi bir dil", "Anytime": "İstediğin zaman", - "AroundTime": "Civarında", + "AroundTime": "Civarında {0}", "Art": "Temiz sanat", "AsManyAsPossible": "Mümkün olduğunca çok", "Ascending": "Artan", @@ -881,7 +881,7 @@ "Menu": "Menü", "EnableBlurHashHelp": "Hala yüklenmekte olan resimler benzersiz bir yer tutucuyla görüntülenecektir.", "EnableBlurHash": "Resimler için bulanık yer tutucuları etkinleştir", - "AllowTonemappingHelp": "Ton eşleme, orijinal sahneyi temsil etmek için çok önemli bilgiler olan görüntü ayrıntılarını ve renkleri korurken bir videonun dinamik aralığını HDR'den SDR'ye dönüştürebilir. Şu anda yalnızca HDR10 veya HLG videolar ile çalışır. İlgili OpenCL veya CUDA çalışma zamanını gerektirir.", + "AllowTonemappingHelp": "Ton eşleme, orijinal sahneyi temsil etmek için çok önemli bilgiler olan görüntü ayrıntılarını ve renkleri korurken bir videonun dinamik aralığını HDR'den SDR'ye dönüştürebilir. Şu anda yalnızca 10bit HDR10,HLG ve DoVi videolarla çalışmaktadır. Bunun için ilgili OpenCL veya CUDA çalışma zamanı gerekir.", "LabelAutomaticDiscovery": "Otomatik Keşfetmeyi Etkinleştir", "LabelAutoDiscoveryTracingHelp": "Etkinleştirildiğinde, otomatik keşfetme bağlantı noktasına gelen paketler günlüğe kaydedilir.", "LabelAutoDiscoveryTracing": "Otomatik Keşfetme izlemesini etkinleştirin.", @@ -1087,7 +1087,7 @@ "ScanLibrary": "Kitaplığı tara", "ScanForNewAndUpdatedFiles": "Yeni ve güncellenmiş dosyaları tarayın", "OptionDateAddedImportTime": "Kitaplığa tarandığı tarihi kullanın", - "MessagePluginInstallDisclaimer": "Topluluk üyeleri tarafından oluşturulan eklentiler, ek özellikler ve avantajlarla deneyiminizi geliştirmenin harika bir yoludur. Yüklemeden önce, lütfen daha uzun kitaplık taramaları, ek arka plan işlemleri ve azalan sistem kararlılığı gibi sunucunuzda oluşabilecek etkilere dikkat ediniz.", + "MessagePluginInstallDisclaimer": "UYARI: Topluluk üyeleri tarafından oluşturulan eklentiler risk oluşturmaktadır. Eklentiler gizli özellikler veya zararlı kod içerebilir ve eklentilerin içerikleri habersizin değişebilir. Sadece güvendiğiniz kişiler tarafından geliştirilmiş eklendileri yükleyin, aksi taktirde oluşabilecek potansiyel zararları göz önünde bulundurun. Bunlar, uzaktan servis sorgulama, uzun süren kitaplık taramaları ve ek arka plan işlemleri içerebilir.", "MessageEnablingOptionLongerScans": "Bu seçeneğin etkinleştirilmesi, önemli ölçüde daha uzun kitaplık taramalarına neden olabilir.", "LabelRefreshMode": "Yenileme modu", "MessageImageFileTypeAllowed": "Yalnızca JPEG ve PNG dosyaları desteklenir.", @@ -1147,8 +1147,8 @@ "LabelBlastMessageIntervalHelp": "Acil tanı mesajları arasındaki saniye cinsinden süreyi belirtir.", "HeaderContinueReading": "Okumaya devam et", "DisableCustomCss": "Sunucu tarafından sağlanan özel CSS kodunu devre dışı bırak", - "DirectPlayHelp": "Kaynak dosyası bu istemci ile tamamen uyumlu ve oturum dosyayı değişiklik yapmadan alıyor.", - "OptionCaptionInfoExSamsung": "CaptionInfoEx (Samsung)", + "DirectPlayHelp": "Kaynak dosya bu iştemci ile tamamen uyumlu ve oturum dosyayı değişiklik yapmadan alıyor.", + "OptionCaptionInfoExSamsung": "Altyazı Bilgisi Ex (Samsung)", "OptionBluray": "BD", "LabelAutomaticallyAddToCollection": "Otomatik olarak koleksiyona ekle", "HeaderSyncPlayPlaybackSettings": "Oynatma", @@ -1562,7 +1562,7 @@ "MessageSyncPlayErrorMedia": "SyncPlay etkinleştirilemedi! Medya hatası.", "MessageSyncPlayErrorAccessingGroups": "Gruplar listesine erişim sağlarken bir hata oluştu.", "MessagePlayAccessRestricted": "Bu içeriğin oynatılması şu anda kısıtlıdır. Daha fazla bilgi için lütfen sunucu yöneticinizle iletişim kurun.", - "MessagePasswordResetForUsers": "Aşağıdaki kullanıcılar şifrelerini sıfırladı. Artık şifrelerini sıfırlamak için kullandıkları Easy PIN kodları ile giriş yapabilirler.", + "MessagePasswordResetForUsers": "Kullanıcı şifresini sıfırlama isteğinde bulunmuştur. Sıfırlama esnasında kullanılan PIN oturum açmak için kullanilabilir.", "MessageNoRepositories": "Kaynak deposu yok.", "LabelMaxVideoResolution": "En Yüksek İzin Verilen Video Transkodlama Çözünürlüğü", "ReleaseGroup": "Yayın Grubu", @@ -1589,9 +1589,9 @@ "MediaInfoVideoRangeType": "Videonun aralık türü", "LabelVideoRangeType": "Videonun aralık türü", "VideoRangeTypeNotSupported": "Videonun aralık türü desteklenmiyor", - "LabelVppTonemappingContrastHelp": "VPP ton eşlemede kontrast kazancını uygulayın. Önerilen ve varsayılan değerler 1,2 ve 1'dir.", + "LabelVppTonemappingContrastHelp": "VPP ton eşlemesinde kontrast kazancı uygulayın. Hem önerilen hem de varsayılan değerler 1'dir.", "LabelVppTonemappingContrast": "VPP Ton eşleme kontrast kazancı", - "LabelVppTonemappingBrightnessHelp": "VPP ton eşlemesinde parlaklık kazancını uygulayın. Hem önerilen hem de varsayılan değerler 0'dır.", + "LabelVppTonemappingBrightnessHelp": "VPP ton eşlemesinde parlaklık kazancı uygulayın. Önerilen ve varsayılan değerler 16 ve 0'dır.", "LabelVppTonemappingBrightness": "VPP Ton eşleme parlaklık kazancı", "EnableSplashScreen": "Açılış ekranını etkinleştir", "EnableEnhancedNvdecDecoderHelp": "Deneysel NVDEC uyarlanması, kod çözme hatalarıyla karşılaşmadığınız sürece bu seçeneği etkinleştirmeyin.", @@ -1674,5 +1674,79 @@ "TabContainers": "Barındırıcılar", "OptionDateShowAdded": "Dizi Eklenme Tarihi", "OptionDateEpisodeAdded": "Bölüm Eklenme Tarihi", - "DownloadAll": "Hepsini indir" + "DownloadAll": "Hepsini indir", + "AllowCollectionManagement": "Bu kullanıcının koleksiyonları yönetmesine izin ver", + "Experimental": "Deneysel", + "Featurette": "Tanıtım", + "Select": "Seçiniz", + "EnableCardLayout": "Görsel Kart kutusunu göster", + "TonemappingModeHelp": "Ton eşleme modunu seçin. Vurguların patladığını görürseniz RGB moduna geçmeyi deneyin.", + "Unreleased": "Henüz yayınlanmadı", + "LabelTonemappingMode": "Ton eşleme modu", + "GetThePlugin": "Eklentiyi edinin", + "HeaderDummyChapter": "Bölüm Görüntüleri", + "HeaderPerformance": "Performans", + "EnableAudioNormalizationHelp": "Ses normalizasyonu, ortalamayı istenen seviyede (-18dB) tutmak için sabit bir kazanç ekleyecektir.", + "EnableAudioNormalization": "Ses Normalizasyonu", + "HeaderRecordingMetadataSaving": "Meta Verilerin Kaydedilmesi", + "Short": "Kısa", + "LabelDummyChapterDurationHelp": "Taklit bölümler arasındaki aralık. Taklit bölüm oluşturmayı devre dışı bırakmak için 0 olarak ayarlayın. Bunu değiştirmenin mevcut taklit bölümler üzerinde hiçbir etkisi olmayacaktır.", + "LabelChapterImageResolution": "Çözünürlük", + "ResolutionMatchSource": "Eşleşme Kaynağı", + "SaveRecordingImages": "EPG görüntülerini kaydet", + "SubtitleBlack": "Siyah", + "SubtitleCyan": "Cam göbeği", + "SubtitleRed": "Kırmızı", + "PasswordRequiredForAdmin": "Yönetici hesapları için bir parola gereklidir.", + "SaveRecordingImagesHelp": "EPG listeleme sağlayıcısından görüntüleri medyanın yanına kaydedin.", + "StereoDownmixAlgorithmHelp": "Çok kanallı sesi stereoya düşürmek için kullanılan algoritma.", + "SubtitleGray": "Gri", + "SubtitleMagenta": "Eflatun", + "SubtitleYellow": "Sarı", + "UserMenu": "Kullanıcı Menüsü", + "Studio": "Stüdyo", + "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Ekstralar genellikle ana adla aynı gömülü ada sahiptir, yine de gömülü başlıkları kullanmak için bunu kontrol edin.", + "PreferEmbeddedExtrasTitlesOverFileNames": "Ekstralar için dosya adları yerine gömülü başlıkları tercih edin", + "MessageNoItemsAvailable": "Şu anda hiçbir ürün mevcut değildir.", + "LabelSyncPlayNoGroups": "Mevcut grup yok", + "MessageNoFavoritesAvailable": "Şu anda hiçbir favori mevcut değildir.", + "SaveRecordingNFO": "EPG meta verilerini NFO'ya kaydet", + "SaveRecordingNFOHelp": "EPG listeleme sağlayıcısından gelen meta verileri medya ile birlikte kaydedin.", + "SubtitleBlue": "Mavi", + "SubtitleGreen": "Yeşil", + "SubtitleLightGray": "Açık Gri", + "SubtitleWhite": "Beyaz", + "SecondarySubtitles": "İkincil Altyazılar", + "LabelStereoDownmixAlgorithm": "Stereo Karıştırma Algoritması", + "LabelEnableAudioVbr": "VBR ses kodlamasını etkinleştir", + "MenuClose": "Menüyü Kapat", + "MessageRenameMediaFolder": "Bir medya kitaplığını yeniden adlandırmak tüm meta verilerin kaybolmasına neden olacaktır, dikkatli olun.", + "Notifications": "Bildirimler", + "NotificationsMovedMessage": "Bildirim işlevi Webhook eklentisine taşındı.", + "MenuOpen": "Açık Menü", + "LabelEnableAudioVbrHelp": "Değişken bit hızı, ortalama bit hızı oranına göre daha iyi kalite sunar, ancak bazı nadir durumlarda arabelleğe alma ve uyumluluk sorunlarına neden olabilir.", + "LabelEnableLUFSScan": "LUFS taramasını etkinleştir", + "LabelEnableLUFSScanHelp": "Müzik için LUFS taramasını etkinleştirin (Bu daha uzun sürecek ve daha fazla kaynak gerektirecektir).", + "LabelDummyChapterDuration": "Aralık", + "LabelChapterImageResolutionHelp": "Çıkarılan bölüm görüntülerinin çözünürlüğü. Bunu değiştirmenin mevcut sahte bölümler üzerinde hiçbir etkisi olmayacaktır.", + "LabelParallelImageEncodingLimit": "Paralel görüntü kodlama sınırı", + "LabelParallelImageEncodingLimitHelp": "Paralel olarak çalışmasına izin verilen maksimum görüntü kodlaması miktarı. Bunu 0 olarak ayarlamak, sistem özelliklerinize göre bir sınır seçecektir.", + "AllowSegmentDeletionHelp": "Kullanıya gönderilmiş eski segmentleri silin. Bu işlem tamamen çevrilmiş dosyaların harddiskte ter kaplamasına engel olur. Bu seçenek sadece sınırlandırma etkin olduğunda çalışır. Eğer oynatımda problem yaşıyorsanız lütfen bu seçeneği devre dışı bırakın.", + "LabelDeveloper": "Yazılımcı", + "HeaderEpisodesStatus": "Bölüm Durumu", + "LogLevel.Debug": "Ayıkla", + "LabelDate": "Tarih", + "LabelLevel": "Seviye", + "LabelMediaDetails": "Medya detayları", + "LabelSystem": "Sistem", + "LogLevel.Trace": "Kalıntı", + "LogLevel.Information": "Bilgi", + "LogLevel.Warning": "Uyarı", + "LogLevel.Error": "Hata", + "LogLevel.Critical": "Kritik", + "HeaderConfirmRepositoryInstallation": "Eklenti yüklemesini onaylayın", + "Unknown": "Bilinmeyen", + "AllowSegmentDeletion": "Segmenti sil", + "LabelThrottleDelaySeconds": "…dan sonra sınılandır", + "LabelSegmentKeepSeconds": "Segmenteleri saklanma süresi" } diff --git a/src/strings/uk.json b/src/strings/uk.json index 2e33c4b6ce..c22ea84559 100644 --- a/src/strings/uk.json +++ b/src/strings/uk.json @@ -414,7 +414,7 @@ "HeaderChannelAccess": "Доступ до каналів", "Framerate": "Частота кадрів", "EnableThemeSongsHelp": "Відтворювання тематичної музики у фоновому режимі під час перегляду медіатеки.", - "DirectPlayHelp": "Вихідний файл повністю сумісний з цим клієнтом, а сеанс отримує файл без модифікацій.", + "DirectPlayHelp": "Вихідний файл повністю сумісний з цим клієнтом і сеанс отримує файл без модифікацій.", "DeviceAccessHelp": "Це стосується лише пристроїв, які можна однозначно ідентифікувати та не блокують доступ до браузера. Фільтрування доступу користувацьких пристроїв не дозволить їм використовувати нові пристрої, доки вони не будуть затверджені тут.", "DeinterlaceMethodHelp": "Оберіть метод деінтерлейсингу, який використовуватиметься під час програмного перекодування вмісту. Якщо ввімкнено апаратне прискорення з підтримкою апаратного деінтерлейсингу, воно буде використовуватись натомість.", "MusicVideos": "Відеокліпи", @@ -672,7 +672,7 @@ "MessageUnsetContentHelp": "Вміст відображатиметься у вигляді звичайних папок. Для досягнення найкращих результатів за допомогою менеджера метаданих встановіть типи вмісту підпапок.", "MessageSyncPlayErrorAccessingGroups": "Під час доступу до списку груп сталася помилка.", "MessagePlayAccessRestricted": "Відтворення цього вмісту наразі обмежене. Для отримання додаткової інформації зверніться до адміністратора свого сервера.", - "MessagePasswordResetForUsers": "У наступних користувачів було скинуто паролі. Тепер вони можуть увійти за допомогою простих PIN-кодів, які були використані для скидання.", + "MessagePasswordResetForUsers": "У наступних користувачів було скинуто паролі. Тепер вони можуть увійти за допомогою PIN-кодів, які були використані для скидання.", "MessageNoPluginConfiguration": "Цей плагін не має налаштувань.", "MessageDirectoryPickerLinuxInstruction": "Для Linux на Arch Linux, CentOS, Debian, Fedora, openSUSE або Ubuntu, ви повинні надати користувачеві служби принаймні доступ для читання до ваших сховищ.", "MessageDirectoryPickerBSDInstruction": "Для BSD вам може знадобитися налаштувати сховище у вашому jail FreeNAS, щоб Jellyfin мав доступ до ваших медіа.", @@ -1151,7 +1151,7 @@ "MessageReenableUser": "Щоб повторно ввімкнути, дивіться нижче", "MessagePluginInstallError": "Під час встановлення плагіна сталася помилка.", "MessagePluginInstalled": "Плагін успішно встановлено. Щоб зміни набули чинності, сервер потрібно буде перезапустити.", - "MessagePluginInstallDisclaimer": "Плагіни, створені учасниками спільноти, — це чудовий спосіб покращити ваш досвід за допомогою додаткових функцій і переваг. Перед встановленням зверніть увагу на наслідки, які вони можуть мати на вашому сервері, наприклад, триваліші сканування медіатек, додаткова фонова обробка та зниження стабільності системи.", + "MessagePluginInstallDisclaimer": "ПОПЕРЕДЖЕННЯ: Встановлення сторонніх плагінів несе певні ризики. Вони можуть містити нестабільний або шкідливий код і можуть змінюватися в будь-який час. Встановлюйте плагіни лише від авторів, яким ви довіряєте, і будь ласка, пам'ятайте про потенційні наслідки, які вони можуть мати, зокрема зовнішні запити до служб, довше сканування бібліотек або додаткову фонову обробку.", "MessagePluginConfigurationRequiresLocalAccess": "Щоб налаштувати цей плагін, увійдіть безпосередньо на свій локальний сервер.", "MessagePleaseWait": "Будь ласка, зачекайте. Це може зайняти хвилину.", "MessagePleaseEnsureInternetMetadata": "Переконайтеся, що завантаження Інтернет-метаданих увімкнено.", @@ -1303,7 +1303,7 @@ "OptionPlainVideoItems": "Відображати всі відео як звичайні відеоелементи", "OptionPlainStorageFolders": "Відображати всі папки як звичайні папки зберігання", "OptionParentalRating": "Батьківський рейтинг", - "OptionOnInterval": "На інтервалі", + "OptionOnInterval": "З інтервалом", "OptionNew": "Новий…", "OptionMissingEpisode": "Відсутні епізоди", "OptionMaxActiveSessionsHelp": "Значення 0 вимкне функцію.", @@ -1734,5 +1734,29 @@ "GetThePlugin": "Отримати плагін", "NotificationsMovedMessage": "Функціонал сповіщень переїхав до плагіна Webhook.", "Notifications": "Сповіщення", - "PasswordRequiredForAdmin": "Для облікових записів адміністратора потрібен пароль." + "PasswordRequiredForAdmin": "Для облікових записів адміністратора потрібен пароль.", + "LabelSyncPlayNoGroups": "Групи не доступні", + "HeaderConfirmRepositoryInstallation": "Підтвердити встановлення репозиторію плагінів", + "LabelDeveloper": "Розробник", + "MessageRepositoryInstallDisclaimer": "ПОПЕРЕДЖЕННЯ: Встановлення стороннього репозиторію плагінів несе певні ризики. Він може містити нестабільний або шкідливий код і може змінюватися в будь-який час. Встановлюйте репозиторії лише від авторів, яким ви довіряєте.", + "PleaseConfirmRepositoryInstallation": "Будь ласка, натисніть OK, щоб підтвердити, що ви прочитали вищезазначене і бажаєте продовжити встановлення репозиторію плагінів.", + "Unknown": "Невідомо", + "LabelDate": "Дата", + "LabelLevel": "Рівень", + "LabelMediaDetails": "Деталі про медіа", + "LabelSystem": "Система", + "LogLevel.Debug": "Налагодження", + "LogLevel.Information": "Інформація", + "LogLevel.Error": "Помилка", + "LogLevel.Critical": "Критично", + "LogLevel.None": "Нічого", + "LogLevel.Trace": "Відстеження", + "LogLevel.Warning": "Попередження", + "AllowSegmentDeletion": "Видалити сегменти", + "AllowSegmentDeletionHelp": "Видаляє старі сегменти після того, як вони були надіслані клієнту. Це дозволяє уникнути необхідності зберігати весь перекодований файл на диску. Працює лише з увімкненим дроселюванням. Вимкніть цю опцію, якщо у вас виникають проблеми з відтворенням.", + "LabelThrottleDelaySecondsHelp": "Час у секундах, після якого транскодер буде пригальмовано. Має бути достатньо великим, щоб клієнт міг підтримувати працездатний буфер. Працює тільки якщо обмеження увімкнено.", + "HeaderEpisodesStatus": "Статус епізодів", + "LabelThrottleDelaySeconds": "Обмежити після", + "LabelSegmentKeepSecondsHelp": "Час у секундах, протягом якого сегменти мають зберігатися перед перезаписом. Має бути більшим за \"Обмежити після\". Працює тільки якщо увімкнено видалення сегментів.", + "LabelSegmentKeepSeconds": "Час збереження сегментів" } diff --git a/src/strings/uz.json b/src/strings/uz.json index d48972b3e7..479bef48cf 100644 --- a/src/strings/uz.json +++ b/src/strings/uz.json @@ -172,7 +172,7 @@ "Artist": "Ijrochi", "Art": "Qirqim", "Arranger": "Aranjirovkachi", - "AroundTime": "Taxminan", + "AroundTime": "Taxminan {0}", "ApiKeysCaption": "Joriy API kalitlari ro'yxati", "Anytime": "Har qanday vaqtda", "AnyLanguage": "Har qanday til", diff --git a/src/strings/vi.json b/src/strings/vi.json index ed29622e76..d10764f510 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -884,7 +884,7 @@ "ButtonActivate": "Kích hoạt", "Authorize": "Ủy quyền", "MessagePlayAccessRestricted": "Việc phát lại nội dung này hiện bị hạn chế. Vui lòng liên hệ với quản trị viên máy chủ của bạn để biết thêm thông tin.", - "MessagePasswordResetForUsers": "Những người dùng này đã đặt lại mật khẩu của họ. Họ có thể đăng nhập với mã PIN Tiện Lợi đã được dùng để thiết lập lại.", + "MessagePasswordResetForUsers": "Những người dùng này đã đặt lại mật khẩu của họ. Giờ đây, họ có thể đăng nhập bằng mã PIN đã được sử dụng để thực hiện đặt lại.", "MessageNoTrailersFound": "Cài đặt kênh đoạn giới thiệu để nâng cao trải nghiệm phim của bạn bằng cách thêm thư viện đoạn giới thiệu trên internet.", "MessageNoServersAvailable": "Không tìm thấy máy chủ nào qua tính năng phát hiện máy chủ tự động.", "MessageNoPluginsInstalled": "Bạn chưa cài đặt plugin nào.", @@ -1140,7 +1140,7 @@ "MessageUnableToConnectToServer": "Chúng tôi không thể kết nối với máy chủ đã chọn ngay bây giờ. Hãy đảm bảo rằng nó đang chạy và thử lại.", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "Các vị trí phương tiện sau sẽ bị xóa khỏi thư viện của bạn", "MessageReenableUser": "Xem bên dưới để kích hoạt lại", - "MessagePluginInstallDisclaimer": "Các plugin do các thành viên cộng đồng xây dựng là một cách tuyệt vời để nâng cao trải nghiệm của bạn với các tính năng và lợi ích bổ sung. Trước khi cài đặt, hãy lưu ý về những ảnh hưởng mà chúng có thể có trên máy chủ của bạn, chẳng hạn như quét thư viện lâu hơn, xử lý nền bổ sung và giảm độ ổn định của hệ thống.", + "MessagePluginInstallDisclaimer": "CẢNH BÁO: Việc cài đặt plugin của bên thứ ba có rủi ro. Nó có thể chứa mã độc hại hoặc không ổn định và có thể thay đổi bất cứ lúc nào. Chỉ cài đặt plugin từ các tác giả mà bạn tin tưởng và vui lòng lưu ý những tác động tiềm ẩn mà nó có thể có, bao gồm truy vấn dịch vụ bên ngoài, quét thư viện lâu hơn hoặc xử lý nền bổ sung.", "MessagePluginConfigurationRequiresLocalAccess": "Để cài đặt plugin này, hãy đăng nhập trực tiếp vào máy chủ cục bộ của bạn.", "MessagePleaseWait": "Vui lòng đợi. Việc này có thể phải mất một ít thời gian.", "MessagePleaseEnsureInternetMetadata": "Hãy đảm bảo rằng việc tải xuống dữ liệu mô tả trên internet đã được bật.", @@ -1508,7 +1508,7 @@ "DisablePlugin": "Tắt", "EnablePlugin": "Bật", "Framerate": "Tỷ lệ khung hình", - "DirectPlayHelp": "Tệp nguồn hoàn toàn tương thích với ứng dụng khách này và phiên đang nhận tệp mà không có sửa đổi.", + "DirectPlayHelp": "Tệp nguồn hoàn toàn tương thích với ứng dụng khách này và phiên đang nhận tệp mà không cần sửa đổi.", "HeaderContinueReading": "Tiếp Tục Đọc", "EnableGamepadHelp": "Nghe đầu vào từ bất kỳ bộ điều khiển được kết nối nào. (Yêu cầu: Chế độ hiển thị 'TV')", "LabelEnableGamepad": "Bật Tay Cầm Chơi Game", @@ -1724,5 +1724,27 @@ "Notifications": "Thông báo", "NotificationsMovedMessage": "Chức năng thông báo đã chuyển sang plugin Webhook.", "LabelEnableLUFSScan": "Bật tính năng quét LUFS", - "LabelEnableLUFSScanHelp": "Bật tính năng quét LUFS để tìm nhạc (Việc này sẽ mất nhiều thời gian hơn và tốn nhiều tài nguyên hơn)." + "LabelEnableLUFSScanHelp": "Bật tính năng quét LUFS để tìm nhạc (Việc này sẽ mất nhiều thời gian hơn và tốn nhiều tài nguyên hơn).", + "PasswordRequiredForAdmin": "Cần có mật khẩu cho tài khoản quản trị viên.", + "LabelSyncPlayNoGroups": "Không có nhóm nào", + "LabelDate": "Ngày", + "LabelLevel": "Mức độ", + "LabelMediaDetails": "Chi tiết phương tiện", + "LabelSystem": "Hệ thống", + "LogLevel.Debug": "Gỡ lỗi", + "LogLevel.Information": "Thông tin", + "LogLevel.Warning": "Cảnh báo", + "LogLevel.Error": "Lỗi", + "LogLevel.Critical": "Phê bình", + "LogLevel.None": "Không có", + "MessageRepositoryInstallDisclaimer": "CẢNH BÁO: Việc cài đặt kho lưu trữ plugin của bên thứ ba có rủi ro. Nó có thể chứa mã độc hại hoặc không ổn định và có thể thay đổi bất cứ lúc nào. Chỉ cài đặt các kho lưu trữ từ các tác giả mà bạn tin tưởng.", + "Unknown": "Không rõ", + "HeaderConfirmRepositoryInstallation": "Xác Nhận Cài Đặt Kho Lưu Trữ Plugin", + "LabelDeveloper": "Nhà phát triển", + "PleaseConfirmRepositoryInstallation": "Vui lòng nhấp vào OK để xác nhận rằng bạn đã đọc phần trên và muốn tiếp tục cài đặt kho plugin.", + "AllowSegmentDeletionHelp": "Xóa các phân đoạn cũ sau khi chúng đã được gửi cho khách hàng. Điều này tránh phải lưu trữ toàn bộ tệp đã chuyển mã trên đĩa. Sẽ chỉ hoạt động khi bật điều chỉnh. Tắt tính năng này nếu bạn gặp sự cố phát lại.", + "LabelThrottleDelaySecondsHelp": "Thời gian tính bằng giây sau đó bộ chuyển mã sẽ được điều chỉnh. Phải đủ lớn để khách hàng duy trì bộ đệm khỏe mạnh. Chỉ hoạt động nếu điều chỉnh được bật.", + "AllowSegmentDeletion": "Xóa phân đoạn", + "LabelSegmentKeepSeconds": "Thời gian giữ phân đoạn", + "HeaderEpisodesStatus": "Trạng Thái Tập" } diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index 866af25d15..11c4cffdb1 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -775,7 +775,7 @@ "MessagePleaseEnsureInternetMetadata": "请确认已启用从网络上下载媒体资料的选项。", "MessagePleaseWait": "请稍等。这将花费大约1分钟的时间。", "MessagePluginConfigurationRequiresLocalAccess": "请直接登录你的本地服务器以设置这个插件。", - "MessagePluginInstallDisclaimer": "安装社区成员构建的插件来获取额外的功能是增强您体验的一种很好的方式。但在安装之前请意识到他们可能会对你的服务器造成影响,如更长的媒体库扫描时间、额外的后台处理、以及系统稳定性的降低等。", + "MessagePluginInstallDisclaimer": "警告:安装第三方插件有风险。它可能包含不稳定或恶意的代码,并且可能随时变化。请仅安装您信任的作者提供的插件,并请在安装之前意识到它可能造成的影响,例如对外部服务的查询、更长的媒体库扫描时间、以及额外的后台处理等。", "MessageReenableUser": "请参阅以下以重新启用", "MessageTheFollowingLocationWillBeRemovedFromLibrary": "以下媒体路径将从你的媒体库移除", "MessageUnableToConnectToServer": "现在无法连接所选择的服务器,请确保该服务器目前正在运行。", @@ -1737,5 +1737,22 @@ "GetThePlugin": "获取插件", "Notifications": "通知", "NotificationsMovedMessage": "通知功能已经转移到Webhook插件中。", - "PasswordRequiredForAdmin": "管理员账户需要密码。" + "PasswordRequiredForAdmin": "管理员账户需要密码。", + "LabelSyncPlayNoGroups": "没有可用的组", + "LabelDeveloper": "开发者", + "MessageRepositoryInstallDisclaimer": "警告:安装第三方插件仓库有风险。它可能包含不稳定或恶意的代码,并且可能随时变化。请仅安装您信任的作者提供的插件仓库。", + "PleaseConfirmRepositoryInstallation": "请点击确定键来确认您已经阅读了上述内容,并希望继续进行插件仓库的安装。", + "Unknown": "未知", + "HeaderConfirmRepositoryInstallation": "确认安装插件仓库", + "LabelDate": "日期", + "LabelLevel": "等级", + "LabelMediaDetails": "媒体详情", + "LabelSystem": "系统", + "LogLevel.Trace": "追踪", + "LogLevel.Debug": "调试", + "LogLevel.Information": "信息", + "LogLevel.Warning": "警告", + "LogLevel.Error": "错误", + "LogLevel.Critical": "严重", + "LogLevel.None": "无" } diff --git a/src/strings/zh-tw.json b/src/strings/zh-tw.json index 582b2b8fe7..81f19a4683 100644 --- a/src/strings/zh-tw.json +++ b/src/strings/zh-tw.json @@ -222,7 +222,7 @@ "AlwaysPlaySubtitlesHelp": "將會載入符合語音設定的字幕,無論語音是哪一個語言。", "AnyLanguage": "任何語言", "Anytime": "任何時間", - "AroundTime": "大概", + "AroundTime": "大概 {0}", "Art": "視覺圖", "Artists": "演出者", "AsManyAsPossible": "越多越好", diff --git a/src/types/cardOptions.ts b/src/types/cardOptions.ts new file mode 100644 index 0000000000..7864ab3157 --- /dev/null +++ b/src/types/cardOptions.ts @@ -0,0 +1,74 @@ +import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; + +export interface CardOptions { + itemsContainer?: HTMLElement | null; + parentContainer?: HTMLElement | null; + items?: BaseItemDto[] | null; + allowBottomPadding?: boolean; + centerText?: boolean; + coverImage?: boolean; + inheritThumb?: boolean; + overlayMoreButton?: boolean; + overlayPlayButton?: boolean; + overlayText?: boolean; + preferThumb?: boolean; + preferDisc?: boolean; + preferLogo?: boolean; + scalable?: boolean; + shape?: string | null; + lazy?: boolean; + cardLayout?: boolean | string; + showParentTitle?: boolean; + showParentTitleOrTitle?: boolean; + showAirTime?: boolean; + showAirDateTime?: boolean; + showChannelName?: boolean; + showTitle?: boolean | string; + showYear?: boolean | string; + showDetailsMenu?: boolean; + missingIndicator?: boolean; + showLocationTypeIndicator?: boolean; + showSeriesYear?: boolean; + showUnplayedIndicator?: boolean; + showChildCountIndicator?: boolean; + lines?: number; + context?: string | null; + action?: string | null; + defaultShape?: string; + indexBy?: string; + parentId?: string | null; + showMenu?: boolean; + cardCssClass?: string | null; + cardClass?: string | null; + centerPlayButton?: boolean; + overlayInfoButton?: boolean; + autoUpdate?: boolean; + cardFooterAside?: string; + includeParentInfoInTitle?: boolean; + maxLines?: number; + overlayMarkPlayedButton?: boolean; + overlayRateButton?: boolean; + showAirEndTime?: boolean; + showCurrentProgram?: boolean; + showCurrentProgramTime?: boolean; + showItemCounts?: boolean; + showPersonRoleOrType?: boolean; + showProgressBar?: boolean; + showPremiereDate?: boolean; + showRuntime?: boolean; + showSeriesTimerTime?: boolean; + showSeriesTimerChannel?: boolean; + showSongCount?: boolean; + width?: number; + showChannelLogo?: boolean; + showLogo?: boolean; + serverId?: string; + collectionId?: string | null; + playlistId?: string | null; + defaultCardImageIcon?: string; + disableHoverMenu?: boolean; + disableIndicators?: boolean; + showGroupCount?: boolean; + containerClass?: string; + noItemsMessage?: string; +} diff --git a/src/types/interface.ts b/src/types/interface.ts index 67c6565e27..c577f84e2a 100644 --- a/src/types/interface.ts +++ b/src/types/interface.ts @@ -1,19 +1,3 @@ -import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client'; - -export interface Query extends ViewQuerySettings { - IncludeItemTypes?: string; - Recursive?: boolean; - Fields?: string | null; - ImageTypeLimit?: number; - EnableTotalRecordCount?: boolean; - EnableImageTypes?: string; - StartIndex?: number; - ParentId?: string | null; - IsMissing?: boolean | null; - Limit?:number; - Filters?: string | null; -} - export interface ViewQuerySettings { showTitle?: boolean; showYear?: boolean; @@ -43,80 +27,3 @@ export interface ViewQuerySettings { NameStartsWith?: string | null; StartIndex?: number; } - -export interface CardOptions { - itemsContainer?: HTMLElement | null; - parentContainer?: HTMLElement | null; - items?: BaseItemDto[] | null; - allowBottomPadding?: boolean; - centerText?: boolean; - coverImage?: boolean; - inheritThumb?: boolean; - overlayMoreButton?: boolean; - overlayPlayButton?: boolean; - overlayText?: boolean; - preferThumb?: boolean; - preferDisc?: boolean; - preferLogo?: boolean; - scalable?: boolean; - shape?: string | null; - lazy?: boolean; - cardLayout?: boolean | string; - showParentTitle?: boolean; - showParentTitleOrTitle?: boolean; - showAirTime?: boolean; - showAirDateTime?: boolean; - showChannelName?: boolean; - showTitle?: boolean | string; - showYear?: boolean | string; - showDetailsMenu?: boolean; - missingIndicator?: boolean; - showLocationTypeIndicator?: boolean; - showSeriesYear?: boolean; - showUnplayedIndicator?: boolean; - showChildCountIndicator?: boolean; - lines?: number; - context?: string | null; - action?: string | null; - defaultShape?: string; - indexBy?: string; - parentId?: string | null; - showMenu?: boolean; - cardCssClass?: string | null; - cardClass?: string | null; - centerPlayButton?: boolean; - overlayInfoButton?: boolean; - autoUpdate?: boolean; - cardFooterAside?: string; - includeParentInfoInTitle?: boolean; - maxLines?: number; - overlayMarkPlayedButton?: boolean; - overlayRateButton?: boolean; - showAirEndTime?: boolean; - showCurrentProgram?: boolean; - showCurrentProgramTime?: boolean; - showItemCounts?: boolean; - showPersonRoleOrType?: boolean; - showProgressBar?: boolean; - showPremiereDate?: boolean; - showRuntime?: boolean; - showSeriesTimerTime?: boolean; - showSeriesTimerChannel?: boolean; - showSongCount?: boolean; - width?: number; - showChannelLogo?: boolean; - showLogo?: boolean; - serverId?: string; - collectionId?: string | null; - playlistId?: string | null; - defaultCardImageIcon?: string; - disableHoverMenu?: boolean; - disableIndicators?: boolean; - showGroupCount?: boolean; - containerClass?: string; - noItemsMessage?: string; -} - -export interface LibraryViewProps { - topParentId: string | null; -} diff --git a/src/types/library.ts b/src/types/library.ts new file mode 100644 index 0000000000..313a5ebe3e --- /dev/null +++ b/src/types/library.ts @@ -0,0 +1,41 @@ +import type { ItemFilter } from '@jellyfin/sdk/lib/generated-client/models/item-filter'; +import type { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video-type'; +import type { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import type { SeriesStatus } from '@jellyfin/sdk/lib/generated-client/models/series-status'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; + +export type ParentId = string | null | undefined; + +export interface LibraryViewProps { + parentId: string | null; +} + +interface Filters { + Features?: string[]; + Genres?: string[]; + OfficialRatings?: string[]; + Status?: ItemFilter[]; + EpisodesStatus?: string[]; + SeriesStatus?: SeriesStatus[]; + StudioIds?: string[]; + Tags?: string[]; + VideoTypes?: VideoType[]; + Years?: number[]; +} + +export interface LibraryViewSettings { + SortBy: ItemSortBy; + SortOrder: SortOrder; + StartIndex: number; + CardLayout: boolean; + ImageType: string; + ShowTitle: boolean; + ShowYear?: boolean; + Filters?: Filters; + IsSD?: boolean; + IsHD?: boolean; + Is4K?: boolean; + Is3D?: boolean; + NameLessThan?: string | null; + NameStartsWith?: string | null; +} diff --git a/src/types/suggestionsSections.ts b/src/types/suggestionsSections.ts new file mode 100644 index 0000000000..afb198a976 --- /dev/null +++ b/src/types/suggestionsSections.ts @@ -0,0 +1,36 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; +import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by'; +import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order'; +import { CardOptions } from './cardOptions'; + +interface ParametersOptions { + sortBy?: ItemSortBy[]; + sortOrder?: SortOrder[]; + includeItemTypes?: BaseItemKind[]; +} + +export enum SectionsViewType { + ResumeItems = 'resumeItems', + LatestMedia = 'latestMedia', + NextUp = 'nextUp', +} + +export enum SectionsView { + ContinueWatchingMovies = 'continuewatchingmovies', + LatestMovies = 'latestmovies', + ContinueWatchingEpisode = 'continuewatchingepisode', + LatestEpisode = 'latestepisode', + NextUp = 'nextUp', + LatestMusic = 'latestmusic', + RecentlyPlayedMusic = 'recentlyplayedmusic', + FrequentlyPlayedMusic = 'frequentlyplayedmusic', +} + +export interface Sections { + name: string; + view: SectionsView; + type: string; + viewType?: SectionsViewType, + parametersOptions?: ParametersOptions; + cardOptions: CardOptions; +} diff --git a/src/utils/fetchLocal.ts b/src/utils/fetchLocal.ts index cec934028f..8191e846e7 100644 --- a/src/utils/fetchLocal.ts +++ b/src/utils/fetchLocal.ts @@ -33,7 +33,7 @@ export default async function fetchLocal(url: string, options?: FetchOptions) { xhr.open('GET', url); - if (options && options.cache) { + if (options?.cache) { xhr.setRequestHeader('Cache-Control', options.cache); } diff --git a/webpack.common.js b/webpack.common.js index e1448db74f..59d32fb952 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -40,7 +40,8 @@ const config = { '@mui/private-theming': '@mui/private-theming/legacy', '@mui/styled-engine': '@mui/styled-engine/legacy', '@mui/system': '@mui/system/legacy', - '@mui/utils': '@mui/utils/legacy' + '@mui/utils': '@mui/utils/legacy', + '@mui/x-data-grid': '@mui/x-data-grid/legacy' } }, plugins: [ @@ -161,12 +162,17 @@ const config = { } }, { - test: /\.(js|jsx)$/, + test: /\.(js|jsx|mjs)$/, include: [ path.resolve(__dirname, 'node_modules/event-target-polyfill'), path.resolve(__dirname, 'node_modules/rvfc-polyfill'), path.resolve(__dirname, 'node_modules/@jellyfin/sdk'), + path.resolve(__dirname, 'node_modules/@react-hook/latest'), + path.resolve(__dirname, 'node_modules/@react-hook/passive-layout-effect'), + path.resolve(__dirname, 'node_modules/@react-hook/resize-observer'), path.resolve(__dirname, 'node_modules/@remix-run/router'), + path.resolve(__dirname, 'node_modules/@tanstack/query-core'), + path.resolve(__dirname, 'node_modules/@tanstack/react-query'), path.resolve(__dirname, 'node_modules/@uupaa/dynamic-import-polyfill'), path.resolve(__dirname, 'node_modules/axios'), path.resolve(__dirname, 'node_modules/blurhash'),