mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 18:59:28 -05:00
Compare commits
9 Commits
v2.0.0
...
preview-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
374eaee616 | ||
|
|
7e7a65caee | ||
|
|
7369802146 | ||
|
|
4b18817893 | ||
|
|
5fc5c2b4a3 | ||
|
|
f8ae87f2b5 | ||
|
|
894799d626 | ||
|
|
98b388b999 | ||
|
|
6ab41a5ec2 |
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Report a problem
|
||||
labels: ['bug', 'awaiting triage']
|
||||
labels: ['type:bug', 'awaiting-triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
2
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: ✨ Feature Request
|
||||
description: Suggest an idea
|
||||
labels: ['enhancement', 'awaiting triage']
|
||||
labels: ['type:enhancement', 'awaiting-triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
418
CHANGELOG.md
418
CHANGELOG.md
@@ -1,421 +1,3 @@
|
||||
# [2.0.0](https://github.com/fallenbagel/jellyseerr/compare/v1.9.2...v2.0.0) (2024-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* abort availability sync job if auth token invalid/connection lost ([#845](https://github.com/fallenbagel/jellyseerr/issues/845)) ([bdee340](https://github.com/fallenbagel/jellyseerr/commit/bdee34053080c8975a88ba16a9e8f402e10fe7e1))
|
||||
* add an error message to say when an email is already taken ([#947](https://github.com/fallenbagel/jellyseerr/issues/947)) ([89e0a83](https://github.com/fallenbagel/jellyseerr/commit/89e0a831ec85a6905f539f59b7523bb1feb90bcf))
|
||||
* add missing brackets ([#888](https://github.com/fallenbagel/jellyseerr/issues/888)) ([6cea8bb](https://github.com/fallenbagel/jellyseerr/commit/6cea8bba592b8db566b4d8147630385f5c377f1b))
|
||||
* add missing content-type header ([#887](https://github.com/fallenbagel/jellyseerr/issues/887)) ([2be9c7d](https://github.com/fallenbagel/jellyseerr/commit/2be9c7dcc1f418726a19e99cfdb3933257a03c6f))
|
||||
* add missing header when creating an issue ([#879](https://github.com/fallenbagel/jellyseerr/issues/879)) ([084e1b2](https://github.com/fallenbagel/jellyseerr/commit/084e1b224e109f0f8279741b9a5ead138396d7f8))
|
||||
* add missing parameter to delete requests from ExternalAPI ([#904](https://github.com/fallenbagel/jellyseerr/issues/904)) ([36d98a2](https://github.com/fallenbagel/jellyseerr/commit/36d98a2681921a8770027b78878688f2782e8b77)), closes [#903](https://github.com/fallenbagel/jellyseerr/issues/903)
|
||||
* **api:** fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([0116c13](https://github.com/fallenbagel/jellyseerr/commit/0116c13e0632d1ccec43299fbb10cd71db45bc29))
|
||||
* **api:** handle non-existent ratings on IMDb ([#822](https://github.com/fallenbagel/jellyseerr/issues/822)) ([74a2d25](https://github.com/fallenbagel/jellyseerr/commit/74a2d25f153b07a0cae5b44adca5fa1fed5a3b9e))
|
||||
* **api:** save new password when reset password of local account ([#886](https://github.com/fallenbagel/jellyseerr/issues/886)) ([5cc4389](https://github.com/fallenbagel/jellyseerr/commit/5cc43898256b130c2576f34a3d4e7ce6a3940d3e))
|
||||
* **blacklist:** add blacklist to mobile menu ([#980](https://github.com/fallenbagel/jellyseerr/issues/980)) ([f390da4](https://github.com/fallenbagel/jellyseerr/commit/f390da486625a22951956ba96867de63f73bfc2b)), closes [#979](https://github.com/fallenbagel/jellyseerr/issues/979)
|
||||
* change SeriesSearch to MissingEpisodeSearch for season requests ([#711](https://github.com/fallenbagel/jellyseerr/issues/711)) ([ee7e91c](https://github.com/fallenbagel/jellyseerr/commit/ee7e91c7c948b17b556a625919eb1252a721bb6e))
|
||||
* **docker:** add postinstall script ([#839](https://github.com/fallenbagel/jellyseerr/issues/839)) ([f714132](https://github.com/fallenbagel/jellyseerr/commit/f7141329094d88eb0940b1db1f21376142cb8893))
|
||||
* enhance error messages when Fetch API fails ([#893](https://github.com/fallenbagel/jellyseerr/issues/893)) ([fccfca6](https://github.com/fallenbagel/jellyseerr/commit/fccfca6ed06c8dc599e1ea4b1b3dbac48eb3a7f6))
|
||||
* handle status badge for season packs ([#927](https://github.com/fallenbagel/jellyseerr/issues/927)) ([80f6301](https://github.com/fallenbagel/jellyseerr/commit/80f63017ac5e9b1720a19c761dbef4dd517f1c2c))
|
||||
* length of undefined on users warnings ([#875](https://github.com/fallenbagel/jellyseerr/issues/875)) ([c600566](https://github.com/fallenbagel/jellyseerr/commit/c600566ac0045c2314f9013b063007b087ee4327))
|
||||
* remove DNS caching ([#837](https://github.com/fallenbagel/jellyseerr/issues/837)) ([268c7df](https://github.com/fallenbagel/jellyseerr/commit/268c7df28eea8b911d6a53297f5ce296983067ce))
|
||||
* remove email requirement for the user, and use the username if no email provided ([#900](https://github.com/fallenbagel/jellyseerr/issues/900)) ([d5f817e](https://github.com/fallenbagel/jellyseerr/commit/d5f817e734131cdacc229361d9498a095af57950))
|
||||
* remove protocol-relative URLs from next/image ([#889](https://github.com/fallenbagel/jellyseerr/issues/889)) ([c80d9a8](https://github.com/fallenbagel/jellyseerr/commit/c80d9a853a2a3451293a5382ef183c18add0c040))
|
||||
* resize episode preview image ([#842](https://github.com/fallenbagel/jellyseerr/issues/842)) ([96ba53f](https://github.com/fallenbagel/jellyseerr/commit/96ba53fecc7b9d269f0d974051ab62836b0102bc))
|
||||
* resize header image in network and studio pages ([#902](https://github.com/fallenbagel/jellyseerr/issues/902)) ([4220855](https://github.com/fallenbagel/jellyseerr/commit/422085523e5dfc132f3c3ca19eaa87117828b7be))
|
||||
* rewrite request from axios to Fetch ([#920](https://github.com/fallenbagel/jellyseerr/issues/920)) ([9aee888](https://github.com/fallenbagel/jellyseerr/commit/9aee8887d3cca6e018f4be1c8400c22e86bf8dab))
|
||||
* rewrite the rate limit utility ([#896](https://github.com/fallenbagel/jellyseerr/issues/896)) ([3fc14c9](https://github.com/fallenbagel/jellyseerr/commit/3fc14c9e2262463afec666e7f54e38d0d36cff68))
|
||||
* **session:** set the correct TTL for the cookie store ([#992](https://github.com/fallenbagel/jellyseerr/issues/992)) ([96e1d40](https://github.com/fallenbagel/jellyseerr/commit/96e1d40304749ce00d2ff7359efc39a1d9724358)), closes [#991](https://github.com/fallenbagel/jellyseerr/issues/991)
|
||||
* set correct user type when importing from emby ([#949](https://github.com/fallenbagel/jellyseerr/issues/949)) ([e57d265](https://github.com/fallenbagel/jellyseerr/commit/e57d2654d1c634a91649722d3a2bf4d73c4a02ca)), closes [#948](https://github.com/fallenbagel/jellyseerr/issues/948)
|
||||
* **setup:** page display when homepage is loading ([#940](https://github.com/fallenbagel/jellyseerr/issues/940)) ([7423bbb](https://github.com/fallenbagel/jellyseerr/commit/7423bbbffc5bee2e52e3348254f035dc8527d973))
|
||||
* **tmdb:** fallback movie/show overview to English when none is available in requested locale ([#928](https://github.com/fallenbagel/jellyseerr/issues/928)) ([12f908d](https://github.com/fallenbagel/jellyseerr/commit/12f908de7f5fbd717a5f151858b6edee3be13ed9)), closes [#925](https://github.com/fallenbagel/jellyseerr/issues/925)
|
||||
* update the filter removing existing users from Jellyfin import modal ([#924](https://github.com/fallenbagel/jellyseerr/issues/924)) ([61dcd8e](https://github.com/fallenbagel/jellyseerr/commit/61dcd8e487d7886773ccb12501623c17838476e5))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **jellyfin:** abstract jellyfin hostname, updated ui to reflect it, better validation ([#773](https://github.com/fallenbagel/jellyseerr/issues/773)) ([38ad875](https://github.com/fallenbagel/jellyseerr/commit/38ad875dd7848b4e92ac3ccdd16dbf785f6a5c4d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add environment variable for API key ([#831](https://github.com/fallenbagel/jellyseerr/issues/831)) ([45ef150](https://github.com/fallenbagel/jellyseerr/commit/45ef150e36944d456cc9440574b5ac75f2e4bbc1))
|
||||
* adds status filter for tv shows ([#796](https://github.com/fallenbagel/jellyseerr/issues/796)) ([cfd1bc2](https://github.com/fallenbagel/jellyseerr/commit/cfd1bc253557d6e19725743b8aa9a2fa33bbe760)), closes [#605](https://github.com/fallenbagel/jellyseerr/issues/605)
|
||||
* allow request managers to delete data from sonarr/radarr ([#644](https://github.com/fallenbagel/jellyseerr/issues/644)) ([a5d22ba](https://github.com/fallenbagel/jellyseerr/commit/a5d22ba5b83dd0e812b16f06476d993b5d59cb2a))
|
||||
* blacklist items from Discover page ([#632](https://github.com/fallenbagel/jellyseerr/issues/632)) ([818aa60](https://github.com/fallenbagel/jellyseerr/commit/818aa60aac185da07bfb71b08e0448939b63a736)), closes [#490](https://github.com/fallenbagel/jellyseerr/issues/490)
|
||||
* Jellyfin/Emby server type setup ([#685](https://github.com/fallenbagel/jellyseerr/issues/685)) ([15cb949](https://github.com/fallenbagel/jellyseerr/commit/15cb949f1f2e617853f90ae7bb8ae5d6622f610e))
|
||||
* **jellyfinapi:** switch to API tokens instead of auth tokens ([#868](https://github.com/fallenbagel/jellyseerr/issues/868)) ([bd4da6d](https://github.com/fallenbagel/jellyseerr/commit/bd4da6d5fc8cb55c2bc3d9a8336787cbd30814d0))
|
||||
* Option on item's page to add/remove from watchlist ([#781](https://github.com/fallenbagel/jellyseerr/issues/781)) ([2348f23](https://github.com/fallenbagel/jellyseerr/commit/2348f23f433195d64dee3e6eeede296fca5fdbc9)), closes [#730](https://github.com/fallenbagel/jellyseerr/issues/730)
|
||||
* refresh monitored downloads before getting queue items ([#994](https://github.com/fallenbagel/jellyseerr/issues/994)) ([92ba262](https://github.com/fallenbagel/jellyseerr/commit/92ba26207dcb1ddd696e0f01931d2609c521ae45)), closes [#866](https://github.com/fallenbagel/jellyseerr/issues/866)
|
||||
* show quality profile on request ([#847](https://github.com/fallenbagel/jellyseerr/issues/847)) ([6445332](https://github.com/fallenbagel/jellyseerr/commit/64453320d36595e75dcb710dfd43997bf2d2acd5))
|
||||
* **translation:** added full Hebrew translation ([#871](https://github.com/fallenbagel/jellyseerr/issues/871)) ([c96ca67](https://github.com/fallenbagel/jellyseerr/commit/c96ca6742e0a6d5685319c52f995fe06e439a450))
|
||||
* update Plex logo ([#884](https://github.com/fallenbagel/jellyseerr/issues/884)) ([3a363ae](https://github.com/fallenbagel/jellyseerr/commit/3a363ae1ffa7f384be6f7d25f8558b1e55a73fb3))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* fix(api): fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([#892](https://github.com/fallenbagel/jellyseerr/issues/892)) ([62dbde4](https://github.com/fallenbagel/jellyseerr/commit/62dbde448c7f7d530de8534bb8538452d0f91276))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and
|
||||
instead rely on the mediaServerType that is set in the `settings.json`. Existing environment
|
||||
variable users can log out and log back in to set the mediaServerType to `3` (Emby).
|
||||
|
||||
* feat(api): add severType to the api
|
||||
* This adds a serverType to the `/auth/jellyfin` which requires a serverType to be
|
||||
set (`jellyfin`/`emby`)
|
||||
|
||||
* refactor: use enums for serverType and rename selectedservice to serverType
|
||||
|
||||
* refactor(auth): jellyfin/emby authentication to set MediaServerType
|
||||
|
||||
* fix: issue page formatMessage for 4k media
|
||||
|
||||
* refactor: cleaner way of handling serverType change using MediaServerType instead of strings
|
||||
|
||||
instead of using strings now it will use MediaServerType enums for serverType
|
||||
|
||||
* revert: removed conditional render of the auto-request permission
|
||||
|
||||
reverts the conditional render toshow the auto-request permission if the mediaServerType was set to
|
||||
Plex as this should be handled in a different PR and Cypress tests should be modified
|
||||
accordingly(currently cypress test would fail if this conditional check is there)
|
||||
|
||||
* feat: add server type step to setup
|
||||
|
||||
* feat: migrate existing emby setups to use emby mediaServerType
|
||||
|
||||
* fix: scan jobs not running when media server type is emby
|
||||
|
||||
* fix: emby media server type migration
|
||||
|
||||
* refactor: change emby logo to full logo
|
||||
|
||||
* style: decrease emby logo size in setup screen
|
||||
|
||||
* refactor: use title case for servertype i18n message
|
||||
|
||||
* refactor(i18n): fix a typo
|
||||
|
||||
* refactor: use enums instead of numbers
|
||||
|
||||
* fix: remove old references to JELLYFIN_TYPE environment variable
|
||||
|
||||
* fix: go back to the last step when refresh the setup page
|
||||
|
||||
* fix: move "scanning in background" tip next to the scanning section
|
||||
|
||||
* fix: redirect the setup page when Jellyseerr is already setup
|
||||
* **jellyfin:** Jellyfin settings now does not include a hostname. Instead it abstracted it to ip,
|
||||
port, useSsl, and urlBase. However, migration of old settings to new settings should work
|
||||
automatically.
|
||||
|
||||
* refactor: remove console logs and use getHostname and ApiErrorCodes
|
||||
|
||||
* fix: store req.body jellyfin settings temporarily and store only if valid
|
||||
|
||||
This should fix the issue where settings are saved even if the url
|
||||
was invalid. Now the settings will only be saved if the url is
|
||||
valid. Sort of like a test connection.
|
||||
|
||||
* refactor: clean up commented out code
|
||||
|
||||
* refactor(i18n): extract translation keys
|
||||
|
||||
* fix(auth): auth failing with jellyfin login is disabled
|
||||
|
||||
* fix(settings): jellyfin migrations replacing the rest of the settings
|
||||
|
||||
* fix(settings): jellyfin hostname should be carried out if hostname exists
|
||||
|
||||
* fix(settings): merging the wrong settings source
|
||||
|
||||
* refactor(settings): use migrator for dynamic settings migrations
|
||||
|
||||
* refactor(settingsmigrator): settings migration handler and the migrations
|
||||
|
||||
* test(cypress): fix cypress tests failing
|
||||
|
||||
cypress settings were lacking some of the jobs so when the startJobs() is called when the app
|
||||
starts, it was failing to schedule the jobs where their cron timings were not specified in the
|
||||
cypress settings. Therefore, this commit adds those jobs back. In addition, other setting options
|
||||
were added to keep cypress settings consistent with a normal user.
|
||||
|
||||
* chore(prettierignore): ignore cypress/config/settings.cypress.json as it does not need prettier
|
||||
|
||||
* chore(prettier): ran formatter on cypress config to fix format check error
|
||||
|
||||
format check locally passes on this file. However, it fails during the github actions format check.
|
||||
Therefore, json language features formatter was run instead of prettier to see if that fixes the
|
||||
issue.
|
||||
|
||||
* test(cypress): add only missing jobs to the cypress settings
|
||||
|
||||
* ci: attempt at trying to get formatter to pass on cypress config json file
|
||||
|
||||
* refactor: revert the changes brought to try and fix formatter
|
||||
|
||||
added back the rest of the cypress settings and removed cypress settings from .prettierignore
|
||||
|
||||
* refactor(settings): better erorr logging when jellyfin connection test fails in settings page
|
||||
|
||||
## [1.9.2](https://github.com/fallenbagel/jellyseerr/compare/v1.9.1...v1.9.2) (2024-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** improve login resilience with headerless fallback authentication ([#814](https://github.com/fallenbagel/jellyseerr/issues/814)) ([a9741fa](https://github.com/fallenbagel/jellyseerr/commit/a9741fa36d06710aa00d28db3dd2c29f2b0973d3))
|
||||
* **auth:** validation of ipv6/ipv4 ([#812](https://github.com/fallenbagel/jellyseerr/issues/812)) ([9aeb360](https://github.com/fallenbagel/jellyseerr/commit/9aeb3604e6498c388df1d30dd0b613ba84160fc0)), closes [#795](https://github.com/fallenbagel/jellyseerr/issues/795)
|
||||
* bypass cache-able lookups when resolving localhost ([#813](https://github.com/fallenbagel/jellyseerr/issues/813)) ([b5a0699](https://github.com/fallenbagel/jellyseerr/commit/b5a069901a9545772deaa9c491f2075261da0189))
|
||||
|
||||
## [1.9.1](https://github.com/fallenbagel/jellyseerr/compare/v1.9.0...v1.9.1) (2024-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** add DNS caching ([#810](https://github.com/fallenbagel/jellyseerr/issues/810)) ([46ee8a4](https://github.com/fallenbagel/jellyseerr/commit/46ee8a4ca13b026bd929b4027eb001cc74064bb8)), closes [#387](https://github.com/fallenbagel/jellyseerr/issues/387) [#657](https://github.com/fallenbagel/jellyseerr/issues/657) [#728](https://github.com/fallenbagel/jellyseerr/issues/728)
|
||||
* empty email in user settings ([#807](https://github.com/fallenbagel/jellyseerr/issues/807)) ([20863d4](https://github.com/fallenbagel/jellyseerr/commit/20863d4a8dabe78fb5c52995b5bcb2da557a804e)), closes [#803](https://github.com/fallenbagel/jellyseerr/issues/803)
|
||||
* **jellyfinscanner:** assign only 4k available badge for a 4k request instead of both badges ([#805](https://github.com/fallenbagel/jellyseerr/issues/805)) ([d31a2c3](https://github.com/fallenbagel/jellyseerr/commit/d31a2c37e639c1126b446277fa5d666d8102fef5))
|
||||
* remove the settings button of media when useless ([#809](https://github.com/fallenbagel/jellyseerr/issues/809)) ([f52939e](https://github.com/fallenbagel/jellyseerr/commit/f52939e4cdcbee94fc35165f613f6b3e21599e3c))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "ci: update format check command to ignore .prettierignore files (#787)" (#788) ([4757f1c](https://github.com/fallenbagel/jellyseerr/commit/4757f1c3e599304410a737c11f97db92a2bfcefd)), closes [#787](https://github.com/fallenbagel/jellyseerr/issues/787) [#788](https://github.com/fallenbagel/jellyseerr/issues/788)
|
||||
|
||||
# [1.9.0](https://github.com/fallenbagel/jellyseerr/compare/v1.8.1...v1.9.0) (2024-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** save user email on the first try ([#760](https://github.com/fallenbagel/jellyseerr/issues/760)) ([0bbcfdc](https://github.com/fallenbagel/jellyseerr/commit/0bbcfdc4f9ff9735f45232a2412ac8444f525de9)), closes [#227](https://github.com/fallenbagel/jellyseerr/issues/227) [#748](https://github.com/fallenbagel/jellyseerr/issues/748)
|
||||
* **api:** small errors on overseerr-api.yaml ([#721](https://github.com/fallenbagel/jellyseerr/issues/721)) ([0eea109](https://github.com/fallenbagel/jellyseerr/commit/0eea1090dfdba4333646280c84b09b0197fefa74))
|
||||
* **auth:** case-sensitive logins not updating authtokens ([#778](https://github.com/fallenbagel/jellyseerr/issues/778)) ([2bd125d](https://github.com/fallenbagel/jellyseerr/commit/2bd125d9a55d15a398ceb5f2996105a5e861b6e0))
|
||||
* **jellyfinapi:** use external api class for jellyfin api requests ([#762](https://github.com/fallenbagel/jellyseerr/issues/762)) ([650c339](https://github.com/fallenbagel/jellyseerr/commit/650c339d74d4fe85ef7f76184901e86f4eeada85)), closes [#728](https://github.com/fallenbagel/jellyseerr/issues/728) [#387](https://github.com/fallenbagel/jellyseerr/issues/387)
|
||||
* **logging:** handle media server connection refused error/toast ([#748](https://github.com/fallenbagel/jellyseerr/issues/748)) ([f486fb5](https://github.com/fallenbagel/jellyseerr/commit/f486fb5e75f9ea21456952b6a52cb841e30f3556))
|
||||
* use UTF8 encoding for webhook JSON ([#714](https://github.com/fallenbagel/jellyseerr/issues/714)) ([c0a0b9c](https://github.com/fallenbagel/jellyseerr/commit/c0a0b9c8a8b0c2eeaf3fa9159f10742baa9f6c1f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Latin American Spanish translation ([#725](https://github.com/fallenbagel/jellyseerr/issues/725)) ([783fda9](https://github.com/fallenbagel/jellyseerr/commit/783fda9621aef8ffd46e5f036136de82ed502ccc)), closes [#677](https://github.com/fallenbagel/jellyseerr/issues/677)
|
||||
* add merge conflict labeler workflow ([#719](https://github.com/fallenbagel/jellyseerr/issues/719)) ([d9d07c7](https://github.com/fallenbagel/jellyseerr/commit/d9d07c705a24d5c49905066aac45a3c6a2e36a53))
|
||||
* **auth:** send real information on login ([#470](https://github.com/fallenbagel/jellyseerr/issues/470)) ([d765055](https://github.com/fallenbagel/jellyseerr/commit/d765055da83ee94546399f6348aee14d8427d462))
|
||||
* **settings:** stores jellyfin/emby server name in the settings ([#763](https://github.com/fallenbagel/jellyseerr/issues/763)) ([7a5e8d6](https://github.com/fallenbagel/jellyseerr/commit/7a5e8d69bf620c8e7bf5f284840b1a5fe757ae5f))
|
||||
|
||||
## [1.8.1](https://github.com/fallenbagel/jellyseerr/compare/v1.8.0...v1.8.1) (2024-04-17)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix: disable seasonfolder option in sonarr for jellyfin/Emby users" (#718) ([cd0fa3e](https://github.com/fallenbagel/jellyseerr/commit/cd0fa3e2232dcb522673143f113fc382fb2ff0a3)), closes [#718](https://github.com/fallenbagel/jellyseerr/issues/718)
|
||||
|
||||
# [1.8.0](https://github.com/fallenbagel/jellyseerr/compare/v1.7.0...v1.8.0) (2024-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct width issue in datepicker of filterSliderOver ([f564cdd](https://github.com/fallenbagel/jellyseerr/commit/f564cddff4525ccebffbf304672d49c57aefe635)), closes [#415](https://github.com/fallenbagel/jellyseerr/issues/415)
|
||||
* disable seasonfolder option in sonarr for jellyfin/Emby users ([8ec8f2a](https://github.com/fallenbagel/jellyseerr/commit/8ec8f2ac5730aad3b12dcd8ed95bb553b46b399c)), closes [#126](https://github.com/fallenbagel/jellyseerr/issues/126) [#575](https://github.com/fallenbagel/jellyseerr/issues/575)
|
||||
* **embyauth:** remove the accidentally added mediaServerType change code from another PR ([#684](https://github.com/fallenbagel/jellyseerr/issues/684)) ([c2e8771](https://github.com/fallenbagel/jellyseerr/commit/c2e87714b4c4aa11bf68dcd82b76979f82990f3c))
|
||||
* ensure watchlist updates are immediately reflected ([b85d7f3](https://github.com/fallenbagel/jellyseerr/commit/b85d7f37b931735ca2ad955dccb6599bf445fc73))
|
||||
* fix german translation for "components.Discover.FilterSlideover.tmdbuservotecount" ([e032c02](https://github.com/fallenbagel/jellyseerr/commit/e032c02f5f84dc4b6b470eecb18ba2c376c55f37))
|
||||
* fix the translations for watchlist permissions and userSettings page ([8c82a61](https://github.com/fallenbagel/jellyseerr/commit/8c82a61450a7525c0e2f1b64e6939da47a7c715d))
|
||||
* **i18n:** fixed jellyfin jobs ([7eed236](https://github.com/fallenbagel/jellyseerr/commit/7eed23637ddfb10bdcb19698e7ae171f07299502))
|
||||
* **jellyfin.ts:** process virtual seasons if they have non virtual episodes ([#639](https://github.com/fallenbagel/jellyseerr/issues/639)) ([db84f65](https://github.com/fallenbagel/jellyseerr/commit/db84f6529ab285be26c96daaab065dfabf347417))
|
||||
* **jellyfinapi:** refactors jellyfin library sync to support automatic grouping and collections ([#700](https://github.com/fallenbagel/jellyseerr/issues/700)) ([3856061](https://github.com/fallenbagel/jellyseerr/commit/3856061fe1ee4d3457996586b4979ad9dd60765a)), closes [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#256](https://github.com/fallenbagel/jellyseerr/issues/256) [#489](https://github.com/fallenbagel/jellyseerr/issues/489) [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#515](https://github.com/fallenbagel/jellyseerr/issues/515) [#474](https://github.com/fallenbagel/jellyseerr/issues/474) [#473](https://github.com/fallenbagel/jellyseerr/issues/473)
|
||||
* **jellyfinlogin:** use externalHostname if set for forgetpassword link ([405f6bb](https://github.com/fallenbagel/jellyseerr/commit/405f6bbb7ffc390327c99dcef2cbbf9b3bc75f01)), closes [#199](https://github.com/fallenbagel/jellyseerr/issues/199) [#424](https://github.com/fallenbagel/jellyseerr/issues/424) [#212](https://github.com/fallenbagel/jellyseerr/issues/212)
|
||||
* **jellyfinscanner:** conditionally assign the jellyfinMediaId and jellyfinMediaId4k ([#686](https://github.com/fallenbagel/jellyseerr/issues/686)) ([530be42](https://github.com/fallenbagel/jellyseerr/commit/530be4272cce1b0d74d7f4156b8d794cda6ea03f)), closes [#681](https://github.com/fallenbagel/jellyseerr/issues/681)
|
||||
* **langcode:** fixes the ukranian language code ([dc67aaa](https://github.com/fallenbagel/jellyseerr/commit/dc67aaaf53eae86ba20c6c2798c92ec40962d85f)), closes [#504](https://github.com/fallenbagel/jellyseerr/issues/504)
|
||||
* nullable type for jellyfinMediaId(4k) ([#702](https://github.com/fallenbagel/jellyseerr/issues/702)) ([0900a95](https://github.com/fallenbagel/jellyseerr/commit/0900a95532501b6f4d9698de7530a771512924fc)), closes [#668](https://github.com/fallenbagel/jellyseerr/issues/668)
|
||||
* request watchlist items sequentially to prevent bypassing quota ([#3667](https://github.com/fallenbagel/jellyseerr/issues/3667)) ([b40ba07](https://github.com/fallenbagel/jellyseerr/commit/b40ba07a4de5857b8392f667038eeb0b22aa5d9a))
|
||||
* resolved issue with region selector and all regions value ([#3652](https://github.com/fallenbagel/jellyseerr/issues/3652)) ([28a2c50](https://github.com/fallenbagel/jellyseerr/commit/28a2c50495d0ce531da7f8c442bd488a54b1e84c))
|
||||
* typos on readme ([#655](https://github.com/fallenbagel/jellyseerr/issues/655)) ([eee9a02](https://github.com/fallenbagel/jellyseerr/commit/eee9a025d246c72bcd3aca753d9e49c1f8f064ea))
|
||||
* **watchlist:** added missing prop for watchlist item removal button in watchlist page ([a0ec992](https://github.com/fallenbagel/jellyseerr/commit/a0ec992028093257e9fa043622e236014f02dea3))
|
||||
* **watchlist:** discover local watchlist item display and profile local watchlist slider visibility ([3cb9494](https://github.com/fallenbagel/jellyseerr/commit/3cb9494e6210151716587d8c4b22e0a21692cf88))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ko language ([#3619](https://github.com/fallenbagel/jellyseerr/issues/3619)) ([9250735](https://github.com/fallenbagel/jellyseerr/commit/92507359b48db08b0066047d6505660b8c8b0b12))
|
||||
* add Peacock to Network Slider ([#3545](https://github.com/fallenbagel/jellyseerr/issues/3545)) ([0c39057](https://github.com/fallenbagel/jellyseerr/commit/0c39057ca58743697e9dcc3b678440ac3688c65a))
|
||||
* add tooltips to tautulli avatars ([#3601](https://github.com/fallenbagel/jellyseerr/issues/3601)) ([c484810](https://github.com/fallenbagel/jellyseerr/commit/c484810f965f8d04643c25c6d283dd83f4bd4a23))
|
||||
* added Letterboxd links for the external link blocks for movies ([981f5e6](https://github.com/fallenbagel/jellyseerr/commit/981f5e679c4c707e119741240a58de8bb07f9d6c))
|
||||
* check if first jellyfin user is admin ([#635](https://github.com/fallenbagel/jellyseerr/issues/635)) ([010df62](https://github.com/fallenbagel/jellyseerr/commit/010df62776191fe4c195e590df338f8d8523f55b)), closes [#610](https://github.com/fallenbagel/jellyseerr/issues/610)
|
||||
* jellyseerr makeover ([#715](https://github.com/fallenbagel/jellyseerr/issues/715)) ([0c27132](https://github.com/fallenbagel/jellyseerr/commit/0c2713213c56de342f76300d12ce01fd543d2ce3))
|
||||
* **job:** media availability support for jellyfin/emby ([#522](https://github.com/fallenbagel/jellyseerr/issues/522)) ([3eb1bb3](https://github.com/fallenbagel/jellyseerr/commit/3eb1bb3d8ff22391acb2e629bbec7b6e4b65ca95)), closes [#406](https://github.com/fallenbagel/jellyseerr/issues/406) [#193](https://github.com/fallenbagel/jellyseerr/issues/193) [#516](https://github.com/fallenbagel/jellyseerr/issues/516) [#362](https://github.com/fallenbagel/jellyseerr/issues/362) [#84](https://github.com/fallenbagel/jellyseerr/issues/84)
|
||||
* **notif:** add Pushover sound options ([#2403](https://github.com/fallenbagel/jellyseerr/issues/2403)) ([3ea5076](https://github.com/fallenbagel/jellyseerr/commit/3ea5076053359b518b1b4d537e7b61580d9275a3))
|
||||
* select default seriesType for anime ([#3627](https://github.com/fallenbagel/jellyseerr/issues/3627)) ([f628635](https://github.com/fallenbagel/jellyseerr/commit/f6286359cfd2ed93fc692aa2efda37310e02c11c)), closes [#3626](https://github.com/fallenbagel/jellyseerr/issues/3626)
|
||||
* standard series type selector ([#3628](https://github.com/fallenbagel/jellyseerr/issues/3628)) ([7bdd25e](https://github.com/fallenbagel/jellyseerr/commit/7bdd25e5a45843a3e530d3fa2b0887664b53eec8))
|
||||
* translations update from Hosted Weblate ([#3258](https://github.com/fallenbagel/jellyseerr/issues/3258)) ([e62a078](https://github.com/fallenbagel/jellyseerr/commit/e62a078298ced7dec627fb3ff9fc8f99a39d5e1b))
|
||||
* update SameSite policy of session cookie to Lax ([#3650](https://github.com/fallenbagel/jellyseerr/issues/3650)) ([c84ca43](https://github.com/fallenbagel/jellyseerr/commit/c84ca4307465af4278f3dad5cf9c2b8cbae3fada))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **jellyfinapi:** reverts [#450](https://github.com/fallenbagel/jellyseerr/issues/450) as it broke library sync support for local accounts using LDAP ([b5acc09](https://github.com/fallenbagel/jellyseerr/commit/b5acc09ba98e2dd9b61e6b78721e4dd9f42a996c)), closes [#489](https://github.com/fallenbagel/jellyseerr/issues/489)
|
||||
|
||||
# [1.7.0](https://github.com/fallenbagel/jellyseerr/compare/v1.6.0...v1.7.0) (2023-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust the plex watchlist sync schedule to have fuzziness ([#3502](https://github.com/fallenbagel/jellyseerr/issues/3502)) ([2c3f533](https://github.com/fallenbagel/jellyseerr/commit/2c3f5330764492e1323afd2d1f25e28ad78a2f2f))
|
||||
* handle issue causing incorrect media to change to unknown ([#3516](https://github.com/fallenbagel/jellyseerr/issues/3516)) ([83b008c](https://github.com/fallenbagel/jellyseerr/commit/83b008c8391459bd02dc74bcdb0d8caf27207bdf))
|
||||
* improved handling of edge case that could cause availability sync to fail ([#3497](https://github.com/fallenbagel/jellyseerr/issues/3497)) ([d0836ce](https://github.com/fallenbagel/jellyseerr/commit/d0836ce0efd55fccf2546087a0c4f94f7cb2e82a))
|
||||
* Include all defaults in payload ([#3538](https://github.com/fallenbagel/jellyseerr/issues/3538)) ([cb63bf2](https://github.com/fallenbagel/jellyseerr/commit/cb63bf217b9e8810a5210b4bf475b2a96583cc84))
|
||||
* multiple notifications for available media ([048fa96](https://github.com/fallenbagel/jellyseerr/commit/048fa967f2e5b23831ac9917c703934c50ef75f0))
|
||||
* repeat notifications for available 4k media ([30361f2](https://github.com/fallenbagel/jellyseerr/commit/30361f2ab751d9a882a9120e0f3df28dc42cc2cd))
|
||||
* resolved issue with create slider causing incorrect form submission ([#3514](https://github.com/fallenbagel/jellyseerr/issues/3514)) ([a761b7d](https://github.com/fallenbagel/jellyseerr/commit/a761b7dd35a5bd61bb4eb0275b75d1e0977e6a2d))
|
||||
* resolved user access check issue ([#3551](https://github.com/fallenbagel/jellyseerr/issues/3551)) ([2816c66](https://github.com/fallenbagel/jellyseerr/commit/2816c66300bf870d493c0665b0e984d60f707dfd))
|
||||
* **server/api/jellyfin.ts:** use /Library/VirtualFolders Jellyfin API call to fetch Jellyfin libs ([8685f57](https://github.com/fallenbagel/jellyseerr/commit/8685f5796a99d9700146bae9892319db10508d68)), closes [#256](https://github.com/fallenbagel/jellyseerr/issues/256)
|
||||
* **statusbadge:** handle missing season/episode number ([#3526](https://github.com/fallenbagel/jellyseerr/issues/3526)) ([01de972](https://github.com/fallenbagel/jellyseerr/commit/01de972a8fe2ea3c18d5b2f426d01b5b14d142d4))
|
||||
* **tautulli:** only test connection if hostname is defined ([#3573](https://github.com/fallenbagel/jellyseerr/issues/3573)) ([f7b4dfc](https://github.com/fallenbagel/jellyseerr/commit/f7b4dfcac472d08c54779a14fc1ad3c90927df26))
|
||||
* **ui:** corrected issues icon color ([#3498](https://github.com/fallenbagel/jellyseerr/issues/3498)) ([c1a47bd](https://github.com/fallenbagel/jellyseerr/commit/c1a47bd9de332cb4925974690f5a33448b5cc2e6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **rating:** added IMDB Radarr proxy ([#3496](https://github.com/fallenbagel/jellyseerr/issues/3496)) ([b4191f9](https://github.com/fallenbagel/jellyseerr/commit/b4191f9c65b7ff08764e61d18e7a75bc8d4b3325))
|
||||
|
||||
# [1.6.0](https://github.com/fallenbagel/jellyseerr/compare/v1.5.0...v1.6.0) (2023-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* availability sync file detection ([#3371](https://github.com/fallenbagel/jellyseerr/issues/3371)) ([7522aa3](https://github.com/fallenbagel/jellyseerr/commit/7522aa31743b169c903ebdf9d4d698645d27514c))
|
||||
* corrected initial fallback data load on details page ([#3395](https://github.com/fallenbagel/jellyseerr/issues/3395)) ([4bd8764](https://github.com/fallenbagel/jellyseerr/commit/4bd87647d0551c20e13589a62690a6f3e5ad8ff7))
|
||||
* correctly load series fallback modal with sonarr v4 ([#3451](https://github.com/fallenbagel/jellyseerr/issues/3451)) ([e051b1d](https://github.com/fallenbagel/jellyseerr/commit/e051b1dfea9c9320cc9dd420c475ae74cff0d901))
|
||||
* **deps:** update all non-major dependencies ([#3223](https://github.com/fallenbagel/jellyseerr/issues/3223)) ([f5191ad](https://github.com/fallenbagel/jellyseerr/commit/f5191aded680357522a65bbdcc40d162b8fbf594))
|
||||
* error deleting users with over 1000 requests ([#3376](https://github.com/fallenbagel/jellyseerr/issues/3376)) ([ac77b03](https://github.com/fallenbagel/jellyseerr/commit/ac77b037d5fb0c54f5edf4b29d04adb57aef388f))
|
||||
* external url regex is now consistent with internal url ([33ec443](https://github.com/fallenbagel/jellyseerr/commit/33ec4436fb82e1eb1bc97dd650088c27785e9d94))
|
||||
* externalLinkBlock ([46cd4d0](https://github.com/fallenbagel/jellyseerr/commit/46cd4d01d9a3cf17d79350c5e678202820272299))
|
||||
* fix regex for internal url to use a more effecient one ([e848386](https://github.com/fallenbagel/jellyseerr/commit/e848386d10f05f157e7a6dde8847ecab50c169ac))
|
||||
* fixes RT ratings for tv shows ([#3492](https://github.com/fallenbagel/jellyseerr/issues/3492)) ([04fbd00](https://github.com/fallenbagel/jellyseerr/commit/04fbd00d4ac29045592588ef8b664d1916991e37)), closes [#3491](https://github.com/fallenbagel/jellyseerr/issues/3491)
|
||||
* **genreselector:** fix searching in Genre filter ([#3468](https://github.com/fallenbagel/jellyseerr/issues/3468)) ([d7fa35e](https://github.com/fallenbagel/jellyseerr/commit/d7fa35e066cf371797aaa46ca464aa531ba8fb35))
|
||||
* handle search results with collections ([#3393](https://github.com/fallenbagel/jellyseerr/issues/3393)) ([70b1540](https://github.com/fallenbagel/jellyseerr/commit/70b1540ae23e83e01013856a9e06ad39e600922d))
|
||||
* lock body scroll when using webkit ([#3399](https://github.com/fallenbagel/jellyseerr/issues/3399)) ([c27f960](https://github.com/fallenbagel/jellyseerr/commit/c27f96096ac8cc6c387f9d1dde5b263576ac2132))
|
||||
* **logs:** jellyfin auth error now has the severity warn consistent with local login ([cc041b5](https://github.com/fallenbagel/jellyseerr/commit/cc041b5e0aa2b67573edba5919772b77a5111162)), closes [#224](https://github.com/fallenbagel/jellyseerr/issues/224)
|
||||
* make a (shallow) copy of radarr/sonarr tags into a request before adding user tags ([#3485](https://github.com/fallenbagel/jellyseerr/issues/3485)) ([48f7666](https://github.com/fallenbagel/jellyseerr/commit/48f76662d5c08156f1da3f47e216c5f02668f64b))
|
||||
* **ui:** corrected default badge hover opacity ([#3369](https://github.com/fallenbagel/jellyseerr/issues/3369)) ([a4d07f5](https://github.com/fallenbagel/jellyseerr/commit/a4d07f5afab613317d96c9c6e9b47157a5a28986))
|
||||
* **ui:** corrected mobile menu spacing in collection details ([#3432](https://github.com/fallenbagel/jellyseerr/issues/3432)) ([77a33cb](https://github.com/fallenbagel/jellyseerr/commit/77a33cb74d744bb747b791785799b632af8c7862))
|
||||
* **ui:** Make play symbol white ([1fe4bb8](https://github.com/fallenbagel/jellyseerr/commit/1fe4bb8a0415a72791ced75a2fba1027287398d5))
|
||||
* **ui:** Resize Emby icon and add margins ([ad69d67](https://github.com/fallenbagel/jellyseerr/commit/ad69d6715e976630092bfbbb1843886523551014))
|
||||
* **watchlist:** add validation for creation request ([03316c6](https://github.com/fallenbagel/jellyseerr/commit/03316c642d1ecf89753789af08caf6e3aac80113))
|
||||
* **watchlist:** fix github code scanning ([c08897b](https://github.com/fallenbagel/jellyseerr/commit/c08897bdc1cff65862c62347572bbbd01b6c36ac))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **add watchlist:** adding midding functionality from overserr ([5f1c10d](https://github.com/fallenbagel/jellyseerr/commit/5f1c10d50aaa430bcda96218ef2cc12a0eb926f3))
|
||||
* adds streaming services custom slider ([#3361](https://github.com/fallenbagel/jellyseerr/issues/3361)) ([2520d8f](https://github.com/fallenbagel/jellyseerr/commit/2520d8f739abfde608f3ef66a9fbe6b7b5c6647a))
|
||||
* auto tagging requested media with username ([#3338](https://github.com/fallenbagel/jellyseerr/issues/3338)) ([24f268b](https://github.com/fallenbagel/jellyseerr/commit/24f268b6cb67d9a8d8675cd6e09dd83a7f499add))
|
||||
* **discover:** support filtering by tmdb user vote count on discover page ([#3407](https://github.com/fallenbagel/jellyseerr/issues/3407)) ([aa84977](https://github.com/fallenbagel/jellyseerr/commit/aa849776809dfe891e67ff4db6861ef44df1a774))
|
||||
* **settings:** add internal url to jellyfin settings form ([0a30cd3](https://github.com/fallenbagel/jellyseerr/commit/0a30cd356d217a39546c016cc8bfa6ff6ad75e3e)), closes [#194](https://github.com/fallenbagel/jellyseerr/issues/194)
|
||||
* **src/components/externallinkblock/index.tsx:** support Emby icon ([672061c](https://github.com/fallenbagel/jellyseerr/commit/672061cd646c97c9954790c8e50eac88ea2666e9))
|
||||
* **tooltip:** email tooltip now appears when hovered over info icon ([cd7930e](https://github.com/fallenbagel/jellyseerr/commit/cd7930eef98451a781e5c9dc5ec223600a379f42))
|
||||
* translations update ([47287c3](https://github.com/fallenbagel/jellyseerr/commit/47287c368885d14bd1a56e3e8318ce22dd0f6ddf)), closes [#381](https://github.com/fallenbagel/jellyseerr/issues/381)
|
||||
* **watchlist:** add translation for en ([b7e3d28](https://github.com/fallenbagel/jellyseerr/commit/b7e3d285ed35b623062eceb0d99035cafbf075a6))
|
||||
|
||||
# [1.5.0](https://github.com/fallenbagel/jellyseerr/compare/v1.4.1...v1.5.0) (2023-04-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add better checks on 4k detection of series ([bc9017f](https://github.com/fallenbagel/jellyseerr/commit/bc9017f54d84ec24c4d74d38e1b4e24219425d41))
|
||||
* added a refresh interval if download status is in progress ([#3275](https://github.com/fallenbagel/jellyseerr/issues/3275)) ([1e2c6f4](https://github.com/fallenbagel/jellyseerr/commit/1e2c6f46ab66c836f321b5d8e34f1e8124c0b542))
|
||||
* **build:** increase threshold for amount of data to be fetched when SSR'ing ([#3320](https://github.com/fallenbagel/jellyseerr/issues/3320)) ([d7b83d2](https://github.com/fallenbagel/jellyseerr/commit/d7b83d22cee3d20db564cc0564d42802b02327e3))
|
||||
* disable availability sync temporarily ([2e5cf22](https://github.com/fallenbagel/jellyseerr/commit/2e5cf226265686012329248e7f729fec324c3deb))
|
||||
* hide remove button when default service is not configured ([7d4455b](https://github.com/fallenbagel/jellyseerr/commit/7d4455ba6bfd12e2730f7085cbb87df246f01d22))
|
||||
* **jellyfin scan:** temporary workaround fix for jellyfin scan when display specials within season ([38fb66d](https://github.com/fallenbagel/jellyseerr/commit/38fb66d31e41232c01898d0d362af8338eb7b960)), closes [#215](https://github.com/fallenbagel/jellyseerr/issues/215) [#176](https://github.com/fallenbagel/jellyseerr/issues/176) [#246](https://github.com/fallenbagel/jellyseerr/issues/246)
|
||||
* lint issues ([bcd2bb7](https://github.com/fallenbagel/jellyseerr/commit/bcd2bb7c96810f5a6932f42468a628d2db1bc771))
|
||||
* logger was set to info for the wrong logs ([#3354](https://github.com/fallenbagel/jellyseerr/issues/3354)) ([c36a4ba](https://github.com/fallenbagel/jellyseerr/commit/c36a4ba2b8df05873f5dfd0946a9bc3dc4ecfd1d))
|
||||
* remove unnecessary parenthesis from api key generation ([#3336](https://github.com/fallenbagel/jellyseerr/issues/3336)) ([6bd3f01](https://github.com/fallenbagel/jellyseerr/commit/6bd3f015d65507efca60279007bd2b86ee860643))
|
||||
* **snapcraft:** use the correct config folder for image cache ([#3302](https://github.com/fallenbagel/jellyseerr/issues/3302)) ([c93467b](https://github.com/fallenbagel/jellyseerr/commit/c93467b3acf2c256324297e7e8f21e9944005dd4))
|
||||
* **ui:** hide mini status badge if non-4K media status is unknown ([#3346](https://github.com/fallenbagel/jellyseerr/issues/3346)) ([50f06da](https://github.com/fallenbagel/jellyseerr/commit/50f06dabbffc693f0843584a64d1d96e77982820))
|
||||
* **ui:** hide search bar behind slideover when opened ([#3348](https://github.com/fallenbagel/jellyseerr/issues/3348)) ([b3882de](https://github.com/fallenbagel/jellyseerr/commit/b3882de8930a70adb2f93a27be6370bfa1826587))
|
||||
* **ui:** prevent title cards from flickering when quickly hovering across them ([#3349](https://github.com/fallenbagel/jellyseerr/issues/3349)) ([eb5502a](https://github.com/fallenbagel/jellyseerr/commit/eb5502a16f86e37a933f6beca0678c2d228e77d5))
|
||||
* **watchlist:** correctly load more than 20 watchlist items ([#3351](https://github.com/fallenbagel/jellyseerr/issues/3351)) ([af880a6](https://github.com/fallenbagel/jellyseerr/commit/af880a6c839794b34bddcd7e0fe56353aa48ba36))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a button in ManageSlideOver to remove the movie and the file from Radarr/Sonarr ([2e74584](https://github.com/fallenbagel/jellyseerr/commit/2e7458457e995dd3ec6dd96035fe997646cdd446))
|
||||
* availability sync rework ([#3219](https://github.com/fallenbagel/jellyseerr/issues/3219)) ([ae38183](https://github.com/fallenbagel/jellyseerr/commit/ae3818304b2f75222d1bd223ece94f829a3b42d0)), closes [#377](https://github.com/fallenbagel/jellyseerr/issues/377)
|
||||
* full title of download item on hover with tooltip ([#3296](https://github.com/fallenbagel/jellyseerr/issues/3296)) ([33e7691](https://github.com/fallenbagel/jellyseerr/commit/33e7691b94d7d369a0a1410e434850bc51e5572e))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **imageproxy:** do not set cookies to image proxy so CDNs can cache images ([#3332](https://github.com/fallenbagel/jellyseerr/issues/3332)) ([966639d](https://github.com/fallenbagel/jellyseerr/commit/966639df430d32f6bfebdb16314dc4590d21caf8))
|
||||
|
||||
## [1.4.1](https://github.com/fallenbagel/jellyseerr/compare/v1.4.0...v1.4.1) (2023-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass in library type when scanning recently added items ([#3287](https://github.com/fallenbagel/jellyseerr/issues/3287)) ([8942eb8](https://github.com/fallenbagel/jellyseerr/commit/8942eb8b7c4fa1d16aa2e72e8ba7120a653c9aa2))
|
||||
* **ui:** air date will use UTC for timezone ([#3297](https://github.com/fallenbagel/jellyseerr/issues/3297)) ([3e43586](https://github.com/fallenbagel/jellyseerr/commit/3e43586acc0804c3fff524509caa890a104e132b))
|
||||
* **ui:** correct range slider styling in chrome ([#3299](https://github.com/fallenbagel/jellyseerr/issues/3299)) ([d954328](https://github.com/fallenbagel/jellyseerr/commit/d9543289111d72245564d25d300a71b0ea3954ba))
|
||||
* **ui:** show 5 icons when possible on mobile menu ([#3298](https://github.com/fallenbagel/jellyseerr/issues/3298)) ([7040da1](https://github.com/fallenbagel/jellyseerr/commit/7040da1334f6d18e19a494c73caa17f7df552dfe))
|
||||
* **ui:** style range thumbs correctly for firefox ([#3294](https://github.com/fallenbagel/jellyseerr/issues/3294)) ([9d10e6a](https://github.com/fallenbagel/jellyseerr/commit/9d10e6a88c0996671f1d9d20792e1930dbc82329))
|
||||
|
||||
# [1.4.0](https://github.com/fallenbagel/jellyseerr/compare/v1.3.0...v1.4.0) (2023-01-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add bg-opacity to in-progress status badges ([#3190](https://github.com/fallenbagel/jellyseerr/issues/3190)) ([68223f4](https://github.com/fallenbagel/jellyseerr/commit/68223f4b1e98b01825516dcba39cbb2d3df31a70))
|
||||
* added download status and title to request card/item error components ([#3186](https://github.com/fallenbagel/jellyseerr/issues/3186)) ([3309f77](https://github.com/fallenbagel/jellyseerr/commit/3309f77aa4be1d70b27693531c119a8e26822518))
|
||||
* arrow icons were misplaced on mobile in slider edit ([#3260](https://github.com/fallenbagel/jellyseerr/issues/3260)) ([d328485](https://github.com/fallenbagel/jellyseerr/commit/d328485161b9cae6a70ef0713b4878207bc6015e))
|
||||
* **build:** update usage of publish snap action ([#3272](https://github.com/fallenbagel/jellyseerr/issues/3272)) ([51b05cd](https://github.com/fallenbagel/jellyseerr/commit/51b05cd8fbb5d332807d8c00b2ffb7b10c3d0179))
|
||||
* changed overflow scroll to only if necessary ([#3184](https://github.com/fallenbagel/jellyseerr/issues/3184)) ([27feeea](https://github.com/fallenbagel/jellyseerr/commit/27feeea69121336557deda1f32b65a5daa146f82))
|
||||
* convert genre/studio to string in create slider ([#3201](https://github.com/fallenbagel/jellyseerr/issues/3201)) ([93afead](https://github.com/fallenbagel/jellyseerr/commit/93afead92e497f2e5bce67a34fffdaa08d20c7f2))
|
||||
* correct checkbox position (again) for slider edits ([#3227](https://github.com/fallenbagel/jellyseerr/issues/3227)) ([3ba6df1](https://github.com/fallenbagel/jellyseerr/commit/3ba6df1a41c084c4a6a90354338047623abef521))
|
||||
* correct grid sizing for webkit on streaming services ([#3248](https://github.com/fallenbagel/jellyseerr/issues/3248)) ([6fd11cf](https://github.com/fallenbagel/jellyseerr/commit/6fd11cf4254e1a19310592bec78a6de52bc073a8))
|
||||
* correct issue detail bottom padding on mobile displays ([#3268](https://github.com/fallenbagel/jellyseerr/issues/3268)) ([3db010b](https://github.com/fallenbagel/jellyseerr/commit/3db010b9eaec62aa08d973a61caf1801471bbf3e))
|
||||
* correct link to correct keyword results for series ([#3208](https://github.com/fallenbagel/jellyseerr/issues/3208)) ([4e9be7a](https://github.com/fallenbagel/jellyseerr/commit/4e9be7a3f7304ee7be5ee6fd34b1ea8f6c0cf399))
|
||||
* correct spacing between sliders ([#3225](https://github.com/fallenbagel/jellyseerr/issues/3225)) ([62e2de7](https://github.com/fallenbagel/jellyseerr/commit/62e2de70bf37b72d5f63370b662d4103a642775b))
|
||||
* correctly check mobile menu permissions ([#3271](https://github.com/fallenbagel/jellyseerr/issues/3271)) ([f4a22dc](https://github.com/fallenbagel/jellyseerr/commit/f4a22dc437404558f301ccfc195cf0a300dd1ff2))
|
||||
* correctly restore selected streaming service filters ([#3249](https://github.com/fallenbagel/jellyseerr/issues/3249)) ([154f3e7](https://github.com/fallenbagel/jellyseerr/commit/154f3e72efbf0b663358b3029156f54516f01a2f))
|
||||
* create shared class to add bottom spacing ([#3269](https://github.com/fallenbagel/jellyseerr/issues/3269)) ([5d1c6f7](https://github.com/fallenbagel/jellyseerr/commit/5d1c6f706555613d97ed9e61d8b665543c2f239b))
|
||||
* **deps:** pin dependency @headlessui/react to 1.7.7 ([#3194](https://github.com/fallenbagel/jellyseerr/issues/3194)) [skip ci] ([c4b16ab](https://github.com/fallenbagel/jellyseerr/commit/c4b16abc62647c74215155942a4230a31a238677))
|
||||
* **deps:** update dependency @heroicons/react to v2 ([#2970](https://github.com/fallenbagel/jellyseerr/issues/2970)) ([dd48d59](https://github.com/fallenbagel/jellyseerr/commit/dd48d59b20e2d1800ea30912116f4a4f1bb7928f))
|
||||
* **deps:** update dependency axios to v1 ([#3202](https://github.com/fallenbagel/jellyseerr/issues/3202)) ([421029e](https://github.com/fallenbagel/jellyseerr/commit/421029ebab66c9a6622ba47e56d7f6473524cce4))
|
||||
* **deps:** update dependency swr to v2 ([#3212](https://github.com/fallenbagel/jellyseerr/issues/3212)) ([7b6db50](https://github.com/fallenbagel/jellyseerr/commit/7b6db50ae55b1fc60d19a5cff62dd46bb989fa51))
|
||||
* **experimental:** use new RT API (sorta) ([#3179](https://github.com/fallenbagel/jellyseerr/issues/3179)) ([357cab8](https://github.com/fallenbagel/jellyseerr/commit/357cab87ac7752b8e119b51c938b343c661d83c2))
|
||||
* improve small screen layout for discover editing ([#3221](https://github.com/fallenbagel/jellyseerr/issues/3221)) ([d23b213](https://github.com/fallenbagel/jellyseerr/commit/d23b2132de05f072f7f9daad83d81421d747cf99))
|
||||
* include new package calendar css in build ([#3235](https://github.com/fallenbagel/jellyseerr/issues/3235)) ([c2a1a20](https://github.com/fallenbagel/jellyseerr/commit/c2a1a20a3bb20039a1936c7fe0ecb9e8311a0aea))
|
||||
* issues with issues ([#3267](https://github.com/fallenbagel/jellyseerr/issues/3267)) ([fd21971](https://github.com/fallenbagel/jellyseerr/commit/fd219717c01c558814d7a80de6304272b5a7944e))
|
||||
* multiple genre filtering now works ([#3282](https://github.com/fallenbagel/jellyseerr/issues/3282)) ([5076938](https://github.com/fallenbagel/jellyseerr/commit/507693881b939819413f0959df5ef6b7a357eb5c))
|
||||
* prevent double encode if we are on /search endpoint ([#3238](https://github.com/fallenbagel/jellyseerr/issues/3238)) ([a343f8a](https://github.com/fallenbagel/jellyseerr/commit/a343f8ad915491a9c81512c7e541a1dac8906025))
|
||||
* **request:** approve request when retrying request ([#3234](https://github.com/fallenbagel/jellyseerr/issues/3234)) ([b515701](https://github.com/fallenbagel/jellyseerr/commit/b5157010c46cd9083993d5ee0172007b83d631da))
|
||||
* **request:** mark request as approved if media is already available when retrying failed request ([#3244](https://github.com/fallenbagel/jellyseerr/issues/3244)) ([cb65074](https://github.com/fallenbagel/jellyseerr/commit/cb650745f6a33e69391a633e6d272831f314e098))
|
||||
* restore border to ghost button and fix discover slider visibility toggle position ([#3226](https://github.com/fallenbagel/jellyseerr/issues/3226)) ([2eebb7f](https://github.com/fallenbagel/jellyseerr/commit/2eebb7fd3941b34fe9472aaf9d28265df8cce311))
|
||||
* restore status badges on titles on actors page when hide available media enabled ([#3206](https://github.com/fallenbagel/jellyseerr/issues/3206)) ([9d3446d](https://github.com/fallenbagel/jellyseerr/commit/9d3446d370499c3251159393e5c791b01225e05c))
|
||||
* screen would zoom on mobile if date picker input was selected ([#3241](https://github.com/fallenbagel/jellyseerr/issues/3241)) ([3aefddd](https://github.com/fallenbagel/jellyseerr/commit/3aefddd48834d86150d5f5cceb2d08af3a78847b))
|
||||
* series displayed an empty season with series list/request modal ([#3147](https://github.com/fallenbagel/jellyseerr/issues/3147)) ([2179637](https://github.com/fallenbagel/jellyseerr/commit/2179637d437999290eaa4152f6f37c71fc3d8ba3))
|
||||
* tooltip shows properly if not in progress ([#3185](https://github.com/fallenbagel/jellyseerr/issues/3185)) ([6face8c](https://github.com/fallenbagel/jellyseerr/commit/6face8cc4564b978fb98af32659b326d8c5cede8))
|
||||
* **ui:** series first air date sorting ([#3283](https://github.com/fallenbagel/jellyseerr/issues/3283)) ([374c78c](https://github.com/fallenbagel/jellyseerr/commit/374c78c989cc86bb144a954a91d5d183c4b591c0))
|
||||
* update StatusBadgeMini to shrink on title cards (and remove ring) ([#3210](https://github.com/fallenbagel/jellyseerr/issues/3210)) ([042a1a9](https://github.com/fallenbagel/jellyseerr/commit/042a1a950fdd4d4a61edf4bc19657f9b7a526da8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add discover customization ([#3182](https://github.com/fallenbagel/jellyseerr/issues/3182)) ([cd35748](https://github.com/fallenbagel/jellyseerr/commit/cd3574851a12517cbfadc109e6412a7a9e44c114))
|
||||
* add keywords to movie/series detail pages ([#3204](https://github.com/fallenbagel/jellyseerr/issues/3204)) ([e084649](https://github.com/fallenbagel/jellyseerr/commit/e084649878a58c296786141d12dd69a69a27ee85))
|
||||
* add streaming services filter ([#3247](https://github.com/fallenbagel/jellyseerr/issues/3247)) ([1154156](https://github.com/fallenbagel/jellyseerr/commit/1154156459403494e8daf0c89a3ba356aeea1d97))
|
||||
* discover inline customization ([#3220](https://github.com/fallenbagel/jellyseerr/issues/3220)) ([8bd10b5](https://github.com/fallenbagel/jellyseerr/commit/8bd10b5bf3d1b8069872b616c7c8596caeb4937e))
|
||||
* discover overhaul (filters!) ([#3232](https://github.com/fallenbagel/jellyseerr/issues/3232)) ([dd00e48](https://github.com/fallenbagel/jellyseerr/commit/dd00e48f59054b44bef6b32a2c169e59f6175051))
|
||||
* discover slider edit arrow buttons for reordering ([#3259](https://github.com/fallenbagel/jellyseerr/issues/3259)) ([da00d45](https://github.com/fallenbagel/jellyseerr/commit/da00d454e17e8b00d04f6e26f6dd5153ed6ced81))
|
||||
* **lang:** translations update from Hosted Weblate ([#3030](https://github.com/fallenbagel/jellyseerr/issues/3030)) ([0d8b390](https://github.com/fallenbagel/jellyseerr/commit/0d8b390b678731e76bd1f0f8a0a4952c11e77f4d))
|
||||
* new mobile menu ([#3251](https://github.com/fallenbagel/jellyseerr/issues/3251)) ([fcbca17](https://github.com/fallenbagel/jellyseerr/commit/fcbca1722f31f32633a57bc5048f46c9da057d87))
|
||||
* translations update from Hosted Weblate ([#3218](https://github.com/fallenbagel/jellyseerr/issues/3218)) ([5940ff7](https://github.com/fallenbagel/jellyseerr/commit/5940ff7f5f62eed9ac5aa6f02803418aaa09813a))
|
||||
* **ui:** add episode number to front of episode name in season details ([#3086](https://github.com/fallenbagel/jellyseerr/issues/3086)) ([a672b32](https://github.com/fallenbagel/jellyseerr/commit/a672b324ec391a20f6f3a1daed82a8d276a52c2c))
|
||||
* **ui:** request card progress bar ([#3123](https://github.com/fallenbagel/jellyseerr/issues/3123)) ([03853a1](https://github.com/fallenbagel/jellyseerr/commit/03853a1b9155c8a2153c8885022a74619af1bc15))
|
||||
|
||||
# [1.3.0](https://github.com/fallenbagel/jellyseerr/compare/v1.2.1...v1.3.0) (2023-01-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/ckbvBtDJgC"><img src="https://img.shields.io/discord/952656177924300932" alt="Discord"></a>
|
||||
<a href="https://hub.docker.com/r/fallenbagel/jellyseerr"><img src="https://img.shields.io/docker/pulls/fallenbagel/jellyseerr" alt="Docker pulls"></a>
|
||||
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
|
||||
<a href="http://jellyseerr.borgcube.de/engage/jellyseerr/"><img src="http://jellyseerr.borgcube.de/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
|
||||
<a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-47-orange.svg"/></a>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const VersionMismatchWarning = () => {
|
||||
<>
|
||||
{!isUpToDate ? (
|
||||
<Admonition type="warning">
|
||||
The <a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/servers/jellyseerr/default.nix#L14">upstream Jellyseerr Nix Package (v{nixpkgVersion})</a> is not <b>up-to-date</b>. If you want to use <b>Jellyseerr v{jellyseerrVersion}</b>, you will need to <a href="#overriding-the-package-derivation">override the package derivation</a>.
|
||||
The <a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/servers/jellyseerr/default.nix#L14">upstream Jellyseerr Nix Package (v{nixpkgVersion})</a> is not <b>up-to-date</b>. If you want to use <b>Jellyseerr v{jellyseerrVersion}</b>, you will need to <a href="#overriding-the-package">override the package derivation</a>.
|
||||
</Admonition>
|
||||
) : (
|
||||
<Admonition type="success">
|
||||
@@ -95,12 +95,12 @@ export const VersionMatch = () => {
|
||||
};
|
||||
|
||||
offlineCache = pkgs.fetchYarnDeps {
|
||||
sha256 = pkgs.lib.fakeSha256;
|
||||
sha256 = pkgs.lib.fakeSha256;
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
}`;
|
||||
|
||||
|
||||
const module = `{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
@@ -12,8 +12,6 @@ This is your Jellyseerr API key, which can be used to integrate Jellyseerr with
|
||||
|
||||
If you need to generate a new API key for any reason, simply click the button to the right of the text box.
|
||||
|
||||
If you want to set the API key, rather than letting it be randomly generated, you can use the API_KEY environment variable. Whatever that variable is set to will be your API key.
|
||||
|
||||
## Application Title
|
||||
|
||||
If you aren't a huge fan of the name "Jellyseerr" and would like to display something different to your users, you can customize the application title!
|
||||
|
||||
@@ -35,7 +35,7 @@ Users can override the [global display language](/using-jellyseerr/settings/gene
|
||||
|
||||
### Discover Region & Discover Language
|
||||
|
||||
Users can override the [global filter settings](/using-jellyseerr/settings/general#discover-region--discover-language) to suit their own preferences.
|
||||
Users can override the [global filter settings](/using-jellyseerr/settings/general#discover-region-and-discover-language) to suit their own preferences.
|
||||
|
||||
### Movie Request Limit & Series Request Limit
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ module.exports = {
|
||||
commitTag: process.env.COMMIT_TAG || 'local',
|
||||
forceIpv4First: process.env.FORCE_IPV4_FIRST === 'true' ? 'true' : 'false',
|
||||
},
|
||||
publicRuntimeConfig: {
|
||||
// Will be available on both server and client
|
||||
JELLYFIN_TYPE: process.env.JELLYFIN_TYPE,
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{ hostname: 'gravatar.com' },
|
||||
|
||||
@@ -38,8 +38,6 @@ tags:
|
||||
description: Endpoints related to getting service (Radarr/Sonarr) details.
|
||||
- name: watchlist
|
||||
description: Collection of media to watch later
|
||||
- name: blacklist
|
||||
description: Blacklisted media from discovery page.
|
||||
servers:
|
||||
- url: '{server}/api/v1'
|
||||
variables:
|
||||
@@ -48,19 +46,6 @@ servers:
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Blacklist:
|
||||
type: object
|
||||
properties:
|
||||
tmdbId:
|
||||
type: number
|
||||
example: 1
|
||||
title:
|
||||
type: string
|
||||
media:
|
||||
$ref: '#/components/schemas/MediaInfo'
|
||||
userId:
|
||||
type: number
|
||||
example: 1
|
||||
Watchlist:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3610,8 +3595,6 @@ paths:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
serverType:
|
||||
type: number
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
@@ -4066,94 +4049,6 @@ paths:
|
||||
restricted:
|
||||
type: boolean
|
||||
example: false
|
||||
/blacklist:
|
||||
get:
|
||||
summary: Returns blacklisted items
|
||||
description: Returns list of all blacklisted media
|
||||
tags:
|
||||
- settings
|
||||
parameters:
|
||||
- in: query
|
||||
name: take
|
||||
schema:
|
||||
type: number
|
||||
nullable: true
|
||||
example: 25
|
||||
- in: query
|
||||
name: skip
|
||||
schema:
|
||||
type: number
|
||||
nullable: true
|
||||
example: 0
|
||||
- in: query
|
||||
name: search
|
||||
schema:
|
||||
type: string
|
||||
nullable: true
|
||||
example: dune
|
||||
responses:
|
||||
'200':
|
||||
description: Blacklisted items returned
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
pageInfo:
|
||||
$ref: '#/components/schemas/PageInfo'
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
user:
|
||||
$ref: '#/components/schemas/User'
|
||||
createdAt:
|
||||
type: string
|
||||
example: 2024-04-21T01:55:44.000Z
|
||||
id:
|
||||
type: number
|
||||
example: 1
|
||||
mediaType:
|
||||
type: string
|
||||
example: movie
|
||||
title:
|
||||
type: string
|
||||
example: Dune
|
||||
tmdbId:
|
||||
type: number
|
||||
example: 438631
|
||||
post:
|
||||
summary: Add media to blacklist
|
||||
tags:
|
||||
- blacklist
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Blacklist'
|
||||
responses:
|
||||
'201':
|
||||
description: Item succesfully blacklisted
|
||||
'412':
|
||||
description: Item has already been blacklisted
|
||||
/blacklist/{tmdbId}:
|
||||
delete:
|
||||
summary: Remove media from blacklist
|
||||
tags:
|
||||
- blacklist
|
||||
parameters:
|
||||
- in: path
|
||||
name: tmdbId
|
||||
description: tmdbId ID
|
||||
required: true
|
||||
example: '1'
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Succesfully removed media item
|
||||
/watchlist:
|
||||
post:
|
||||
summary: Add media to watchlist
|
||||
@@ -4976,11 +4871,6 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
example: 8|9
|
||||
- in: query
|
||||
name: status
|
||||
schema:
|
||||
type: string
|
||||
example: 3|4
|
||||
responses:
|
||||
'200':
|
||||
description: Results
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jellyseerr",
|
||||
"version": "2.0.0",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -76,7 +76,7 @@ class ExternalAPI {
|
||||
}
|
||||
const data = await this.getDataFromResponse(response);
|
||||
|
||||
if (this.cache && ttl !== 0) {
|
||||
if (this.cache) {
|
||||
this.cache.set(cacheKey, data, ttl ?? DEFAULT_TTL);
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ class ExternalAPI {
|
||||
}
|
||||
const resData = await this.getDataFromResponse(response);
|
||||
|
||||
if (this.cache && ttl !== 0) {
|
||||
if (this.cache) {
|
||||
this.cache.set(cacheKey, resData, ttl ?? DEFAULT_TTL);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ class ExternalAPI {
|
||||
}
|
||||
const resData = await this.getDataFromResponse(response);
|
||||
|
||||
if (this.cache && ttl !== 0) {
|
||||
if (this.cache) {
|
||||
this.cache.set(cacheKey, resData, ttl ?? DEFAULT_TTL);
|
||||
}
|
||||
|
||||
|
||||
@@ -157,13 +157,9 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
|
||||
|
||||
public getQueue = async (): Promise<(QueueItem & QueueItemAppendT)[]> => {
|
||||
try {
|
||||
const data = await this.get<QueueResponse<QueueItemAppendT>>(
|
||||
`/queue`,
|
||||
{
|
||||
includeEpisode: 'true',
|
||||
},
|
||||
0
|
||||
);
|
||||
const data = await this.get<QueueResponse<QueueItemAppendT>>(`/queue`, {
|
||||
includeEpisode: 'true',
|
||||
});
|
||||
|
||||
return data.records;
|
||||
} catch (e) {
|
||||
@@ -197,24 +193,15 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
|
||||
}
|
||||
};
|
||||
|
||||
async refreshMonitoredDownloads(): Promise<void> {
|
||||
await this.runCommand('RefreshMonitoredDownloads', {});
|
||||
}
|
||||
|
||||
protected async runCommand(
|
||||
commandName: string,
|
||||
options: Record<string, unknown>
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.post(
|
||||
`/command`,
|
||||
{
|
||||
name: commandName,
|
||||
...options,
|
||||
},
|
||||
{},
|
||||
0
|
||||
);
|
||||
await this.post(`/command`, {
|
||||
name: commandName,
|
||||
...options,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`[${this.apiName}] Failed to run command: ${e.message}`);
|
||||
}
|
||||
|
||||
@@ -303,10 +303,10 @@ class SonarrAPI extends ServarrBase<{
|
||||
});
|
||||
|
||||
try {
|
||||
await this.runCommand('MissingEpisodeSearch', { seriesId });
|
||||
await this.runCommand('SeriesSearch', { seriesId });
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while executing Sonarr missing episode search.',
|
||||
'Something went wrong while executing Sonarr series search.',
|
||||
{
|
||||
label: 'Sonarr API',
|
||||
errorMessage: e.message,
|
||||
|
||||
@@ -95,7 +95,6 @@ interface DiscoverTvOptions {
|
||||
sortBy?: SortOptions;
|
||||
watchRegion?: string;
|
||||
watchProviders?: string;
|
||||
withStatus?: string; // Returning Series: 0 Planned: 1 In Production: 2 Ended: 3 Cancelled: 4 Pilot: 5
|
||||
}
|
||||
|
||||
class TheMovieDb extends ExternalAPI {
|
||||
@@ -524,7 +523,6 @@ class TheMovieDb extends ExternalAPI {
|
||||
voteCountLte,
|
||||
watchProviders,
|
||||
watchRegion,
|
||||
withStatus,
|
||||
}: DiscoverTvOptions = {}): Promise<TmdbSearchTvResponse> => {
|
||||
try {
|
||||
const defaultFutureDate = new Date(
|
||||
@@ -572,7 +570,6 @@ class TheMovieDb extends ExternalAPI {
|
||||
'vote_count.lte': voteCountLte || '',
|
||||
with_watch_providers: watchProviders || '',
|
||||
watch_region: watchRegion || '',
|
||||
with_status: withStatus || '',
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -2,7 +2,6 @@ export enum ApiErrorCode {
|
||||
InvalidUrl = 'INVALID_URL',
|
||||
InvalidCredentials = 'INVALID_CREDENTIALS',
|
||||
InvalidAuthToken = 'INVALID_AUTH_TOKEN',
|
||||
InvalidEmail = 'INVALID_EMAIL',
|
||||
NotAdmin = 'NOT_ADMIN',
|
||||
SyncErrorGroupedFolders = 'SYNC_ERROR_GROUPED_FOLDERS',
|
||||
SyncErrorNoLibraries = 'SYNC_ERROR_NO_LIBRARIES',
|
||||
|
||||
@@ -16,5 +16,4 @@ export enum MediaStatus {
|
||||
PROCESSING,
|
||||
PARTIALLY_AVAILABLE,
|
||||
AVAILABLE,
|
||||
BLACKLISTED,
|
||||
}
|
||||
|
||||
@@ -4,8 +4,3 @@ export enum MediaServerType {
|
||||
EMBY,
|
||||
NOT_CONFIGURED,
|
||||
}
|
||||
|
||||
export enum ServerType {
|
||||
JELLYFIN = 'Jellyfin',
|
||||
EMBY = 'Emby',
|
||||
}
|
||||
|
||||
@@ -2,5 +2,4 @@ export enum UserType {
|
||||
PLEX = 1,
|
||||
LOCAL = 2,
|
||||
JELLYFIN = 3,
|
||||
EMBY = 4,
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import { MediaStatus, type MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import { User } from '@server/entity/User';
|
||||
import type { BlacklistItem } from '@server/interfaces/api/blacklistInterfaces';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import type { ZodNumber, ZodOptional, ZodString } from 'zod';
|
||||
|
||||
@Entity()
|
||||
@Unique(['tmdbId'])
|
||||
export class Blacklist implements BlacklistItem {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column({ type: 'varchar' })
|
||||
public mediaType: MediaType;
|
||||
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
title?: string;
|
||||
|
||||
@Column()
|
||||
@Index()
|
||||
public tmdbId: number;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.id, {
|
||||
eager: true,
|
||||
})
|
||||
user: User;
|
||||
|
||||
@OneToOne(() => Media, (media) => media.blacklist, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public media: Media;
|
||||
|
||||
@CreateDateColumn()
|
||||
public createdAt: Date;
|
||||
|
||||
constructor(init?: Partial<Blacklist>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
|
||||
public static async addToBlacklist({
|
||||
blacklistRequest,
|
||||
}: {
|
||||
blacklistRequest: {
|
||||
mediaType: MediaType;
|
||||
title?: ZodOptional<ZodString>['_output'];
|
||||
tmdbId: ZodNumber['_output'];
|
||||
};
|
||||
}): Promise<void> {
|
||||
const blacklist = new this({
|
||||
...blacklistRequest,
|
||||
});
|
||||
|
||||
const mediaRepository = getRepository(Media);
|
||||
let media = await mediaRepository.findOne({
|
||||
where: {
|
||||
tmdbId: blacklistRequest.tmdbId,
|
||||
},
|
||||
});
|
||||
|
||||
const blacklistRepository = getRepository(this);
|
||||
|
||||
await blacklistRepository.save(blacklist);
|
||||
|
||||
if (!media) {
|
||||
media = new Media({
|
||||
tmdbId: blacklistRequest.tmdbId,
|
||||
status: MediaStatus.BLACKLISTED,
|
||||
status4k: MediaStatus.BLACKLISTED,
|
||||
mediaType: blacklistRequest.mediaType,
|
||||
blacklist: blacklist,
|
||||
});
|
||||
|
||||
await mediaRepository.save(media);
|
||||
} else {
|
||||
media.blacklist = blacklist;
|
||||
media.status = MediaStatus.BLACKLISTED;
|
||||
media.status4k = MediaStatus.BLACKLISTED;
|
||||
|
||||
await mediaRepository.save(media);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import { MediaStatus, MediaType } from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { Blacklist } from '@server/entity/Blacklist';
|
||||
import type { User } from '@server/entity/User';
|
||||
import { Watchlist } from '@server/entity/Watchlist';
|
||||
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
Entity,
|
||||
Index,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
@@ -68,7 +66,7 @@ class Media {
|
||||
|
||||
try {
|
||||
const media = await mediaRepository.findOne({
|
||||
where: { tmdbId: id, mediaType: mediaType },
|
||||
where: { tmdbId: id, mediaType },
|
||||
relations: { requests: true, issues: true },
|
||||
});
|
||||
|
||||
@@ -118,11 +116,6 @@ class Media {
|
||||
@OneToMany(() => Issue, (issue) => issue.media, { cascade: true })
|
||||
public issues: Issue[];
|
||||
|
||||
@OneToOne(() => Blacklist, (blacklist) => blacklist.media, {
|
||||
eager: true,
|
||||
})
|
||||
public blacklist: Blacklist;
|
||||
|
||||
@CreateDateColumn()
|
||||
public createdAt: Date;
|
||||
|
||||
@@ -218,10 +211,9 @@ class Media {
|
||||
}
|
||||
} else {
|
||||
const pageName =
|
||||
getSettings().main.mediaServerType == MediaServerType.EMBY
|
||||
? 'item'
|
||||
: 'details';
|
||||
process.env.JELLYFIN_TYPE === 'emby' ? 'item' : 'details';
|
||||
const { serverId, externalHostname } = getSettings().jellyfin;
|
||||
|
||||
const jellyfinHost =
|
||||
externalHostname && externalHostname.length > 0
|
||||
? externalHostname
|
||||
|
||||
@@ -40,7 +40,6 @@ export class RequestPermissionError extends Error {}
|
||||
export class QuotaRestrictedError extends Error {}
|
||||
export class DuplicateMediaRequestError extends Error {}
|
||||
export class NoSeasonsAvailableError extends Error {}
|
||||
export class BlacklistedMediaError extends Error {}
|
||||
|
||||
type MediaRequestOptions = {
|
||||
isAutoRequest?: boolean;
|
||||
@@ -144,16 +143,6 @@ export class MediaRequest {
|
||||
mediaType: requestBody.mediaType,
|
||||
});
|
||||
} else {
|
||||
if (media.status === MediaStatus.BLACKLISTED) {
|
||||
logger.warn('Request for media blocked due to being blacklisted', {
|
||||
tmdbId: tmdbMedia.id,
|
||||
mediaType: requestBody.mediaType,
|
||||
label: 'Media Request',
|
||||
});
|
||||
|
||||
throw new BlacklistedMediaError('This media is blacklisted.');
|
||||
}
|
||||
|
||||
if (media.status === MediaStatus.UNKNOWN && !requestBody.is4k) {
|
||||
media.status = MediaStatus.PENDING;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ app
|
||||
},
|
||||
store: new TypeormStore({
|
||||
cleanupLimit: 2,
|
||||
ttl: 60 * 60 * 24 * 30,
|
||||
ttl: 1000 * 60 * 60 * 24 * 30,
|
||||
}).connect(sessionRespository) as Store,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { User } from '@server/entity/User';
|
||||
import type { PaginatedResponse } from '@server/interfaces/api/common';
|
||||
|
||||
export interface BlacklistItem {
|
||||
tmdbId: number;
|
||||
mediaType: 'movie' | 'tv';
|
||||
title?: string;
|
||||
createdAt?: Date;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface BlacklistResultsResponse extends PaginatedResponse {
|
||||
results: BlacklistItem[];
|
||||
}
|
||||
@@ -20,7 +20,6 @@ export interface DownloadingItem {
|
||||
timeLeft: string;
|
||||
estimatedCompletionTime: Date;
|
||||
title: string;
|
||||
downloadId: string;
|
||||
episode?: EpisodeNumberResult;
|
||||
}
|
||||
|
||||
@@ -85,7 +84,6 @@ class DownloadTracker {
|
||||
});
|
||||
|
||||
try {
|
||||
await radarr.refreshMonitoredDownloads();
|
||||
const queueItems = await radarr.getQueue();
|
||||
|
||||
this.radarrServers[server.id] = queueItems.map((item) => ({
|
||||
@@ -97,7 +95,6 @@ class DownloadTracker {
|
||||
status: item.status,
|
||||
timeLeft: item.timeleft,
|
||||
title: item.title,
|
||||
downloadId: item.downloadId,
|
||||
}));
|
||||
|
||||
if (queueItems.length > 0) {
|
||||
@@ -163,7 +160,6 @@ class DownloadTracker {
|
||||
});
|
||||
|
||||
try {
|
||||
await sonarr.refreshMonitoredDownloads();
|
||||
const queueItems = await sonarr.getQueue();
|
||||
|
||||
this.sonarrServers[server.id] = queueItems.map((item) => ({
|
||||
@@ -176,7 +172,6 @@ class DownloadTracker {
|
||||
timeLeft: item.timeleft,
|
||||
title: item.title,
|
||||
episode: item.episode,
|
||||
downloadId: item.downloadId,
|
||||
}));
|
||||
|
||||
if (queueItems.length > 0) {
|
||||
|
||||
@@ -190,7 +190,7 @@ class ImageProxy {
|
||||
|
||||
await promises.rm(directory, { recursive: true });
|
||||
|
||||
logger.info(`Cleared ${files[0]} from cache 'avatar'`, {
|
||||
logger.info(`Cleared ${files[0]} image from cache 'avatar'`, {
|
||||
label: 'Image Cache',
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -27,8 +27,6 @@ export enum Permission {
|
||||
AUTO_REQUEST_TV = 33554432,
|
||||
RECENT_VIEW = 67108864,
|
||||
WATCHLIST_VIEW = 134217728,
|
||||
MANAGE_BLACKLIST = 268435456,
|
||||
VIEW_BLACKLIST = 1073741824,
|
||||
}
|
||||
|
||||
export interface PermissionCheckOptions {
|
||||
|
||||
@@ -567,10 +567,7 @@ class JellyfinScanner {
|
||||
public async run(): Promise<void> {
|
||||
const settings = getSettings();
|
||||
|
||||
if (
|
||||
settings.main.mediaServerType != MediaServerType.JELLYFIN &&
|
||||
settings.main.mediaServerType != MediaServerType.EMBY
|
||||
) {
|
||||
if (settings.main.mediaServerType != MediaServerType.JELLYFIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -611,11 +611,7 @@ class Settings {
|
||||
}
|
||||
|
||||
private generateApiKey(): string {
|
||||
if (process.env.API_KEY) {
|
||||
return process.env.API_KEY;
|
||||
} else {
|
||||
return Buffer.from(`${Date.now()}${randomUUID()}`).toString('base64');
|
||||
}
|
||||
return Buffer.from(`${Date.now()}${randomUUID()}`).toString('base64');
|
||||
}
|
||||
|
||||
private generateVapidKeys(force = false): void {
|
||||
@@ -652,12 +648,6 @@ class Settings {
|
||||
|
||||
this.data = merge(this.data, parsedJson);
|
||||
|
||||
if (process.env.API_KEY) {
|
||||
if (this.main.apiKey != process.env.API_KEY) {
|
||||
this.main.apiKey = process.env.API_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
this.save();
|
||||
}
|
||||
return this;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { AllSettings } from '@server/lib/settings';
|
||||
|
||||
const migrateHostname = (settings: any): AllSettings => {
|
||||
const oldMediaServerType = settings.main.mediaServerType;
|
||||
if (
|
||||
oldMediaServerType === MediaServerType.JELLYFIN &&
|
||||
process.env.JELLYFIN_TYPE === 'emby'
|
||||
) {
|
||||
settings.main.mediaServerType = MediaServerType.EMBY;
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
export default migrateHostname;
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddBlacklist1699901142442 implements MigrationInterface {
|
||||
name = 'AddBlacklist1699901142442';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "blacklist" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "title" varchar, "tmdbId" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')),"userId" integer, "mediaId" integer,CONSTRAINT "UQ_6bbafa28411e6046421991ea21c" UNIQUE ("tmdbId", "userId"))`
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_6bbafa28411e6046421991ea21" ON "blacklist" ("tmdbId") `
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "blacklist"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_6bbafa28411e6046421991ea21"`);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import JellyfinAPI from '@server/api/jellyfin';
|
||||
import PlexTvAPI from '@server/api/plextv';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import { MediaServerType, ServerType } from '@server/constants/server';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { UserType } from '@server/constants/user';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { User } from '@server/entity/User';
|
||||
@@ -228,20 +228,15 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
urlBase?: string;
|
||||
useSsl?: boolean;
|
||||
email?: string;
|
||||
serverType?: number;
|
||||
};
|
||||
|
||||
//Make sure jellyfin login is enabled, but only if jellyfin && Emby is not already configured
|
||||
//Make sure jellyfin login is enabled, but only if jellyfin is not already configured
|
||||
if (
|
||||
settings.main.mediaServerType !== MediaServerType.JELLYFIN &&
|
||||
settings.main.mediaServerType !== MediaServerType.EMBY &&
|
||||
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED &&
|
||||
settings.jellyfin.ip !== ''
|
||||
settings.main.mediaServerType != MediaServerType.NOT_CONFIGURED
|
||||
) {
|
||||
return res.status(500).json({ error: 'Jellyfin login is disabled' });
|
||||
}
|
||||
|
||||
if (!body.username) {
|
||||
} else if (!body.username) {
|
||||
return res.status(500).json({ error: 'You must provide an username' });
|
||||
} else if (settings.jellyfin.ip !== '' && body.hostname) {
|
||||
return res
|
||||
@@ -279,8 +274,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
}
|
||||
|
||||
// First we need to attempt to log the user in to jellyfin
|
||||
const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId);
|
||||
|
||||
const jellyfinserver = new JellyfinAPI(hostname, undefined, deviceId);
|
||||
const jellyfinHost =
|
||||
externalHostname && externalHostname.length > 0
|
||||
? externalHostname
|
||||
@@ -324,49 +318,22 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
);
|
||||
|
||||
// User doesn't exist, and there are no users in the database, we'll create the user
|
||||
// with admin permissions
|
||||
switch (body.serverType) {
|
||||
case MediaServerType.EMBY:
|
||||
settings.main.mediaServerType = MediaServerType.EMBY;
|
||||
user = new User({
|
||||
email: body.email || account.User.Name,
|
||||
jellyfinUsername: account.User.Name,
|
||||
jellyfinUserId: account.User.Id,
|
||||
jellyfinDeviceId: deviceId,
|
||||
jellyfinAuthToken: account.AccessToken,
|
||||
permissions: Permission.ADMIN,
|
||||
avatar: account.User.PrimaryImageTag
|
||||
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
|
||||
: gravatarUrl(body.email || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
userType: UserType.EMBY,
|
||||
});
|
||||
|
||||
break;
|
||||
case MediaServerType.JELLYFIN:
|
||||
settings.main.mediaServerType = MediaServerType.JELLYFIN;
|
||||
user = new User({
|
||||
email: body.email || account.User.Name,
|
||||
jellyfinUsername: account.User.Name,
|
||||
jellyfinUserId: account.User.Id,
|
||||
jellyfinDeviceId: deviceId,
|
||||
jellyfinAuthToken: account.AccessToken,
|
||||
permissions: Permission.ADMIN,
|
||||
avatar: account.User.PrimaryImageTag
|
||||
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
|
||||
: gravatarUrl(body.email || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
userType: UserType.JELLYFIN,
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new Error('select_server_type');
|
||||
}
|
||||
// with admin permission
|
||||
settings.main.mediaServerType = MediaServerType.JELLYFIN;
|
||||
user = new User({
|
||||
email: body.email || account.User.Name,
|
||||
jellyfinUsername: account.User.Name,
|
||||
jellyfinUserId: account.User.Id,
|
||||
jellyfinDeviceId: deviceId,
|
||||
permissions: Permission.ADMIN,
|
||||
avatar: account.User.PrimaryImageTag
|
||||
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
|
||||
: gravatarUrl(body.email || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
userType: UserType.JELLYFIN,
|
||||
});
|
||||
|
||||
// Create an API key on Jellyfin from this admin user
|
||||
const jellyfinClient = new JellyfinAPI(
|
||||
@@ -395,12 +362,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
logger.info(
|
||||
`Found matching ${
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: ServerType.EMBY
|
||||
? 'Jellyfin'
|
||||
: 'Emby'
|
||||
} user; updating user with ${
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: ServerType.EMBY
|
||||
? 'Jellyfin'
|
||||
: 'Emby'
|
||||
}`,
|
||||
{
|
||||
label: 'API',
|
||||
@@ -426,7 +393,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
const avatarProxy = new ImageProxy('avatar', '');
|
||||
avatarProxy.clearCachedImage(user.avatar);
|
||||
}
|
||||
|
||||
user.avatar = avatar;
|
||||
}
|
||||
user.jellyfinUsername = account.User.Name;
|
||||
@@ -435,6 +401,12 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
user.username = '';
|
||||
}
|
||||
|
||||
// TODO: If JELLYFIN_TYPE is set to 'emby' then set mediaServerType to EMBY
|
||||
// if (process.env.JELLYFIN_TYPE === 'emby') {
|
||||
// settings.main.mediaServerType = MediaServerType.EMBY;
|
||||
// settings.save();
|
||||
// }
|
||||
|
||||
await userRepository.save(user);
|
||||
} else if (!settings.main.newPlexLogin) {
|
||||
logger.warn(
|
||||
@@ -472,12 +444,8 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
userType:
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? UserType.JELLYFIN
|
||||
: UserType.EMBY,
|
||||
userType: UserType.JELLYFIN,
|
||||
});
|
||||
|
||||
//initialize Jellyfin/Emby users with local login
|
||||
const passedExplicitPassword = body.password && body.password.length > 0;
|
||||
if (passedExplicitPassword) {
|
||||
|
||||
@@ -8,7 +8,6 @@ const avatarImageProxy = new ImageProxy('avatar', '');
|
||||
// Proxy avatar images
|
||||
router.get('/*', async (req, res) => {
|
||||
const imagePath = req.url.startsWith('/') ? req.url.slice(1) : req.url;
|
||||
|
||||
try {
|
||||
const imageData = await avatarImageProxy.getImage(imagePath);
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { Blacklist } from '@server/entity/Blacklist';
|
||||
import Media from '@server/entity/Media';
|
||||
import { NotFoundError } from '@server/entity/Watchlist';
|
||||
import type { BlacklistResultsResponse } from '@server/interfaces/api/blacklistInterfaces';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import logger from '@server/logger';
|
||||
import { isAuthenticated } from '@server/middleware/auth';
|
||||
import { Router } from 'express';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
|
||||
const blacklistRoutes = Router();
|
||||
|
||||
export const blacklistAdd = z.object({
|
||||
tmdbId: z.coerce.number(),
|
||||
mediaType: z.nativeEnum(MediaType),
|
||||
title: z.coerce.string().optional(),
|
||||
user: z.coerce.number(),
|
||||
});
|
||||
|
||||
blacklistRoutes.get(
|
||||
'/',
|
||||
isAuthenticated([Permission.MANAGE_BLACKLIST, Permission.VIEW_BLACKLIST], {
|
||||
type: 'or',
|
||||
}),
|
||||
rateLimit({ windowMs: 60 * 1000, max: 50 }),
|
||||
async (req, res, next) => {
|
||||
const pageSize = req.query.take ? Number(req.query.take) : 25;
|
||||
const skip = req.query.skip ? Number(req.query.skip) : 0;
|
||||
const search = (req.query.search as string) ?? '';
|
||||
|
||||
try {
|
||||
let query = getRepository(Blacklist)
|
||||
.createQueryBuilder('blacklist')
|
||||
.leftJoinAndSelect('blacklist.user', 'user');
|
||||
|
||||
if (search.length > 0) {
|
||||
query = query.where('blacklist.title like :title', {
|
||||
title: `%${search}%`,
|
||||
});
|
||||
}
|
||||
|
||||
const [blacklistedItems, itemsCount] = await query
|
||||
.orderBy('blacklist.createdAt', 'DESC')
|
||||
.take(pageSize)
|
||||
.skip(skip)
|
||||
.getManyAndCount();
|
||||
|
||||
return res.status(200).json({
|
||||
pageInfo: {
|
||||
pages: Math.ceil(itemsCount / pageSize),
|
||||
pageSize,
|
||||
results: itemsCount,
|
||||
page: Math.ceil(skip / pageSize) + 1,
|
||||
},
|
||||
results: blacklistedItems,
|
||||
} as BlacklistResultsResponse);
|
||||
} catch (error) {
|
||||
logger.error('Something went wrong while retrieving blacklisted items', {
|
||||
label: 'Blacklist',
|
||||
errorMessage: error.message,
|
||||
});
|
||||
return next({
|
||||
status: 500,
|
||||
message: 'Unable to retrieve blacklisted items.',
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
blacklistRoutes.post(
|
||||
'/',
|
||||
isAuthenticated([Permission.MANAGE_BLACKLIST], {
|
||||
type: 'or',
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const values = blacklistAdd.parse(req.body);
|
||||
|
||||
await Blacklist.addToBlacklist({
|
||||
blacklistRequest: values,
|
||||
});
|
||||
|
||||
return res.status(201).send();
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof QueryFailedError) {
|
||||
switch (error.driverError.errno) {
|
||||
case 19:
|
||||
return next({ status: 412, message: 'Item already blacklisted' });
|
||||
default:
|
||||
logger.warn('Something wrong with data blacklist', {
|
||||
tmdbId: req.body.tmdbId,
|
||||
mediaType: req.body.mediaType,
|
||||
label: 'Blacklist',
|
||||
});
|
||||
return next({ status: 409, message: 'Something wrong' });
|
||||
}
|
||||
}
|
||||
|
||||
return next({ status: 500, message: error.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
blacklistRoutes.delete(
|
||||
'/:id',
|
||||
isAuthenticated([Permission.MANAGE_BLACKLIST], {
|
||||
type: 'or',
|
||||
}),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const blacklisteRepository = getRepository(Blacklist);
|
||||
|
||||
const blacklistItem = await blacklisteRepository.findOneOrFail({
|
||||
where: { tmdbId: Number(req.params.id) },
|
||||
});
|
||||
|
||||
await blacklisteRepository.remove(blacklistItem);
|
||||
|
||||
const mediaRepository = getRepository(Media);
|
||||
|
||||
const mediaItem = await mediaRepository.findOneOrFail({
|
||||
where: { tmdbId: Number(req.params.id) },
|
||||
});
|
||||
|
||||
await mediaRepository.remove(mediaItem);
|
||||
|
||||
return res.status(204).send();
|
||||
} catch (e) {
|
||||
if (e instanceof NotFoundError) {
|
||||
return next({
|
||||
status: 401,
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
return next({ status: 500, message: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default blacklistRoutes;
|
||||
@@ -71,7 +71,6 @@ const QueryFilterOptions = z.object({
|
||||
network: z.coerce.string().optional(),
|
||||
watchProviders: z.coerce.string().optional(),
|
||||
watchRegion: z.coerce.string().optional(),
|
||||
status: z.coerce.string().optional(),
|
||||
});
|
||||
|
||||
export type FilterOptions = z.infer<typeof QueryFilterOptions>;
|
||||
@@ -386,7 +385,6 @@ discoverRoutes.get('/tv', async (req, res, next) => {
|
||||
voteCountLte: query.voteCountLte,
|
||||
watchProviders: query.watchProviders,
|
||||
watchRegion: query.watchRegion,
|
||||
withStatus: query.status,
|
||||
});
|
||||
|
||||
const media = await Media.getRelatedMedia(
|
||||
|
||||
@@ -23,7 +23,6 @@ import restartFlag from '@server/utils/restartFlag';
|
||||
import { isPerson } from '@server/utils/typeHelpers';
|
||||
import { Router } from 'express';
|
||||
import authRoutes from './auth';
|
||||
import blacklistRoutes from './blacklist';
|
||||
import collectionRoutes from './collection';
|
||||
import discoverRoutes, { createTmdbWithRegionLanguage } from './discover';
|
||||
import issueRoutes from './issue';
|
||||
@@ -145,7 +144,6 @@ router.use('/search', isAuthenticated(), searchRoutes);
|
||||
router.use('/discover', isAuthenticated(), discoverRoutes);
|
||||
router.use('/request', isAuthenticated(), requestRoutes);
|
||||
router.use('/watchlist', isAuthenticated(), watchlistRoutes);
|
||||
router.use('/blacklist', isAuthenticated(), blacklistRoutes);
|
||||
router.use('/movie', isAuthenticated(), movieRoutes);
|
||||
router.use('/tv', isAuthenticated(), tvRoutes);
|
||||
router.use('/media', isAuthenticated(), mediaRoutes);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
import {
|
||||
BlacklistedMediaError,
|
||||
DuplicateMediaRequestError,
|
||||
MediaRequest,
|
||||
NoSeasonsAvailableError,
|
||||
@@ -244,8 +243,6 @@ requestRoutes.post<never, MediaRequest, MediaRequestBody>(
|
||||
return next({ status: 409, message: error.message });
|
||||
case NoSeasonsAvailableError:
|
||||
return next({ status: 202, message: error.message });
|
||||
case BlacklistedMediaError:
|
||||
return next({ status: 403, message: error.message });
|
||||
default:
|
||||
return next({ status: 500, message: error.message });
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import JellyfinAPI from '@server/api/jellyfin';
|
||||
import PlexTvAPI from '@server/api/plextv';
|
||||
import TautulliAPI from '@server/api/tautulli';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { UserType } from '@server/constants/user';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import Media from '@server/entity/Media';
|
||||
@@ -551,10 +550,7 @@ router.post(
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
userType:
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? UserType.JELLYFIN
|
||||
: UserType.EMBY,
|
||||
userType: UserType.JELLYFIN,
|
||||
});
|
||||
|
||||
await userRepository.save(newUser);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { User } from '@server/entity/User';
|
||||
import { UserSettings } from '@server/entity/UserSettings';
|
||||
@@ -10,7 +9,6 @@ import { Permission } from '@server/lib/permissions';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { isAuthenticated } from '@server/middleware/auth';
|
||||
import { ApiError } from '@server/types/error';
|
||||
import { Router } from 'express';
|
||||
import { canMakePermissionsChange } from '.';
|
||||
|
||||
@@ -100,18 +98,10 @@ userSettingsRoutes.post<
|
||||
}
|
||||
|
||||
user.username = req.body.username;
|
||||
const oldEmail = user.email;
|
||||
if (user.jellyfinUsername) {
|
||||
user.email = req.body.email || user.jellyfinUsername || user.email;
|
||||
}
|
||||
|
||||
const existingUser = await userRepository.findOne({
|
||||
where: { email: user.email },
|
||||
});
|
||||
if (oldEmail !== user.email && existingUser) {
|
||||
throw new ApiError(400, ApiErrorCode.InvalidEmail);
|
||||
}
|
||||
|
||||
// Update quota values only if the user has the correct permissions
|
||||
if (
|
||||
!user.hasPermission(Permission.MANAGE_USERS) &&
|
||||
@@ -155,14 +145,7 @@ userSettingsRoutes.post<
|
||||
email: savedUser.email,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.errorCode) {
|
||||
return next({
|
||||
status: e.statusCode,
|
||||
message: e.errorCode,
|
||||
});
|
||||
} else {
|
||||
return next({ status: 500, message: e.message });
|
||||
}
|
||||
next({ status: 500, message: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
viewBox="0 0 712.60077 712.5481"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
style="opacity:0;fill:#ffffff;stroke-width:4.12543"
|
||||
id="rect249"
|
||||
width="712.60077"
|
||||
height="712.5481"
|
||||
x="-0.00071160076"
|
||||
y="2.0223413e-11" />
|
||||
<rect
|
||||
style="fill:#ffffff"
|
||||
id="rect289"
|
||||
width="230.18982"
|
||||
height="229.82355"
|
||||
x="241.20476"
|
||||
y="241.36227" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="matrix(0.70249853,0,0,0.70249853,88.770447,96.84571)">
|
||||
<path
|
||||
id="path3427"
|
||||
d="m 327.06546,642.18589 c -45.39663,-45.86009 -82.73776,-83.3683 -82.98029,-83.3516 -0.24253,0.0167 -7.23324,6.65975 -15.53493,14.7623 l -15.09396,14.73193 -40.13624,-40.38805 C 151.24511,525.72706 108.73555,482.86504 78.854363,452.69158 l -54.329437,-54.86086 83.720394,-82.90796 83.72039,-82.90797 -15.19316,-15.20441 -15.19315,-15.20443 95.18008,-94.29313 c 52.34904,-51.86121 95.35849,-94.293118 95.57653,-94.293118 0.21805,0 37.39519,37.357576 82.61589,83.016832 45.22068,45.659256 82.53772,83.131956 82.92673,83.272666 0.38901,0.14071 7.46336,-6.49498 15.72077,-14.746 l 15.01348,-15.00184 7.14591,7.19902 c 73.95232,74.50189 181.50599,183.56427 181.36678,183.9109 -0.10065,0.25064 -37.54056,37.44106 -83.19981,82.64536 -45.65926,45.2043 -83.10802,82.41429 -83.21946,82.68884 -0.11145,0.27456 6.50478,7.34753 14.70272,15.71771 l 14.90534,15.21851 -15.3888,15.28883 c -21.09609,20.95904 -162.95155,161.27018 -169.79551,167.947 l -5.52526,5.39033 z m 89.8523,-204.1566 c 64.84836,-37.53366 117.81919,-68.54793 117.71294,-68.92058 -0.15927,-0.55862 -233.55022,-136.2489 -236.27084,-137.3646 -0.68441,-0.28068 -0.85761,27.45642 -0.85761,137.33982 0,99.83563 0.20749,137.62237 0.75471,137.43996 0.41509,-0.13837 53.81245,-30.96093 118.6608,-68.4946 z"
|
||||
style="fill:#52b54b;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,131 +1,46 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" width="100%" viewBox="0 0 617 188" enable-background="new 0 0 617 188" xml:space="preserve">
|
||||
<path fill="#52B54B" opacity="1.000000" stroke="none" d="
|
||||
M89.583336,1.000000
|
||||
C90.189529,1.685005 90.166168,2.574803 90.599510,3.025271
|
||||
C103.718315,16.662701 116.882103,30.256845 129.948212,43.764053
|
||||
C130.577850,43.523941 130.916519,43.491173 131.111343,43.306595
|
||||
C138.657471,36.157455 138.655273,36.156090 146.005478,43.505203
|
||||
C159.538589,57.036308 173.016449,70.623535 186.654617,84.047913
|
||||
C189.264145,86.616562 189.414017,88.253456 186.716782,90.895164
|
||||
C174.709808,102.655037 162.893280,114.609337 151.008514,126.493958
|
||||
C146.073502,131.428925 146.076691,131.427155 151.017944,136.523712
|
||||
C151.698944,137.226120 152.340485,137.966812 153.259171,138.973434
|
||||
C151.947098,140.380035 150.766312,141.712204 149.516266,142.975861
|
||||
C134.544815,158.110641 119.563087,173.235260 104.792023,188.681274
|
||||
C103.611107,189.000000 102.222221,189.000000 100.624634,188.681274
|
||||
C86.361732,174.796494 72.307518,161.230438 57.702755,147.132965
|
||||
C56.157101,149.136856 54.135899,151.757263 51.994804,154.533112
|
||||
C35.932781,138.457108 20.569420,123.048477 5.141897,107.704361
|
||||
C3.997114,106.565773 2.391420,105.890610 1.000000,105.000000
|
||||
C1.000000,103.611107 1.000000,102.222221 1.318741,100.624641
|
||||
C15.203506,86.361694 28.769531,72.307434 42.867004,57.702602
|
||||
C40.863205,56.156994 38.242813,54.135792 35.425343,51.962570
|
||||
C51.518696,35.908516 66.939468,20.557360 82.295547,5.141749
|
||||
C83.434830,3.998048 84.109390,2.391417 85.000000,0.999999
|
||||
C86.388893,1.000000 87.777779,1.000000 89.583336,1.000000
|
||||
M73.196465,79.500702
|
||||
C73.196465,96.254150 73.196465,113.007599 73.196465,130.872055
|
||||
C94.273178,118.764557 114.417175,107.192863 135.221664,95.241745
|
||||
C114.247169,83.251732 94.091187,71.729622 73.196594,59.785294
|
||||
C73.196594,66.631348 73.196594,72.566254 73.196465,79.500702
|
||||
z" />
|
||||
<path fill="#FDFDFD" opacity="1.000000" stroke="none" d="
|
||||
M618.000000,60.571537
|
||||
C617.004395,62.042580 615.613281,62.912964 615.073181,64.153824
|
||||
C608.143372,80.073746 601.328613,96.043816 594.498169,112.006920
|
||||
C586.973572,129.592300 579.343018,147.133865 571.999390,164.794601
|
||||
C568.632385,172.892075 568.893372,173.002594 560.133972,172.999832
|
||||
C555.470825,172.998367 550.807617,172.994385 546.144592,172.969360
|
||||
C545.841980,172.967712 545.540466,172.775543 544.836609,172.534256
|
||||
C548.592896,163.531219 551.714905,154.222061 556.286133,145.689255
|
||||
C559.733765,139.253830 559.138794,134.062668 556.454224,127.695969
|
||||
C546.360352,103.757523 536.803345,79.592712 526.837830,55.000847
|
||||
C534.817078,55.000847 542.437622,54.725182 550.003540,55.244331
|
||||
C551.436218,55.342628 553.169678,58.412052 553.885010,60.423309
|
||||
C558.720520,74.018005 563.307556,87.700912 568.003784,101.345413
|
||||
C569.107483,104.551987 570.321045,107.720764 571.976196,112.255157
|
||||
C573.889587,107.365631 575.415283,103.375916 577.007935,99.413109
|
||||
C582.693298,85.266724 588.344238,71.105591 594.218018,57.037624
|
||||
C594.650513,56.001743 596.734497,55.132927 598.079773,55.089733
|
||||
C604.401855,54.886726 610.734131,54.999401 617.531372,54.999699
|
||||
C618.000000,56.714359 618.000000,58.428715 618.000000,60.571537
|
||||
z" />
|
||||
<path fill="#FDFDFD" opacity="1.000000" stroke="none" d="
|
||||
M430.000122,99.002235
|
||||
C430.000122,112.477097 430.000122,125.452438 430.000122,138.713440
|
||||
C423.048126,138.713440 416.308685,138.713440 408.999878,138.713440
|
||||
C408.999878,129.350739 409.120758,119.916939 408.962219,110.487823
|
||||
C408.832153,102.753624 409.088898,94.909142 407.866791,87.324188
|
||||
C406.440887,78.474220 401.302399,74.201607 394.304291,74.000290
|
||||
C387.617249,73.807938 380.317963,79.297188 378.047363,86.438652
|
||||
C377.420715,88.409592 377.055725,90.550858 377.044647,92.616508
|
||||
C376.962494,107.913475 377.000122,123.211082 377.000122,138.753479
|
||||
C369.630646,138.753479 362.559692,138.753479 354.999878,138.753479
|
||||
C354.999878,123.256836 355.044769,107.816956 354.977661,92.377571
|
||||
C354.951050,86.251518 352.748199,80.799278 347.911346,77.066116
|
||||
C339.239685,70.373154 327.811401,74.635170 324.084412,84.471092
|
||||
C322.793915,87.876816 322.147491,91.713402 322.090881,95.366882
|
||||
C321.868958,109.685005 322.000122,124.008591 322.000122,138.665009
|
||||
C314.823853,138.665009 307.760773,138.665009 300.346558,138.665009
|
||||
C300.346558,111.006645 300.346558,83.281189 300.346558,55.001301
|
||||
C306.163818,55.001301 312.104645,54.855133 318.024780,55.139343
|
||||
C319.060455,55.189068 320.450378,56.891682 320.882477,58.112110
|
||||
C321.380768,59.519447 320.998291,61.238617 320.998291,64.136040
|
||||
C328.715179,54.407440 338.407898,52.804527 348.408875,54.206123
|
||||
C356.403381,55.326527 361.770447,57.638248 366.682190,66.544373
|
||||
C372.325470,62.972542 377.601440,58.269657 383.771973,56.014080
|
||||
C396.273407,51.444298 408.602570,53.673611 419.067657,61.818150
|
||||
C426.629364,67.703125 429.037811,76.770744 429.932556,86.011482
|
||||
C430.332214,90.138710 430.000122,94.336792 430.000122,99.002235
|
||||
z" />
|
||||
<path fill="#FDFDFD" opacity="1.000000" stroke="none" d="
|
||||
M462.000427,35.006332
|
||||
C462.000427,44.815434 462.000427,54.126144 462.000427,64.132019
|
||||
C468.844696,58.319965 476.100769,54.654530 484.669739,53.656227
|
||||
C496.686127,52.256294 507.565582,54.979622 516.927185,62.503853
|
||||
C534.236755,76.416115 535.360107,106.231667 523.651062,123.341644
|
||||
C516.745056,133.433182 506.539673,139.485458 493.555267,140.111023
|
||||
C483.836304,140.579254 474.670624,139.889420 466.610413,133.799713
|
||||
C465.039795,132.613068 463.390686,131.530289 461.957214,130.525391
|
||||
C461.633789,132.375305 461.105469,135.397171 460.522095,138.733841
|
||||
C454.446686,138.733841 448.017822,138.733841 441.292542,138.733841
|
||||
C441.292542,99.722672 441.292542,60.652122 441.292542,21.290209
|
||||
C447.943787,21.290209 454.684204,21.290209 462.000427,21.290209
|
||||
C462.000427,25.636984 462.000427,30.072460 462.000427,35.006332
|
||||
M480.890228,119.974937
|
||||
C485.426086,119.681152 490.365997,120.444260 494.421356,118.893707
|
||||
C506.182587,114.396866 510.858643,104.919495 509.036591,92.234833
|
||||
C507.422546,80.997993 496.539307,71.772278 483.551605,73.864754
|
||||
C469.724976,76.092384 464.376770,85.538391 463.152863,96.752327
|
||||
C462.120667,106.209480 469.961761,116.189537 480.890228,119.974937
|
||||
z" />
|
||||
<path fill="#FDFDFD" opacity="1.000000" stroke="none" d="
|
||||
M234.797928,54.654831
|
||||
C244.856339,52.605957 254.504562,52.040043 264.239868,54.923946
|
||||
C279.600891,59.474377 286.402191,68.163963 289.768585,81.937614
|
||||
C291.530579,89.146889 290.954620,96.927589 291.469940,105.005005
|
||||
C269.550385,105.005005 248.375092,105.005005 227.094437,105.005005
|
||||
C229.577957,116.288628 239.741562,120.764336 248.594757,121.034813
|
||||
C256.790771,121.285217 264.390472,119.882645 271.081848,114.731178
|
||||
C271.774902,114.197632 273.962708,114.659111 274.786041,115.402222
|
||||
C278.726318,118.958458 282.435333,122.770882 286.509888,126.770363
|
||||
C281.309174,132.968170 274.787445,135.946014 267.542938,138.175064
|
||||
C253.746231,142.420120 240.209259,142.317459 227.237503,135.935410
|
||||
C212.712891,128.789368 205.730453,116.523628 204.973831,100.473404
|
||||
C204.537735,91.222557 205.503754,82.283119 210.008469,74.017265
|
||||
C215.396210,64.131088 223.372589,57.511646 234.797928,54.654831
|
||||
M266.971497,78.708908
|
||||
C259.384399,70.789909 249.920425,70.480316 240.489548,73.410858
|
||||
C234.405487,75.301414 229.437546,79.631561 227.800247,86.722244
|
||||
C242.152313,86.722244 256.002747,86.722244 270.947815,86.722244
|
||||
C269.410950,83.870155 268.228943,81.676651 266.971497,78.708908
|
||||
z" />
|
||||
<path fill="#FCFEFC" opacity="1.000000" stroke="none" d="
|
||||
M73.196533,79.000931
|
||||
C73.196594,72.566254 73.196594,66.631348 73.196594,59.785294
|
||||
C94.091187,71.729622 114.247169,83.251732 135.221664,95.241745
|
||||
C114.417175,107.192863 94.273178,118.764557 73.196465,130.872055
|
||||
C73.196465,113.007599 73.196465,96.254150 73.196533,79.000931
|
||||
z" />
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
viewBox="0 0 712.60077 712.5481"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<rect
|
||||
style="opacity:0;fill:#ffffff;stroke-width:4.12543"
|
||||
id="rect249"
|
||||
width="712.60077"
|
||||
height="712.5481"
|
||||
x="-0.00071160076"
|
||||
y="2.0223413e-11" />
|
||||
<rect
|
||||
style="fill:#ffffff"
|
||||
id="rect289"
|
||||
width="230.18982"
|
||||
height="229.82355"
|
||||
x="241.20476"
|
||||
y="241.36227" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="matrix(0.70249853,0,0,0.70249853,88.770447,96.84571)">
|
||||
<path
|
||||
id="path3427"
|
||||
d="m 327.06546,642.18589 c -45.39663,-45.86009 -82.73776,-83.3683 -82.98029,-83.3516 -0.24253,0.0167 -7.23324,6.65975 -15.53493,14.7623 l -15.09396,14.73193 -40.13624,-40.38805 C 151.24511,525.72706 108.73555,482.86504 78.854363,452.69158 l -54.329437,-54.86086 83.720394,-82.90796 83.72039,-82.90797 -15.19316,-15.20441 -15.19315,-15.20443 95.18008,-94.29313 c 52.34904,-51.86121 95.35849,-94.293118 95.57653,-94.293118 0.21805,0 37.39519,37.357576 82.61589,83.016832 45.22068,45.659256 82.53772,83.131956 82.92673,83.272666 0.38901,0.14071 7.46336,-6.49498 15.72077,-14.746 l 15.01348,-15.00184 7.14591,7.19902 c 73.95232,74.50189 181.50599,183.56427 181.36678,183.9109 -0.10065,0.25064 -37.54056,37.44106 -83.19981,82.64536 -45.65926,45.2043 -83.10802,82.41429 -83.21946,82.68884 -0.11145,0.27456 6.50478,7.34753 14.70272,15.71771 l 14.90534,15.21851 -15.3888,15.28883 c -21.09609,20.95904 -162.95155,161.27018 -169.79551,167.947 l -5.52526,5.39033 z m 89.8523,-204.1566 c 64.84836,-37.53366 117.81919,-68.54793 117.71294,-68.92058 -0.15927,-0.55862 -233.55022,-136.2489 -236.27084,-137.3646 -0.68441,-0.28068 -0.85761,27.45642 -0.85761,137.33982 0,99.83563 0.20749,137.62237 0.75471,137.43996 0.41509,-0.13837 53.81245,-30.96093 118.6608,-68.4946 z"
|
||||
style="fill:#52b54b;fill-opacity:1;stroke:none" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -1,417 +0,0 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import ConfirmButton from '@app/components/Common/ConfirmButton';
|
||||
import Header from '@app/components/Common/Header';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import useDebouncedState from '@app/hooks/useDebouncedState';
|
||||
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import Error from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
MagnifyingGlassIcon,
|
||||
TrashIcon,
|
||||
} from '@heroicons/react/24/solid';
|
||||
import type {
|
||||
BlacklistItem,
|
||||
BlacklistResultsResponse,
|
||||
} from '@server/interfaces/api/blacklistInterfaces';
|
||||
import type { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { FormattedRelativeTime, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const messages = defineMessages('components.Blacklist', {
|
||||
blacklistsettings: 'Blacklist Settings',
|
||||
blacklistSettingsDescription: 'Manage blacklisted media.',
|
||||
mediaName: 'Name',
|
||||
mediaType: 'Type',
|
||||
mediaTmdbId: 'tmdb Id',
|
||||
blacklistdate: 'date',
|
||||
blacklistedby: '{date} by {user}',
|
||||
blacklistNotFoundError: '<strong>{title}</strong> is not blacklisted.',
|
||||
});
|
||||
|
||||
const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
|
||||
return (movie as MovieDetails).title !== undefined;
|
||||
};
|
||||
|
||||
const Blacklist = () => {
|
||||
const [currentPageSize, setCurrentPageSize] = useState<number>(10);
|
||||
const [searchFilter, debouncedSearchFilter, setSearchFilter] =
|
||||
useDebouncedState('');
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
|
||||
const page = router.query.page ? Number(router.query.page) : 1;
|
||||
const pageIndex = page - 1;
|
||||
const updateQueryParams = useUpdateQueryParams({ page: page.toString() });
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
mutate: revalidate,
|
||||
} = useSWR<BlacklistResultsResponse>(
|
||||
`/api/v1/blacklist/?take=${currentPageSize}
|
||||
&skip=${pageIndex * currentPageSize}
|
||||
${debouncedSearchFilter ? `&search=${debouncedSearchFilter}` : ''}`,
|
||||
{
|
||||
refreshInterval: 0,
|
||||
revalidateOnFocus: false,
|
||||
}
|
||||
);
|
||||
|
||||
// check if there's no data and no errors in the table
|
||||
// so as to show a spinner inside the table and not refresh the whole component
|
||||
if (!data && error) {
|
||||
return <Error statusCode={500} />;
|
||||
}
|
||||
|
||||
const searchItem = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
// Remove the "page" query param from the URL
|
||||
// so that the "skip" query param on line 62 is empty
|
||||
// and the search returns results without skipping items
|
||||
if (router.query.page) router.replace(router.basePath);
|
||||
|
||||
setSearchFilter(e.target.value as string);
|
||||
};
|
||||
|
||||
const hasNextPage = data && data.pageInfo.pages > pageIndex + 1;
|
||||
const hasPrevPage = pageIndex > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={[intl.formatMessage(globalMessages.blacklist)]} />
|
||||
<Header>{intl.formatMessage(globalMessages.blacklist)}</Header>
|
||||
|
||||
<div className="mt-2 flex flex-grow flex-col sm:flex-grow-0 sm:flex-row sm:justify-end">
|
||||
<div className="mb-2 flex flex-grow sm:mb-0 sm:mr-2 md:flex-grow-0">
|
||||
<span className="inline-flex cursor-default items-center rounded-l-md border border-r-0 border-gray-500 bg-gray-800 px-3 text-sm text-gray-100">
|
||||
<MagnifyingGlassIcon className="h-6 w-6" />
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
className="rounded-r-only"
|
||||
value={searchFilter}
|
||||
onChange={(e) => searchItem(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!data ? (
|
||||
<LoadingSpinner />
|
||||
) : data.results.length === 0 ? (
|
||||
<div className="flex w-full flex-col items-center justify-center py-24 text-white">
|
||||
<span className="text-2xl text-gray-400">
|
||||
{intl.formatMessage(globalMessages.noresults)}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
data.results.map((item: BlacklistItem) => {
|
||||
return (
|
||||
<div className="py-2" key={`request-list-${item.tmdbId}`}>
|
||||
<BlacklistedItem item={item} revalidateList={revalidate} />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
|
||||
<div className="actions">
|
||||
<nav
|
||||
className="mb-3 flex flex-col items-center space-y-3 sm:flex-row sm:space-y-0"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<div className="hidden lg:flex lg:flex-1">
|
||||
<p className="text-sm">
|
||||
{data &&
|
||||
(data?.results.length ?? 0) > 0 &&
|
||||
intl.formatMessage(globalMessages.showingresults, {
|
||||
from: pageIndex * currentPageSize + 1,
|
||||
to:
|
||||
data.results.length < currentPageSize
|
||||
? pageIndex * currentPageSize + data.results.length
|
||||
: (pageIndex + 1) * currentPageSize,
|
||||
total: data.pageInfo.results,
|
||||
strong: (msg: React.ReactNode) => (
|
||||
<span className="font-medium">{msg}</span>
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center sm:flex-1 sm:justify-start lg:justify-center">
|
||||
<span className="-mt-3 items-center truncate text-sm sm:mt-0">
|
||||
{intl.formatMessage(globalMessages.resultsperpage, {
|
||||
pageSize: (
|
||||
<select
|
||||
id="pageSize"
|
||||
name="pageSize"
|
||||
onChange={(e) => {
|
||||
setCurrentPageSize(Number(e.target.value));
|
||||
router
|
||||
.push({
|
||||
pathname: router.pathname,
|
||||
query: router.query.userId
|
||||
? { userId: router.query.userId }
|
||||
: {},
|
||||
})
|
||||
.then(() => window.scrollTo(0, 0));
|
||||
}}
|
||||
value={currentPageSize}
|
||||
className="short inline"
|
||||
>
|
||||
<option value="5">5</option>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-auto justify-center space-x-2 sm:flex-1 sm:justify-end">
|
||||
<Button
|
||||
disabled={!hasPrevPage}
|
||||
onClick={() => updateQueryParams('page', (page - 1).toString())}
|
||||
>
|
||||
<ChevronLeftIcon />
|
||||
<span>{intl.formatMessage(globalMessages.previous)}</span>
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!hasNextPage}
|
||||
onClick={() => updateQueryParams('page', (page + 1).toString())}
|
||||
>
|
||||
<span>{intl.formatMessage(globalMessages.next)}</span>
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Blacklist;
|
||||
|
||||
interface BlacklistedItemProps {
|
||||
item: BlacklistItem;
|
||||
revalidateList: () => void;
|
||||
}
|
||||
|
||||
const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
const [isUpdating, setIsUpdating] = useState<boolean>(false);
|
||||
const { addToast } = useToasts();
|
||||
const { ref, inView } = useInView({
|
||||
triggerOnce: true,
|
||||
});
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
|
||||
const url =
|
||||
item.mediaType === 'movie'
|
||||
? `/api/v1/movie/${item.tmdbId}`
|
||||
: `/api/v1/tv/${item.tmdbId}`;
|
||||
const { data: title, error } = useSWR<MovieDetails | TvDetails>(
|
||||
inView ? url : null
|
||||
);
|
||||
|
||||
if (!title && !error) {
|
||||
return (
|
||||
<div
|
||||
className="h-64 w-full animate-pulse rounded-xl bg-gray-800 xl:h-28"
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
|
||||
setIsUpdating(true);
|
||||
|
||||
const res = await fetch('/api/v1/blacklist/' + tmdbId, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
revalidateList();
|
||||
setIsUpdating(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex w-full flex-col justify-between overflow-hidden rounded-xl bg-gray-800 py-4 text-gray-400 shadow-md ring-1 ring-gray-700 xl:h-28 xl:flex-row">
|
||||
{title && title.backdropPath && (
|
||||
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
|
||||
<CachedImage
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
fill
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'linear-gradient(90deg, rgba(31, 41, 55, 0.47) 0%, rgba(31, 41, 55, 1) 100%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="relative flex w-full flex-col justify-between overflow-hidden sm:flex-row">
|
||||
<div className="relative z-10 flex w-full items-center overflow-hidden pl-4 pr-4 sm:pr-0 xl:w-7/12 2xl:w-2/3">
|
||||
<Link
|
||||
href={
|
||||
item.mediaType === 'movie'
|
||||
? `/movie/${item.tmdbId}`
|
||||
: `/tv/${item.tmdbId}`
|
||||
}
|
||||
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
|
||||
>
|
||||
<CachedImage
|
||||
src={
|
||||
title?.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
: '/images/overseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
|
||||
width={600}
|
||||
height={900}
|
||||
/>
|
||||
</Link>
|
||||
<div className="flex flex-col justify-center overflow-hidden pl-2 xl:pl-4">
|
||||
<div className="pt-0.5 text-xs font-medium text-white sm:pt-1">
|
||||
{title &&
|
||||
(isMovie(title)
|
||||
? title.releaseDate
|
||||
: title.firstAirDate
|
||||
)?.slice(0, 4)}
|
||||
</div>
|
||||
<Link
|
||||
href={
|
||||
item.mediaType === 'movie'
|
||||
? `/movie/${item.tmdbId}`
|
||||
: `/tv/${item.tmdbId}`
|
||||
}
|
||||
>
|
||||
<span className="mr-2 min-w-0 truncate text-lg font-bold text-white hover:underline xl:text-xl">
|
||||
{title && (isMovie(title) ? title.title : title.name)}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="z-10 mt-4 ml-4 flex w-full flex-col justify-center overflow-hidden pr-4 text-sm sm:ml-2 sm:mt-0 xl:flex-1 xl:pr-0">
|
||||
<div className="card-field">
|
||||
<span className="card-field-name">Status</span>
|
||||
<Badge badgeType="danger">
|
||||
{intl.formatMessage(globalMessages.blacklisted)}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{item.createdAt && (
|
||||
<div className="card-field">
|
||||
<span className="card-field-name">
|
||||
{intl.formatMessage(globalMessages.blacklisted)}
|
||||
</span>
|
||||
<span className="flex truncate text-sm text-gray-300">
|
||||
{intl.formatMessage(messages.blacklistedby, {
|
||||
date: (
|
||||
<FormattedRelativeTime
|
||||
value={Math.floor(
|
||||
(new Date(item.createdAt).getTime() - Date.now()) / 1000
|
||||
)}
|
||||
updateIntervalInSeconds={1}
|
||||
numeric="auto"
|
||||
/>
|
||||
),
|
||||
user: (
|
||||
<Link href={`/users/${item.user.id}`}>
|
||||
<span className="group flex items-center truncate">
|
||||
<CachedImage
|
||||
src={item.user.avatar}
|
||||
alt=""
|
||||
className="avatar-sm ml-1.5"
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ objectFit: 'cover' }}
|
||||
/>
|
||||
<span className="ml-1 truncate text-sm font-semibold group-hover:text-white group-hover:underline">
|
||||
{item.user.displayName}
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="card-field">
|
||||
{item.mediaType === 'movie' ? (
|
||||
<div className="pointer-events-none z-40 self-start rounded-full border border-blue-500 bg-blue-600 bg-opacity-80 shadow-md">
|
||||
<div className="flex h-4 items-center px-2 py-2 text-center text-xs font-medium uppercase tracking-wider text-white sm:h-5">
|
||||
{intl.formatMessage(globalMessages.movie)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="pointer-events-none z-40 self-start rounded-full border border-purple-600 bg-purple-600 bg-opacity-80 shadow-md">
|
||||
<div className="flex h-4 items-center px-2 py-2 text-center text-xs font-medium uppercase tracking-wider text-white sm:h-5">
|
||||
{intl.formatMessage(globalMessages.tvshow)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="z-10 mt-4 flex w-full flex-col justify-center space-y-2 pl-4 pr-4 xl:mt-0 xl:w-96 xl:items-end xl:pl-0">
|
||||
{hasPermission(Permission.MANAGE_BLACKLIST) && (
|
||||
<ConfirmButton
|
||||
onClick={() =>
|
||||
removeFromBlacklist(
|
||||
item.tmdbId,
|
||||
title && (isMovie(title) ? title.title : title.name)
|
||||
)
|
||||
}
|
||||
confirmText={intl.formatMessage(
|
||||
isUpdating ? globalMessages.deleting : globalMessages.areyousure
|
||||
)}
|
||||
className={`w-full ${
|
||||
isUpdating ? 'pointer-events-none opacity-50' : ''
|
||||
}`}
|
||||
>
|
||||
<TrashIcon />
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removefromBlacklist)}
|
||||
</span>
|
||||
</ConfirmButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { CalendarIcon, TrashIcon, UserIcon } from '@heroicons/react/24/solid';
|
||||
import type { Blacklist } from '@server/entity/Blacklist';
|
||||
import Link from 'next/link';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
|
||||
const messages = defineMessages('component.BlacklistBlock', {
|
||||
blacklistedby: 'Blacklisted By',
|
||||
blacklistdate: 'Blacklisted date',
|
||||
});
|
||||
|
||||
interface BlacklistBlockProps {
|
||||
blacklistItem: Blacklist;
|
||||
onUpdate?: () => void;
|
||||
onDelete?: () => void;
|
||||
}
|
||||
|
||||
const BlacklistBlock = ({
|
||||
blacklistItem,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
}: BlacklistBlockProps) => {
|
||||
const { user } = useUser();
|
||||
const intl = useIntl();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const { addToast } = useToasts();
|
||||
|
||||
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
|
||||
setIsUpdating(true);
|
||||
|
||||
const res = await fetch('/api/v1/blacklist/' + tmdbId, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
onUpdate && onUpdate();
|
||||
onDelete && onDelete();
|
||||
|
||||
setIsUpdating(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-4 py-3 text-gray-300">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mr-6 min-w-0 flex-1 flex-col items-center text-sm leading-5">
|
||||
<div className="white mb-1 flex flex-nowrap">
|
||||
<Tooltip content={intl.formatMessage(messages.blacklistedby)}>
|
||||
<UserIcon className="mr-1.5 h-5 w-5 min-w-0 flex-shrink-0" />
|
||||
</Tooltip>
|
||||
<span className="w-40 truncate md:w-auto">
|
||||
<Link
|
||||
href={
|
||||
blacklistItem.user.id === user?.id
|
||||
? '/profile'
|
||||
: `/users/${blacklistItem.user.id}`
|
||||
}
|
||||
>
|
||||
<span className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline">
|
||||
{blacklistItem.user.displayName}
|
||||
</span>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-shrink-0 flex-wrap">
|
||||
<Tooltip
|
||||
content={intl.formatMessage(globalMessages.removefromBlacklist)}
|
||||
>
|
||||
<Button
|
||||
buttonType="danger"
|
||||
onClick={() =>
|
||||
removeFromBlacklist(blacklistItem.tmdbId, blacklistItem.title)
|
||||
}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<TrashIcon className="icon-sm" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 sm:flex sm:justify-between">
|
||||
<div className="sm:flex">
|
||||
<div className="mr-6 flex items-center text-sm leading-5">
|
||||
<Badge badgeType="danger">
|
||||
{intl.formatMessage(globalMessages.blacklisted)}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center text-sm leading-5 sm:mt-0">
|
||||
<Tooltip content={intl.formatMessage(messages.blacklistdate)}>
|
||||
<CalendarIcon className="mr-1.5 h-5 w-5 flex-shrink-0" />
|
||||
</Tooltip>
|
||||
<span>
|
||||
{intl.formatDate(blacklistItem.createdAt, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlacklistBlock;
|
||||
@@ -1,79 +0,0 @@
|
||||
import Modal from '@app/components/Common/Modal';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import type { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
|
||||
interface BlacklistModalProps {
|
||||
tmdbId: number;
|
||||
type: 'movie' | 'tv' | 'collection';
|
||||
show: boolean;
|
||||
onComplete?: () => void;
|
||||
onCancel?: () => void;
|
||||
isUpdating?: boolean;
|
||||
}
|
||||
|
||||
const messages = defineMessages('component.BlacklistModal', {
|
||||
blacklisting: 'Blacklisting',
|
||||
});
|
||||
|
||||
const isMovie = (
|
||||
movie: MovieDetails | TvDetails | undefined
|
||||
): movie is MovieDetails => {
|
||||
if (!movie) return false;
|
||||
return (movie as MovieDetails).title !== undefined;
|
||||
};
|
||||
|
||||
const BlacklistModal = ({
|
||||
tmdbId,
|
||||
type,
|
||||
show,
|
||||
onComplete,
|
||||
onCancel,
|
||||
isUpdating,
|
||||
}: BlacklistModalProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { data, error } = useSWR<TvDetails | MovieDetails>(
|
||||
`/api/v1/${type}/${tmdbId}`
|
||||
);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
as="div"
|
||||
enter="transition-opacity duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-300"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
show={show}
|
||||
>
|
||||
<Modal
|
||||
loading={!data && !error}
|
||||
backgroundClickable
|
||||
title={`${intl.formatMessage(globalMessages.blacklist)} ${
|
||||
isMovie(data)
|
||||
? intl.formatMessage(globalMessages.movie)
|
||||
: intl.formatMessage(globalMessages.tvshow)
|
||||
}`}
|
||||
subTitle={`${isMovie(data) ? data.title : data?.name}`}
|
||||
onCancel={onCancel}
|
||||
onOk={onComplete}
|
||||
okText={
|
||||
isUpdating
|
||||
? intl.formatMessage(messages.blacklisting)
|
||||
: intl.formatMessage(globalMessages.blacklist)
|
||||
}
|
||||
okButtonType="danger"
|
||||
okDisabled={isUpdating}
|
||||
backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`}
|
||||
/>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlacklistModal;
|
||||
@@ -183,11 +183,6 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
const blacklistVisibility = hasPermission(
|
||||
[Permission.MANAGE_BLACKLIST, Permission.VIEW_BLACKLIST],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="media-page"
|
||||
@@ -340,26 +335,20 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
sliderKey="collection-movies"
|
||||
isLoading={false}
|
||||
isEmpty={data.parts.length === 0}
|
||||
items={data.parts
|
||||
.filter((title) => {
|
||||
if (!blacklistVisibility)
|
||||
return title.mediaInfo?.status !== MediaStatus.BLACKLISTED;
|
||||
return title;
|
||||
})
|
||||
.map((title) => (
|
||||
<TitleCard
|
||||
key={`collection-movie-${title.id}`}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
/>
|
||||
))}
|
||||
items={data.parts.map((title) => (
|
||||
<TitleCard
|
||||
key={`collection-movie-${title.id}`}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
<div className="extra-bottom-space relative" />
|
||||
</div>
|
||||
|
||||
@@ -16,11 +16,8 @@ const CachedImage = ({ src, ...props }: ImageProps) => {
|
||||
if (typeof imageUrl === 'string' && imageUrl.startsWith('http')) {
|
||||
const parsedUrl = new URL(imageUrl);
|
||||
|
||||
if (parsedUrl.host === 'image.tmdb.org') {
|
||||
if (currentSettings.cacheImages)
|
||||
imageUrl = imageUrl.replace('https://image.tmdb.org', '/imageproxy');
|
||||
} else if (parsedUrl.host !== 'gravatar.com') {
|
||||
imageUrl = '/avatarproxy/' + imageUrl;
|
||||
if (parsedUrl.host === 'image.tmdb.org' && currentSettings.cacheImages) {
|
||||
imageUrl = imageUrl.replace('https://image.tmdb.org', '/imageproxy');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import PersonCard from '@app/components/PersonCard';
|
||||
import TitleCard from '@app/components/TitleCard';
|
||||
import TmdbTitleCard from '@app/components/TitleCard/TmdbTitleCard';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import useVerticalScroll from '@app/hooks/useVerticalScroll';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
import type { WatchlistItem } from '@server/interfaces/api/discoverInterfaces';
|
||||
import type {
|
||||
CollectionResult,
|
||||
@@ -34,14 +32,7 @@ const ListView = ({
|
||||
mutateParent,
|
||||
}: ListViewProps) => {
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
useVerticalScroll(onScrollBottom, !isLoading && !isEmpty && !isReachingEnd);
|
||||
|
||||
const blacklistVisibility = hasPermission(
|
||||
[Permission.MANAGE_BLACKLIST, Permission.VIEW_BLACKLIST],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isEmpty && (
|
||||
@@ -64,89 +55,76 @@ const ListView = ({
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{items
|
||||
?.filter((title) => {
|
||||
if (!blacklistVisibility)
|
||||
return (
|
||||
(title as TvResult | MovieResult).mediaInfo?.status !==
|
||||
MediaStatus.BLACKLISTED
|
||||
{items?.map((title, index) => {
|
||||
let titleCard: React.ReactNode;
|
||||
|
||||
switch (title.mediaType) {
|
||||
case 'movie':
|
||||
titleCard = (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={
|
||||
(title.mediaInfo?.downloadStatus ?? []).length > 0
|
||||
}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
return title;
|
||||
})
|
||||
.map((title, index) => {
|
||||
let titleCard: React.ReactNode;
|
||||
break;
|
||||
case 'tv':
|
||||
titleCard = (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.name}
|
||||
userScore={title.voteAverage}
|
||||
year={title.firstAirDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={
|
||||
(title.mediaInfo?.downloadStatus ?? []).length > 0
|
||||
}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'collection':
|
||||
titleCard = (
|
||||
<TitleCard
|
||||
id={title.id}
|
||||
image={title.posterPath}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
mediaType={title.mediaType}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'person':
|
||||
titleCard = (
|
||||
<PersonCard
|
||||
personId={title.id}
|
||||
name={title.name}
|
||||
profilePath={title.profilePath}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (title.mediaType) {
|
||||
case 'movie':
|
||||
titleCard = (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={
|
||||
title.mediaInfo?.watchlists?.length ?? 0
|
||||
}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={
|
||||
(title.mediaInfo?.downloadStatus ?? []).length > 0
|
||||
}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'tv':
|
||||
titleCard = (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={
|
||||
title.mediaInfo?.watchlists?.length ?? 0
|
||||
}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.name}
|
||||
userScore={title.voteAverage}
|
||||
year={title.firstAirDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={
|
||||
(title.mediaInfo?.downloadStatus ?? []).length > 0
|
||||
}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'collection':
|
||||
titleCard = (
|
||||
<TitleCard
|
||||
id={title.id}
|
||||
image={title.posterPath}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
mediaType={title.mediaType}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'person':
|
||||
titleCard = (
|
||||
<PersonCard
|
||||
personId={title.id}
|
||||
name={title.name}
|
||||
profilePath={title.profilePath}
|
||||
canExpand
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return <li key={`${title.id}-${index}`}>{titleCard}</li>;
|
||||
})}
|
||||
return <li key={`${title.id}-${index}`}>{titleCard}</li>;
|
||||
})}
|
||||
{isLoading &&
|
||||
!isReachingEnd &&
|
||||
[...Array(20)].map((_item, i) => (
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import Spinner from '@app/assets/spinner.svg';
|
||||
import { CheckCircleIcon } from '@heroicons/react/20/solid';
|
||||
import {
|
||||
BellIcon,
|
||||
ClockIcon,
|
||||
EyeSlashIcon,
|
||||
MinusSmallIcon,
|
||||
} from '@heroicons/react/24/solid';
|
||||
import { BellIcon, ClockIcon, MinusSmallIcon } from '@heroicons/react/24/solid';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
|
||||
interface StatusBadgeMiniProps {
|
||||
@@ -49,10 +44,6 @@ const StatusBadgeMini = ({
|
||||
);
|
||||
indicatorIcon = <BellIcon />;
|
||||
break;
|
||||
case MediaStatus.BLACKLISTED:
|
||||
badgeStyle.push('bg-red-500 border-white-400 ring-white-400 text-white');
|
||||
indicatorIcon = <EyeSlashIcon />;
|
||||
break;
|
||||
case MediaStatus.PARTIALLY_AVAILABLE:
|
||||
badgeStyle.push(
|
||||
'bg-green-500 border-green-400 ring-green-400 text-green-100'
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
CompanySelector,
|
||||
GenreSelector,
|
||||
KeywordSelector,
|
||||
StatusSelector,
|
||||
WatchProviderSelector,
|
||||
} from '@app/components/Selector';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
@@ -41,7 +40,6 @@ const messages = defineMessages('components.Discover.FilterSlideover', {
|
||||
runtime: 'Runtime',
|
||||
streamingservices: 'Streaming Services',
|
||||
voteCount: 'Number of votes between {minValue} and {maxValue}',
|
||||
status: 'Status',
|
||||
});
|
||||
|
||||
type FilterSlideoverProps = {
|
||||
@@ -152,23 +150,6 @@ const FilterSlideover = ({
|
||||
updateQueryParams('genre', value?.map((v) => v.value).join(','));
|
||||
}}
|
||||
/>
|
||||
{type === 'tv' && (
|
||||
<>
|
||||
<span className="text-lg font-semibold">
|
||||
{intl.formatMessage(messages.status)}
|
||||
</span>
|
||||
<StatusSelector
|
||||
defaultValue={currentFilters.status}
|
||||
isMulti
|
||||
onChange={(value) => {
|
||||
updateQueryParams(
|
||||
'status',
|
||||
value?.map((v) => v.value).join('|')
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<span className="text-lg font-semibold">
|
||||
{intl.formatMessage(messages.keywords)}
|
||||
</span>
|
||||
|
||||
@@ -108,7 +108,6 @@ export const QueryFilterOptions = z.object({
|
||||
voteCountGte: z.string().optional(),
|
||||
watchRegion: z.string().optional(),
|
||||
watchProviders: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
});
|
||||
|
||||
export type FilterOptions = z.infer<typeof QueryFilterOptions>;
|
||||
@@ -148,10 +147,6 @@ export const prepareFilterValues = (
|
||||
filterValues.genre = values.genre;
|
||||
}
|
||||
|
||||
if (values.status) {
|
||||
filterValues.status = values.status;
|
||||
}
|
||||
|
||||
if (values.keywords) {
|
||||
filterValues.keywords = values.keywords;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { MediaType } from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import getConfig from 'next/config';
|
||||
|
||||
interface ExternalLinkBlockProps {
|
||||
mediaType: 'movie' | 'tv';
|
||||
@@ -30,6 +31,7 @@ const ExternalLinkBlock = ({
|
||||
mediaUrl,
|
||||
}: ExternalLinkBlockProps) => {
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { locale } = useLocale();
|
||||
|
||||
return (
|
||||
@@ -43,8 +45,7 @@ const ExternalLinkBlock = ({
|
||||
>
|
||||
{settings.currentSettings.mediaServerType === MediaServerType.PLEX ? (
|
||||
<PlexLogo />
|
||||
) : settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY ? (
|
||||
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
|
||||
<EmbyLogo />
|
||||
) : (
|
||||
<JellyfinLogo />
|
||||
|
||||
@@ -89,7 +89,7 @@ const IssueComment = ({
|
||||
</Transition>
|
||||
<Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}>
|
||||
<CachedImage
|
||||
src={`${comment.user.avatar}`}
|
||||
src={`/avatarproxy/${comment.user.avatar}`}
|
||||
alt=""
|
||||
className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
|
||||
width={40}
|
||||
|
||||
@@ -28,6 +28,7 @@ import type Issue from '@server/entity/Issue';
|
||||
import type { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import getConfig from 'next/config';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
@@ -106,6 +107,7 @@ const IssueDetails = () => {
|
||||
(opt) => opt.issueType === issueData?.issueType
|
||||
);
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
@@ -287,7 +289,7 @@ const IssueDetails = () => {
|
||||
className="group ml-1 inline-flex h-full items-center xl:ml-1.5"
|
||||
>
|
||||
<CachedImage
|
||||
src={`${issueData.createdBy.avatar}`}
|
||||
src={`/avatarproxy/${issueData.createdBy.avatar}`}
|
||||
alt=""
|
||||
className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6"
|
||||
width={20}
|
||||
@@ -387,8 +389,7 @@ const IssueDetails = () => {
|
||||
>
|
||||
<PlayIcon />
|
||||
<span>
|
||||
{settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.playonplex, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
@@ -435,8 +436,7 @@ const IssueDetails = () => {
|
||||
>
|
||||
<PlayIcon />
|
||||
<span>
|
||||
{settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.play4konplex, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
@@ -661,8 +661,7 @@ const IssueDetails = () => {
|
||||
>
|
||||
<PlayIcon />
|
||||
<span>
|
||||
{settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.playonplex, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
@@ -708,8 +707,7 @@ const IssueDetails = () => {
|
||||
>
|
||||
<PlayIcon />
|
||||
<span>
|
||||
{settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.play4konplex, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ import { MediaType } from '@server/constants/media';
|
||||
import type Issue from '@server/entity/Issue';
|
||||
import type { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useInView } from 'react-intersection-observer';
|
||||
import { FormattedRelativeTime, useIntl } from 'react-intl';
|
||||
@@ -225,8 +226,8 @@ const IssueItem = ({ issue }: IssueItemProps) => {
|
||||
href={`/users/${issue.createdBy.id}`}
|
||||
className="group flex items-center truncate"
|
||||
>
|
||||
<CachedImage
|
||||
src={'/avatarproxy/' + issue.createdBy.avatar}
|
||||
<Image
|
||||
src={issue.createdBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm ml-1.5 object-cover"
|
||||
width={20}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
CogIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
ExclamationTriangleIcon,
|
||||
EyeSlashIcon,
|
||||
FilmIcon,
|
||||
SparklesIcon,
|
||||
TvIcon,
|
||||
@@ -17,7 +16,6 @@ import {
|
||||
ClockIcon as FilledClockIcon,
|
||||
CogIcon as FilledCogIcon,
|
||||
ExclamationTriangleIcon as FilledExclamationTriangleIcon,
|
||||
EyeSlashIcon as FilledEyeSlashIcon,
|
||||
FilmIcon as FilledFilmIcon,
|
||||
SparklesIcon as FilledSparklesIcon,
|
||||
TvIcon as FilledTvIcon,
|
||||
@@ -86,18 +84,6 @@ const MobileMenu = () => {
|
||||
svgIconSelected: <FilledClockIcon className="h-6 w-6" />,
|
||||
activeRegExp: /^\/requests/,
|
||||
},
|
||||
{
|
||||
href: '/blacklist',
|
||||
content: intl.formatMessage(menuMessages.blacklist),
|
||||
svgIcon: <EyeSlashIcon className="h-6 w-6" />,
|
||||
svgIconSelected: <FilledEyeSlashIcon className="h-6 w-6" />,
|
||||
activeRegExp: /^\/blacklist/,
|
||||
requiredPermission: [
|
||||
Permission.MANAGE_BLACKLIST,
|
||||
Permission.VIEW_BLACKLIST,
|
||||
],
|
||||
permissionType: 'or',
|
||||
},
|
||||
{
|
||||
href: '/issues',
|
||||
content: intl.formatMessage(menuMessages.issues),
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ClockIcon,
|
||||
CogIcon,
|
||||
ExclamationTriangleIcon,
|
||||
EyeSlashIcon,
|
||||
FilmIcon,
|
||||
SparklesIcon,
|
||||
TvIcon,
|
||||
@@ -26,7 +25,6 @@ export const menuMessages = defineMessages('components.Layout.Sidebar', {
|
||||
browsemovies: 'Movies',
|
||||
browsetv: 'Series',
|
||||
requests: 'Requests',
|
||||
blacklist: 'Blacklist',
|
||||
issues: 'Issues',
|
||||
users: 'Users',
|
||||
settings: 'Settings',
|
||||
@@ -73,17 +71,6 @@ const SidebarLinks: SidebarLinkProps[] = [
|
||||
svgIcon: <ClockIcon className="mr-3 h-6 w-6" />,
|
||||
activeRegExp: /^\/requests/,
|
||||
},
|
||||
{
|
||||
href: '/blacklist',
|
||||
messagesKey: 'blacklist',
|
||||
svgIcon: <EyeSlashIcon className="mr-3 h-6 w-6" />,
|
||||
activeRegExp: /^\/blacklist/,
|
||||
requiredPermission: [
|
||||
Permission.MANAGE_BLACKLIST,
|
||||
Permission.VIEW_BLACKLIST,
|
||||
],
|
||||
permissionType: 'or',
|
||||
},
|
||||
{
|
||||
href: '/issues',
|
||||
messagesKey: 'issues',
|
||||
|
||||
@@ -58,7 +58,7 @@ const UserDropdown = () => {
|
||||
>
|
||||
<CachedImage
|
||||
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
|
||||
src={user ? user.avatar : ''}
|
||||
src={user && user.avatar ? `/avatarproxy/${user.avatar}` : ''}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
@@ -81,7 +81,7 @@ const UserDropdown = () => {
|
||||
<div className="flex items-center space-x-2">
|
||||
<CachedImage
|
||||
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
|
||||
src={user ? user.avatar : ''}
|
||||
src={user && user.avatar ? `/avatarproxy/${user.avatar}` : ''}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
|
||||
@@ -4,9 +4,9 @@ import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { InformationCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import { MediaServerType, ServerType } from '@server/constants/server';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import getConfig from 'next/config';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -26,7 +26,6 @@ const messages = defineMessages('components.Login', {
|
||||
validationemailformat: 'Valid email required',
|
||||
validationusernamerequired: 'Username required',
|
||||
validationpasswordrequired: 'Password required',
|
||||
validationservertyperequired: 'Please select a server type',
|
||||
validationHostnameRequired: 'You must provide a valid hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
@@ -41,51 +40,42 @@ const messages = defineMessages('components.Login', {
|
||||
initialsigningin: 'Connecting…',
|
||||
initialsignin: 'Connect',
|
||||
forgotpassword: 'Forgot Password?',
|
||||
servertype: 'Server Type',
|
||||
back: 'Go back',
|
||||
});
|
||||
|
||||
interface JellyfinLoginProps {
|
||||
revalidate: () => void;
|
||||
initial?: boolean;
|
||||
serverType?: MediaServerType;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
revalidate,
|
||||
initial,
|
||||
serverType,
|
||||
onCancel,
|
||||
}) => {
|
||||
const toasts = useToasts();
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
serverType === MediaServerType.JELLYFIN
|
||||
? ServerType.JELLYFIN
|
||||
: serverType === MediaServerType.EMBY
|
||||
? ServerType.EMBY
|
||||
: 'Media Server',
|
||||
};
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
if (initial) {
|
||||
const LoginSchema = Yup.object().shape({
|
||||
hostname: Yup.string().required(
|
||||
intl.formatMessage(
|
||||
messages.validationhostrequired,
|
||||
mediaServerFormatValues
|
||||
)
|
||||
intl.formatMessage(messages.validationhostrequired, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
})
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
urlBase: Yup.string().matches(
|
||||
/^(.*[^/])$/,
|
||||
intl.formatMessage(messages.validationUrlBaseTrailingSlash)
|
||||
),
|
||||
urlBase: Yup.string()
|
||||
.matches(
|
||||
/^(\/[^/].*[^/]$)/,
|
||||
intl.formatMessage(messages.validationUrlBaseLeadingSlash)
|
||||
)
|
||||
.matches(
|
||||
/^(.*[^/])$/,
|
||||
intl.formatMessage(messages.validationUrlBaseTrailingSlash)
|
||||
),
|
||||
email: Yup.string()
|
||||
.email(intl.formatMessage(messages.validationemailformat))
|
||||
.required(intl.formatMessage(messages.validationemailrequired)),
|
||||
@@ -95,6 +85,11 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
password: Yup.string(),
|
||||
});
|
||||
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{
|
||||
@@ -109,11 +104,6 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
validationSchema={LoginSchema}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
// Check if serverType is either 'Jellyfin' or 'Emby'
|
||||
// if (serverType !== 'Jellyfin' && serverType !== 'Emby') {
|
||||
// throw new Error('Invalid serverType'); // You can customize the error message
|
||||
// }
|
||||
|
||||
const res = await fetch('/api/v1/auth/jellyfin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -127,7 +117,6 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
useSsl: values.useSsl,
|
||||
urlBase: values.urlBase,
|
||||
email: values.email,
|
||||
serverType: serverType,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error(res.statusText, { cause: res });
|
||||
@@ -323,7 +312,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex flex-row-reverse justify-between">
|
||||
<div className="flex justify-end">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
@@ -335,13 +324,6 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</Button>
|
||||
</span>
|
||||
{onCancel && (
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button buttonType="default" onClick={() => onCancel()}>
|
||||
<FormattedMessage {...messages.back} />
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
@@ -447,8 +429,7 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
|
||||
jellyfinForgotPasswordUrl
|
||||
? `${jellyfinForgotPasswordUrl}`
|
||||
: `${baseUrl}/web/index.html#!/${
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
process.env.JELLYFIN_TYPE === 'emby'
|
||||
? 'startup/'
|
||||
: ''
|
||||
}forgotpassword.html`
|
||||
|
||||
@@ -10,6 +10,7 @@ import defineMessages from '@app/utils/defineMessages';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { XCircleIcon } from '@heroicons/react/24/solid';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import getConfig from 'next/config';
|
||||
import { useRouter } from 'next/dist/client/router';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -33,6 +34,7 @@ const Login = () => {
|
||||
const { user, revalidate } = useUser();
|
||||
const router = useRouter();
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
|
||||
// We take the token and attempt to sign in. If we get a success message, we will
|
||||
@@ -86,15 +88,6 @@ const Login = () => {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen flex-col bg-gray-900 py-14">
|
||||
<PageTitle title={intl.formatMessage(messages.signin)} />
|
||||
@@ -161,10 +154,12 @@ const Login = () => {
|
||||
{settings.currentSettings.mediaServerType ==
|
||||
MediaServerType.PLEX
|
||||
? intl.formatMessage(messages.signinwithplex)
|
||||
: intl.formatMessage(
|
||||
messages.signinwithjellyfin,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
: intl.formatMessage(messages.signinwithjellyfin, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
})}
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(0)}>
|
||||
<div className="px-10 py-8">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import BlacklistBlock from '@app/components/BlacklistBlock';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import ConfirmButton from '@app/components/Common/ConfirmButton';
|
||||
@@ -28,6 +27,7 @@ import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfa
|
||||
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
|
||||
import type { MovieDetails } from '@server/models/Movie';
|
||||
import type { TvDetails } from '@server/models/Tv';
|
||||
import getConfig from 'next/config';
|
||||
import Link from 'next/link';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
@@ -95,6 +95,7 @@ const ManageSlideOver = ({
|
||||
const { user: currentUser, hasPermission } = useUser();
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { data: watchData } = useSWR<MediaWatchDataResponse>(
|
||||
settings.currentSettings.mediaServerType === MediaServerType.PLEX &&
|
||||
data.mediaInfo &&
|
||||
@@ -285,20 +286,6 @@ const ManageSlideOver = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data.mediaInfo?.status === MediaStatus.BLACKLISTED && (
|
||||
<div>
|
||||
<h3 className="mb-2 text-xl font-bold">
|
||||
{intl.formatMessage(globalMessages.blacklist)}
|
||||
</h3>
|
||||
<div className="overflow-hidden rounded-md border border-gray-700 shadow">
|
||||
<BlacklistBlock
|
||||
blacklistItem={data.mediaInfo.blacklist}
|
||||
onUpdate={() => revalidate()}
|
||||
onDelete={() => onClose()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasPermission(Permission.ADMIN) &&
|
||||
(data.mediaInfo?.serviceUrl ||
|
||||
data.mediaInfo?.tautulliUrl ||
|
||||
@@ -369,7 +356,7 @@ const ManageSlideOver = ({
|
||||
content={user.displayName}
|
||||
>
|
||||
<CachedImage
|
||||
src={user.avatar}
|
||||
src={`/avatarproxy/${user.avatar}`}
|
||||
alt={user.displayName}
|
||||
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
|
||||
width={32}
|
||||
@@ -530,7 +517,7 @@ const ManageSlideOver = ({
|
||||
content={user.displayName}
|
||||
>
|
||||
<CachedImage
|
||||
src={user.avatar}
|
||||
src={`/avatarproxy/${user.avatar}`}
|
||||
alt={user.displayName}
|
||||
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
|
||||
width={32}
|
||||
@@ -618,17 +605,32 @@ const ManageSlideOver = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{hasPermission(Permission.ADMIN) &&
|
||||
data?.mediaInfo &&
|
||||
data.mediaInfo.status !== MediaStatus.BLACKLISTED && (
|
||||
<div>
|
||||
<h3 className="mb-2 text-xl font-bold">
|
||||
{intl.formatMessage(messages.manageModalAdvanced)}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
|
||||
{hasPermission(Permission.ADMIN) && data?.mediaInfo && (
|
||||
<div>
|
||||
<h3 className="mb-2 text-xl font-bold">
|
||||
{intl.formatMessage(messages.manageModalAdvanced)}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
|
||||
<Button
|
||||
onClick={() => markAvailable()}
|
||||
className="w-full"
|
||||
buttonType="success"
|
||||
>
|
||||
<CheckCircleIcon />
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
mediaType === 'movie'
|
||||
? messages.markavailable
|
||||
: messages.markallseasonsavailable
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
{data?.mediaInfo.status4k !== MediaStatus.AVAILABLE &&
|
||||
settings.currentSettings.series4kEnabled && (
|
||||
<Button
|
||||
onClick={() => markAvailable()}
|
||||
onClick={() => markAvailable(true)}
|
||||
className="w-full"
|
||||
buttonType="success"
|
||||
>
|
||||
@@ -636,59 +638,41 @@ const ManageSlideOver = ({
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
mediaType === 'movie'
|
||||
? messages.markavailable
|
||||
: messages.markallseasonsavailable
|
||||
? messages.mark4kavailable
|
||||
: messages.markallseasons4kavailable
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
{data?.mediaInfo.status4k !== MediaStatus.AVAILABLE &&
|
||||
settings.currentSettings.series4kEnabled && (
|
||||
<Button
|
||||
onClick={() => markAvailable(true)}
|
||||
className="w-full"
|
||||
buttonType="success"
|
||||
>
|
||||
<CheckCircleIcon />
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
mediaType === 'movie'
|
||||
? messages.mark4kavailable
|
||||
: messages.markallseasons4kavailable
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
<div>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteMedia()}
|
||||
confirmText={intl.formatMessage(globalMessages.areyousure)}
|
||||
className="w-full"
|
||||
>
|
||||
<DocumentMinusIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.manageModalClearMedia)}
|
||||
</span>
|
||||
</ConfirmButton>
|
||||
<div className="mt-2 text-xs text-gray-400">
|
||||
{intl.formatMessage(messages.manageModalClearMediaWarning, {
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie' ? messages.movie : messages.tvshow
|
||||
),
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: 'Jellyfin',
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteMedia()}
|
||||
confirmText={intl.formatMessage(globalMessages.areyousure)}
|
||||
className="w-full"
|
||||
>
|
||||
<DocumentMinusIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.manageModalClearMedia)}
|
||||
</span>
|
||||
</ConfirmButton>
|
||||
<div className="mt-2 text-xs text-gray-400">
|
||||
{intl.formatMessage(messages.manageModalClearMediaWarning, {
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie' ? messages.movie : messages.tvshow
|
||||
),
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: 'Jellyfin',
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SlideOver>
|
||||
);
|
||||
|
||||
@@ -3,10 +3,8 @@ import PersonCard from '@app/components/PersonCard';
|
||||
import Slider from '@app/components/Slider';
|
||||
import TitleCard from '@app/components/TitleCard';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import { ArrowRightCircleIcon } from '@heroicons/react/24/outline';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import type {
|
||||
MovieResult,
|
||||
PersonResult,
|
||||
@@ -43,7 +41,6 @@ const MediaSlider = ({
|
||||
onNewTitles,
|
||||
}: MediaSliderProps) => {
|
||||
const settings = useSettings();
|
||||
const { hasPermission } = useUser();
|
||||
const { data, error, setSize, size } = useSWRInfinite<MixedResult>(
|
||||
(pageIndex: number, previousPageData: MixedResult | null) => {
|
||||
if (previousPageData && pageIndex + 1 > previousPageData.totalPages) {
|
||||
@@ -93,65 +90,50 @@ const MediaSlider = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const blacklistVisibility = hasPermission(
|
||||
[Permission.MANAGE_BLACKLIST, Permission.VIEW_BLACKLIST],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
const finalTitles = titles
|
||||
.slice(0, 20)
|
||||
.filter((title) => {
|
||||
if (!blacklistVisibility)
|
||||
const finalTitles = titles.slice(0, 20).map((title) => {
|
||||
switch (title.mediaType) {
|
||||
case 'movie':
|
||||
return (
|
||||
(title as TvResult | MovieResult).mediaInfo?.status !==
|
||||
MediaStatus.BLACKLISTED
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={(title.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||
/>
|
||||
);
|
||||
return title;
|
||||
})
|
||||
.map((title) => {
|
||||
switch (title.mediaType) {
|
||||
case 'movie':
|
||||
return (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.title}
|
||||
userScore={title.voteAverage}
|
||||
year={title.releaseDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={(title.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||
/>
|
||||
);
|
||||
case 'tv':
|
||||
return (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.name}
|
||||
userScore={title.voteAverage}
|
||||
year={title.firstAirDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={(title.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||
/>
|
||||
);
|
||||
case 'person':
|
||||
return (
|
||||
<PersonCard
|
||||
personId={title.id}
|
||||
name={title.name}
|
||||
profilePath={title.profilePath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
case 'tv':
|
||||
return (
|
||||
<TitleCard
|
||||
key={title.id}
|
||||
id={title.id}
|
||||
isAddedToWatchlist={title.mediaInfo?.watchlists?.length ?? 0}
|
||||
image={title.posterPath}
|
||||
status={title.mediaInfo?.status}
|
||||
summary={title.overview}
|
||||
title={title.name}
|
||||
userScore={title.voteAverage}
|
||||
year={title.firstAirDate}
|
||||
mediaType={title.mediaType}
|
||||
inProgress={(title.mediaInfo?.downloadStatus ?? []).length > 0}
|
||||
/>
|
||||
);
|
||||
case 'person':
|
||||
return (
|
||||
<PersonCard
|
||||
personId={title.id}
|
||||
name={title.name}
|
||||
profilePath={title.profilePath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (linkUrl && titles.length > 20) {
|
||||
finalTitles.push(
|
||||
|
||||
@@ -5,7 +5,6 @@ import RTRotten from '@app/assets/rt_rotten.svg';
|
||||
import ImdbLogo from '@app/assets/services/imdb.svg';
|
||||
import Spinner from '@app/assets/spinner.svg';
|
||||
import TmdbLogo from '@app/assets/tmdb_logo.svg';
|
||||
import BlacklistModal from '@app/components/BlacklistModal';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
@@ -36,7 +35,6 @@ import {
|
||||
CloudIcon,
|
||||
CogIcon,
|
||||
ExclamationTriangleIcon,
|
||||
EyeSlashIcon,
|
||||
FilmIcon,
|
||||
PlayIcon,
|
||||
TicketIcon,
|
||||
@@ -55,9 +53,10 @@ import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
|
||||
import { countries } from 'country-flag-icons';
|
||||
import 'country-flag-icons/3x2/flags.css';
|
||||
import { uniqBy } from 'lodash';
|
||||
import getConfig from 'next/config';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
@@ -127,9 +126,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
const [toggleWatchlist, setToggleWatchlist] = useState<boolean>(
|
||||
!movie?.onUserWatchlist
|
||||
);
|
||||
const [isBlacklistUpdating, setIsBlacklistUpdating] =
|
||||
useState<boolean>(false);
|
||||
const [showBlacklistModal, setShowBlacklistModal] = useState(false);
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { addToast } = useToasts();
|
||||
|
||||
const {
|
||||
@@ -160,11 +157,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
setShowManager(router.query.manage == '1' ? true : false);
|
||||
}, [router.query.manage]);
|
||||
|
||||
const closeBlacklistModal = useCallback(
|
||||
() => setShowBlacklistModal(false),
|
||||
[]
|
||||
);
|
||||
|
||||
const { mediaUrl: plexUrl, mediaUrl4k: plexUrl4k } = useDeepLinks({
|
||||
mediaUrl: data?.mediaInfo?.mediaUrl,
|
||||
mediaUrl4k: data?.mediaInfo?.mediaUrl4k,
|
||||
@@ -287,7 +279,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
?.flatrate ?? [];
|
||||
|
||||
function getAvalaibleMediaServerName() {
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
|
||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||
}
|
||||
|
||||
@@ -299,8 +291,8 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
}
|
||||
|
||||
function getAvalaible4kMediaServerName() {
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
|
||||
return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
|
||||
}
|
||||
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
|
||||
@@ -384,60 +376,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onClickHideItemBtn = async (): Promise<void> => {
|
||||
setIsBlacklistUpdating(true);
|
||||
|
||||
const res = await fetch('/api/v1/blacklist', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tmdbId: movie?.id,
|
||||
mediaType: 'movie',
|
||||
title: movie?.title,
|
||||
user: user?.id,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
title: movie?.title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
|
||||
revalidate();
|
||||
} else if (res.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title: movie?.title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
setIsBlacklistUpdating(false);
|
||||
closeBlacklistModal();
|
||||
};
|
||||
|
||||
const showHideButton = hasPermission([Permission.MANAGE_BLACKLIST], {
|
||||
type: 'or',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className="media-page"
|
||||
@@ -483,14 +421,6 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
revalidate={() => revalidate()}
|
||||
show={showManager}
|
||||
/>
|
||||
<BlacklistModal
|
||||
tmdbId={data.id}
|
||||
type="movie"
|
||||
show={showBlacklistModal}
|
||||
onCancel={closeBlacklistModal}
|
||||
onComplete={onClickHideItemBtn}
|
||||
isUpdating={isBlacklistUpdating}
|
||||
/>
|
||||
<div className="media-header">
|
||||
<div className="media-poster">
|
||||
<CachedImage
|
||||
@@ -567,61 +497,40 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="media-actions">
|
||||
{showHideButton &&
|
||||
data?.mediaInfo?.status !== MediaStatus.PROCESSING &&
|
||||
data?.mediaInfo?.status !== MediaStatus.AVAILABLE &&
|
||||
data?.mediaInfo?.status !== MediaStatus.PARTIALLY_AVAILABLE &&
|
||||
data?.mediaInfo?.status !== MediaStatus.PENDING &&
|
||||
data?.mediaInfo?.status !== MediaStatus.BLACKLISTED && (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(globalMessages.addToBlacklist)}
|
||||
>
|
||||
<>
|
||||
{toggleWatchlist ? (
|
||||
<Tooltip content={intl.formatMessage(messages.addtowatchlist)}>
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={() => setShowBlacklistModal(true)}
|
||||
onClick={onClickWatchlistBtn}
|
||||
>
|
||||
<EyeSlashIcon className={'h-3'} />
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<StarIcon className={'h-3 text-amber-300'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.removefromwatchlist)}
|
||||
>
|
||||
<Button
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={onClickDeleteWatchlistBtn}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<MinusCircleIcon className={'h-3'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{data?.mediaInfo?.status !== MediaStatus.BLACKLISTED && (
|
||||
<>
|
||||
{toggleWatchlist ? (
|
||||
<Tooltip content={intl.formatMessage(messages.addtowatchlist)}>
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={onClickWatchlistBtn}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<StarIcon className={'h-3 text-amber-300'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.removefromwatchlist)}
|
||||
>
|
||||
<Button
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={onClickDeleteWatchlistBtn}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<MinusCircleIcon className={'h-3'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
<PlayButton links={mediaLinks} />
|
||||
<RequestButton
|
||||
mediaType="movie"
|
||||
|
||||
@@ -78,13 +78,6 @@ export const messages = defineMessages('components.PermissionEdit', {
|
||||
viewwatchlists: 'View {mediaServerName} Watchlists',
|
||||
viewwatchlistsDescription:
|
||||
"Grant permission to view other users' {mediaServerName} Watchlists.",
|
||||
manageblacklist: 'Manage Blacklist',
|
||||
manageblacklistDescription: 'Grant permission to manage blacklisted media.',
|
||||
blacklistedItems: 'Blacklist media.',
|
||||
blacklistedItemsDescription: 'Grant permission to blacklist media.',
|
||||
viewblacklistedItems: 'View blacklisted media.',
|
||||
viewblacklistedItemsDescription:
|
||||
'Grant permission to view blacklisted media.',
|
||||
});
|
||||
|
||||
interface PermissionEditProps {
|
||||
@@ -339,22 +332,6 @@ export const PermissionEdit = ({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'manageblacklist',
|
||||
name: intl.formatMessage(messages.manageblacklist),
|
||||
description: intl.formatMessage(messages.manageblacklistDescription),
|
||||
permission: Permission.MANAGE_BLACKLIST,
|
||||
children: [
|
||||
{
|
||||
id: 'viewblacklisteditems',
|
||||
name: intl.formatMessage(messages.viewblacklistedItems),
|
||||
description: intl.formatMessage(
|
||||
messages.viewblacklistedItemsDescription
|
||||
),
|
||||
permission: Permission.VIEW_BLACKLIST,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -300,7 +300,6 @@ const RequestButton = ({
|
||||
}) &&
|
||||
media &&
|
||||
media.status !== MediaStatus.AVAILABLE &&
|
||||
media.status !== MediaStatus.BLACKLISTED &&
|
||||
!isShowComplete
|
||||
) {
|
||||
buttons.push({
|
||||
@@ -346,7 +345,6 @@ const RequestButton = ({
|
||||
}) &&
|
||||
media &&
|
||||
media.status4k !== MediaStatus.AVAILABLE &&
|
||||
media.status !== MediaStatus.BLACKLISTED &&
|
||||
!is4kShowComplete &&
|
||||
settings.currentSettings.series4kEnabled
|
||||
) {
|
||||
|
||||
@@ -116,7 +116,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
>
|
||||
<span className="avatar-sm">
|
||||
<CachedImage
|
||||
src={requestData.requestedBy.avatar}
|
||||
src={`/avatarproxy/${requestData.requestedBy.avatar}`}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
@@ -390,7 +390,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
>
|
||||
<span className="avatar-sm">
|
||||
<CachedImage
|
||||
src={requestData.requestedBy.avatar}
|
||||
src={`/avatarproxy/${requestData.requestedBy.avatar}`}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
|
||||
@@ -42,7 +42,6 @@ const messages = defineMessages('components.RequestList.RequestItem', {
|
||||
tmdbid: 'TMDB ID',
|
||||
tvdbid: 'TheTVDB ID',
|
||||
unknowntitle: 'Unknown Title',
|
||||
removearr: 'Remove from {arr}',
|
||||
profileName: 'Profile',
|
||||
});
|
||||
|
||||
@@ -191,7 +190,7 @@ const RequestItemError = ({
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
src={requestData.requestedBy.avatar}
|
||||
src={`/avatarproxy/${requestData.requestedBy.avatar}`}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
@@ -250,7 +249,7 @@ const RequestItemError = ({
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
src={requestData.modifiedBy.avatar}
|
||||
src={`/avatarproxy/${requestData.modifiedBy.avatar}`}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
@@ -342,18 +341,6 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
revalidateList();
|
||||
};
|
||||
|
||||
const deleteMediaFile = async () => {
|
||||
if (request.media) {
|
||||
await fetch(`/api/v1/media/${request.media.id}/file`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
await fetch(`/api/v1/media/${request.media.id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
revalidateList();
|
||||
}
|
||||
};
|
||||
|
||||
const retryRequest = async () => {
|
||||
setRetrying(true);
|
||||
|
||||
@@ -570,7 +557,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
src={requestData.requestedBy.avatar}
|
||||
src={`/avatarproxy/${requestData.requestedBy.avatar}`}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
@@ -629,7 +616,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
src={requestData.requestedBy.avatar}
|
||||
src={`/avatarproxy/${requestData.requestedBy.avatar}`}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
@@ -679,28 +666,14 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
)}
|
||||
{requestData.status !== MediaRequestStatus.PENDING &&
|
||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
<>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteRequest()}
|
||||
confirmText={intl.formatMessage(globalMessages.areyousure)}
|
||||
className="w-full"
|
||||
>
|
||||
<TrashIcon />
|
||||
<span>{intl.formatMessage(messages.deleterequest)}</span>
|
||||
</ConfirmButton>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteMediaFile()}
|
||||
confirmText={intl.formatMessage(globalMessages.areyousure)}
|
||||
className="w-full"
|
||||
>
|
||||
<TrashIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.removearr, {
|
||||
arr: request.type === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
})}
|
||||
</span>
|
||||
</ConfirmButton>
|
||||
</>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteRequest()}
|
||||
confirmText={intl.formatMessage(globalMessages.areyousure)}
|
||||
className="w-full"
|
||||
>
|
||||
<TrashIcon />
|
||||
<span>{intl.formatMessage(messages.deleterequest)}</span>
|
||||
</ConfirmButton>
|
||||
)}
|
||||
{requestData.status === MediaRequestStatus.PENDING &&
|
||||
hasPermission(Permission.MANAGE_REQUESTS) && (
|
||||
|
||||
@@ -562,7 +562,7 @@ const AdvancedRequester = ({
|
||||
<Listbox.Button className="focus:shadow-outline-blue relative w-full cursor-default rounded-md border border-gray-700 bg-gray-800 py-2 pl-3 pr-10 text-left text-white transition duration-150 ease-in-out focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5">
|
||||
<span className="flex items-center">
|
||||
<CachedImage
|
||||
src={selectedUser.avatar}
|
||||
src={`/avatarproxy/${selectedUser.avatar}`}
|
||||
alt=""
|
||||
className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
|
||||
width={24}
|
||||
@@ -614,7 +614,7 @@ const AdvancedRequester = ({
|
||||
} flex items-center`}
|
||||
>
|
||||
<CachedImage
|
||||
src={user.avatar}
|
||||
src={`/avatarproxy/${user.avatar}`}
|
||||
alt=""
|
||||
className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
|
||||
width={24}
|
||||
|
||||
@@ -66,9 +66,7 @@ const CollectionRequestModal = ({
|
||||
(quota?.movie.remaining ?? 0) - selectedParts.length;
|
||||
|
||||
const getAllParts = (): number[] => {
|
||||
return (data?.parts ?? [])
|
||||
.filter((part) => part.mediaInfo?.status !== MediaStatus.BLACKLISTED)
|
||||
.map((part) => part.id);
|
||||
return (data?.parts ?? []).map((part) => part.id);
|
||||
};
|
||||
|
||||
const getAllRequestedParts = (): number[] => {
|
||||
@@ -250,11 +248,6 @@ const CollectionRequestModal = ({
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
const blacklistVisibility = hasPermission(
|
||||
[Permission.MANAGE_BLACKLIST, Permission.VIEW_BLACKLIST],
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
loading={(!data && !error) || !quota}
|
||||
@@ -351,156 +344,122 @@ const CollectionRequestModal = ({
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700">
|
||||
{data?.parts
|
||||
.filter((part) => {
|
||||
if (!blacklistVisibility)
|
||||
return (
|
||||
part.mediaInfo?.status !== MediaStatus.BLACKLISTED
|
||||
);
|
||||
return part;
|
||||
})
|
||||
.map((part) => {
|
||||
const partRequest = getPartRequest(part.id);
|
||||
const partMedia =
|
||||
part.mediaInfo &&
|
||||
part.mediaInfo[is4k ? 'status4k' : 'status'] !==
|
||||
MediaStatus.UNKNOWN
|
||||
? part.mediaInfo
|
||||
: undefined;
|
||||
{data?.parts.map((part) => {
|
||||
const partRequest = getPartRequest(part.id);
|
||||
const partMedia =
|
||||
part.mediaInfo &&
|
||||
part.mediaInfo[is4k ? 'status4k' : 'status'] !==
|
||||
MediaStatus.UNKNOWN
|
||||
? part.mediaInfo
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<tr key={`part-${part.id}`}>
|
||||
<td
|
||||
className={`whitespace-nowrap px-4 py-4 text-sm font-medium leading-5 text-gray-100 ${
|
||||
partMedia?.status === MediaStatus.BLACKLISTED &&
|
||||
'pointer-events-none opacity-50'
|
||||
return (
|
||||
<tr key={`part-${part.id}`}>
|
||||
<td className="whitespace-nowrap px-4 py-4 text-sm font-medium leading-5 text-gray-100">
|
||||
<span
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
aria-checked={
|
||||
!!partMedia || isSelectedPart(part.id)
|
||||
}
|
||||
onClick={() => togglePart(part.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === 'Space') {
|
||||
togglePart(part.id);
|
||||
}
|
||||
}}
|
||||
className={`relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none ${
|
||||
!!partMedia ||
|
||||
partRequest ||
|
||||
(quota?.movie.limit &&
|
||||
currentlyRemaining <= 0 &&
|
||||
!isSelectedPart(part.id))
|
||||
? 'opacity-50'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
role="checkbox"
|
||||
tabIndex={0}
|
||||
aria-checked={
|
||||
(!!partMedia &&
|
||||
partMedia.status !==
|
||||
MediaStatus.BLACKLISTED) ||
|
||||
isSelectedPart(part.id)
|
||||
}
|
||||
onClick={() => togglePart(part.id)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === 'Space') {
|
||||
togglePart(part.id);
|
||||
}
|
||||
}}
|
||||
className={`relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none ${
|
||||
(!!partMedia &&
|
||||
partMedia.status !==
|
||||
MediaStatus.BLACKLISTED) ||
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
!!partMedia ||
|
||||
partRequest ||
|
||||
(quota?.movie.limit &&
|
||||
currentlyRemaining <= 0 &&
|
||||
!isSelectedPart(part.id))
|
||||
? 'opacity-50'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
(!!partMedia &&
|
||||
partMedia.status !==
|
||||
MediaStatus.BLACKLISTED) ||
|
||||
partRequest ||
|
||||
isSelectedPart(part.id)
|
||||
? 'bg-indigo-500'
|
||||
: 'bg-gray-700'
|
||||
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
(!!partMedia &&
|
||||
partMedia.status !==
|
||||
MediaStatus.BLACKLISTED) ||
|
||||
partRequest ||
|
||||
isSelectedPart(part.id)
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0'
|
||||
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
|
||||
></span>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
className={`flex items-center px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6 ${
|
||||
partMedia?.status === MediaStatus.BLACKLISTED &&
|
||||
'pointer-events-none opacity-50'
|
||||
}`}
|
||||
>
|
||||
<div className="relative h-auto w-10 flex-shrink-0 overflow-hidden rounded-md">
|
||||
<CachedImage
|
||||
src={
|
||||
part.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}`
|
||||
: '/images/overseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
width={600}
|
||||
height={900}
|
||||
/>
|
||||
isSelectedPart(part.id)
|
||||
? 'bg-indigo-500'
|
||||
: 'bg-gray-700'
|
||||
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
|
||||
></span>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={`${
|
||||
!!partMedia ||
|
||||
partRequest ||
|
||||
isSelectedPart(part.id)
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0'
|
||||
} absolute left-0 inline-block h-5 w-5 rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
|
||||
></span>
|
||||
</span>
|
||||
</td>
|
||||
<td className="flex items-center px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
|
||||
<div className="relative h-auto w-10 flex-shrink-0 overflow-hidden rounded-md">
|
||||
<CachedImage
|
||||
src={
|
||||
part.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}`
|
||||
: '/images/overseerr_poster_not_found.png'
|
||||
}
|
||||
alt=""
|
||||
sizes="100vw"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
width={600}
|
||||
height={900}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center pl-2">
|
||||
<div className="text-xs font-medium">
|
||||
{part.releaseDate?.slice(0, 4)}
|
||||
</div>
|
||||
<div className="flex flex-col justify-center pl-2">
|
||||
<div className="text-xs font-medium">
|
||||
{part.releaseDate?.slice(0, 4)}
|
||||
</div>
|
||||
<div className="text-base font-bold">
|
||||
{part.title}
|
||||
</div>
|
||||
<div className="text-base font-bold">
|
||||
{part.title}
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap py-4 pr-2 text-sm leading-5 text-gray-200 md:px-6">
|
||||
{!partMedia && !partRequest && (
|
||||
<Badge>
|
||||
{intl.formatMessage(
|
||||
globalMessages.notrequested
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="whitespace-nowrap py-4 pr-2 text-sm leading-5 text-gray-200 md:px-6">
|
||||
{!partMedia && !partRequest && (
|
||||
<Badge>
|
||||
{intl.formatMessage(globalMessages.notrequested)}
|
||||
</Badge>
|
||||
)}
|
||||
{!partMedia &&
|
||||
partRequest?.status ===
|
||||
MediaRequestStatus.PENDING && (
|
||||
<Badge badgeType="warning">
|
||||
{intl.formatMessage(globalMessages.pending)}
|
||||
</Badge>
|
||||
)}
|
||||
{!partMedia &&
|
||||
partRequest?.status ===
|
||||
MediaRequestStatus.PENDING && (
|
||||
<Badge badgeType="warning">
|
||||
{intl.formatMessage(globalMessages.pending)}
|
||||
</Badge>
|
||||
)}
|
||||
{((!partMedia &&
|
||||
partRequest?.status ===
|
||||
MediaRequestStatus.APPROVED) ||
|
||||
partMedia?.[is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.PROCESSING) && (
|
||||
<Badge badgeType="primary">
|
||||
{intl.formatMessage(globalMessages.requested)}
|
||||
</Badge>
|
||||
)}
|
||||
{partMedia?.[is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.AVAILABLE && (
|
||||
<Badge badgeType="success">
|
||||
{intl.formatMessage(globalMessages.available)}
|
||||
</Badge>
|
||||
)}
|
||||
{partMedia?.status === MediaStatus.BLACKLISTED && (
|
||||
<Badge badgeType="danger">
|
||||
{intl.formatMessage(globalMessages.blacklisted)}
|
||||
</Badge>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{((!partMedia &&
|
||||
partRequest?.status ===
|
||||
MediaRequestStatus.APPROVED) ||
|
||||
partMedia?.[is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.PROCESSING) && (
|
||||
<Badge badgeType="primary">
|
||||
{intl.formatMessage(globalMessages.requested)}
|
||||
</Badge>
|
||||
)}
|
||||
{partMedia?.[is4k ? 'status4k' : 'status'] ===
|
||||
MediaStatus.AVAILABLE && (
|
||||
<Badge badgeType="success">
|
||||
{intl.formatMessage(globalMessages.available)}
|
||||
</Badge>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -33,13 +33,6 @@ const messages = defineMessages('components.Selector', {
|
||||
nooptions: 'No results.',
|
||||
showmore: 'Show More',
|
||||
showless: 'Show Less',
|
||||
searchStatus: 'Select status...',
|
||||
returningSeries: 'Returning Series',
|
||||
planned: 'Planned',
|
||||
inProduction: 'In Production',
|
||||
ended: 'Ended',
|
||||
canceled: 'Canceled',
|
||||
pilot: 'Pilot',
|
||||
});
|
||||
|
||||
type SingleVal = {
|
||||
@@ -211,75 +204,6 @@ export const GenreSelector = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const StatusSelector = ({
|
||||
isMulti,
|
||||
defaultValue,
|
||||
onChange,
|
||||
}: BaseSelectorMultiProps | BaseSelectorSingleProps) => {
|
||||
const intl = useIntl();
|
||||
const [defaultDataValue, setDefaultDataValue] = useState<
|
||||
{ label: string; value: number }[] | null
|
||||
>(null);
|
||||
|
||||
const options = useMemo(
|
||||
() => [
|
||||
{ name: intl.formatMessage(messages.returningSeries), id: 0 },
|
||||
{ name: intl.formatMessage(messages.planned), id: 1 },
|
||||
{ name: intl.formatMessage(messages.inProduction), id: 2 },
|
||||
{ name: intl.formatMessage(messages.ended), id: 3 },
|
||||
{ name: intl.formatMessage(messages.canceled), id: 4 },
|
||||
{ name: intl.formatMessage(messages.pilot), id: 5 },
|
||||
],
|
||||
[intl]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const loadDefaultStatus = async (): Promise<void> => {
|
||||
if (!defaultValue) {
|
||||
return;
|
||||
}
|
||||
const statuses = defaultValue.split('|');
|
||||
|
||||
const statusData = options
|
||||
.filter((opt) => statuses.find((s) => Number(s) === opt.id))
|
||||
.map((o) => ({
|
||||
label: o.name,
|
||||
value: o.id,
|
||||
}));
|
||||
|
||||
setDefaultDataValue(statusData);
|
||||
};
|
||||
|
||||
loadDefaultStatus();
|
||||
}, [defaultValue, options]);
|
||||
|
||||
const loadStatusOptions = async () => {
|
||||
return options
|
||||
.map((result) => ({
|
||||
label: result.name,
|
||||
value: result.id,
|
||||
}))
|
||||
.filter(({ label }) => label.toLowerCase());
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncSelect
|
||||
key={`status-select-${defaultDataValue}`}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
defaultValue={isMulti ? defaultDataValue : defaultDataValue?.[0]}
|
||||
defaultOptions
|
||||
isMulti={isMulti}
|
||||
loadOptions={loadStatusOptions}
|
||||
placeholder={intl.formatMessage(messages.searchStatus)}
|
||||
onChange={(value) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onChange(value as any);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const KeywordSelector = ({
|
||||
isMulti,
|
||||
defaultValue,
|
||||
|
||||
@@ -3,14 +3,13 @@ import Button from '@app/components/Common/Button';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import SensitiveInput from '@app/components/Common/SensitiveInput';
|
||||
import LibraryItem from '@app/components/Settings/LibraryItem';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { JellyfinSettings } from '@server/lib/settings';
|
||||
import { Field, Formik } from 'formik';
|
||||
import getConfig from 'next/config';
|
||||
import { useState } from 'react';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
@@ -62,9 +61,6 @@ const messages = defineMessages('components.Settings', {
|
||||
validationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
validationUrlBaseLeadingSlash: 'URL base must have a leading slash',
|
||||
validationUrlBaseTrailingSlash: 'URL base must not end in a trailing slash',
|
||||
tip: 'Tip',
|
||||
scanbackground:
|
||||
'Scanning will run in the background. You can continue the setup process in the meantime.',
|
||||
});
|
||||
|
||||
interface Library {
|
||||
@@ -82,13 +78,13 @@ interface SyncStatus {
|
||||
}
|
||||
|
||||
interface SettingsJellyfinProps {
|
||||
isSetupSettings?: boolean;
|
||||
showAdvancedSettings?: boolean;
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
onComplete,
|
||||
isSetupSettings,
|
||||
showAdvancedSettings,
|
||||
}) => {
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const toasts = useToasts();
|
||||
@@ -106,7 +102,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
);
|
||||
const intl = useIntl();
|
||||
const { addToast } = useToasts();
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
const JellyfinSettingsSchema = Yup.object().shape({
|
||||
hostname: Yup.string()
|
||||
@@ -288,29 +284,26 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(
|
||||
messages.jellyfinlibraries,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.jellyfinlibraries, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
: intl.formatMessage(messages.jellyfinlibraries, {
|
||||
mediaServerName: 'Jellyfin',
|
||||
})}
|
||||
</h3>
|
||||
<p className="description">
|
||||
{intl.formatMessage(
|
||||
messages.jellyfinlibrariesDescription,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.jellyfinlibrariesDescription, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
: intl.formatMessage(messages.jellyfinlibrariesDescription, {
|
||||
mediaServerName: 'Jellyfin',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="section">
|
||||
@@ -347,10 +340,13 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
<FormattedMessage {...messages.manualscanJellyfin} />
|
||||
</h3>
|
||||
<p className="description">
|
||||
{intl.formatMessage(
|
||||
messages.manualscanDescriptionJellyfin,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.manualscanDescriptionJellyfin, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
: intl.formatMessage(messages.manualscanDescriptionJellyfin, {
|
||||
mediaServerName: 'Jellyfin',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div className="section">
|
||||
@@ -450,26 +446,24 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isSetupSettings && (
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="mr-2">
|
||||
<Badge>{intl.formatMessage(messages.tip)}</Badge>
|
||||
</span>
|
||||
{intl.formatMessage(messages.scanbackground)}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-10 mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(
|
||||
messages.jellyfinSettings,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.jellyfinSettings, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
: intl.formatMessage(messages.jellyfinSettings, {
|
||||
mediaServerName: 'Jellyfin',
|
||||
})}
|
||||
</h3>
|
||||
<p className="description">
|
||||
{intl.formatMessage(
|
||||
messages.jellyfinSettingsDescription,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.jellyfinSettingsDescription, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
: intl.formatMessage(messages.jellyfinSettingsDescription, {
|
||||
mediaServerName: 'Jellyfin',
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<Formik
|
||||
@@ -503,10 +497,12 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
if (!res.ok) throw new Error(res.statusText, { cause: res });
|
||||
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
messages.jellyfinSettingsSuccess,
|
||||
mediaServerFormatValues
|
||||
),
|
||||
intl.formatMessage(messages.jellyfinSettingsSuccess, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'success',
|
||||
@@ -522,10 +518,12 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
}
|
||||
if (errorData?.message === ApiErrorCode.InvalidUrl) {
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
messages.invalidurlerror,
|
||||
mediaServerFormatValues
|
||||
),
|
||||
intl.formatMessage(messages.invalidurlerror, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
@@ -533,10 +531,12 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
);
|
||||
} else {
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
messages.jellyfinSettingsFailure,
|
||||
mediaServerFormatValues
|
||||
),
|
||||
intl.formatMessage(messages.jellyfinSettingsFailure, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
@@ -559,7 +559,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<form className="section" onSubmit={handleSubmit}>
|
||||
{!isSetupSettings && (
|
||||
{showAdvancedSettings && (
|
||||
<>
|
||||
<div className="form-row">
|
||||
<label htmlFor="hostname" className="text-label">
|
||||
@@ -643,7 +643,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isSetupSettings && (
|
||||
{showAdvancedSettings && (
|
||||
<>
|
||||
<div className="form-row">
|
||||
<label htmlFor="urlBase" className="text-label">
|
||||
@@ -710,9 +710,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`actions ${isSetupSettings ? 'mt-0 border-0' : ''}`}
|
||||
>
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
|
||||
@@ -7,7 +7,6 @@ import PageTitle from '@app/components/Common/PageTitle';
|
||||
import Table from '@app/components/Common/Table';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { formatBytes } from '@app/utils/numberHelpers';
|
||||
@@ -58,8 +57,8 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages(
|
||||
'plex-recently-added-scan': 'Plex Recently Added Scan',
|
||||
'plex-full-scan': 'Plex Full Library Scan',
|
||||
'plex-watchlist-sync': 'Plex Watchlist Sync',
|
||||
'jellyfin-full-scan': 'Jellyfin Full Library Scan',
|
||||
'jellyfin-recently-added-scan': 'Jellyfin Recently Added Scan',
|
||||
'jellyfin-full-scan': 'Jellyfin Full Library Scan',
|
||||
'availability-sync': 'Media Availability Sync',
|
||||
'radarr-scan': 'Radarr Scan',
|
||||
'sonarr-scan': 'Sonarr Scan',
|
||||
@@ -82,7 +81,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages(
|
||||
'When enabled in settings, Jellyseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.',
|
||||
imagecachecount: 'Images Cached',
|
||||
imagecachesize: 'Total Cache Size',
|
||||
usersavatars: "Users' Avatars",
|
||||
useravatars: 'User Avatars',
|
||||
}
|
||||
);
|
||||
|
||||
@@ -169,20 +168,6 @@ const SettingsJobs = () => {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const settings = useSettings();
|
||||
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||
messages['jellyfin-recently-added-scan'] = {
|
||||
id: 'jellyfin-recently-added-scan',
|
||||
defaultMessage: 'Emby Recently Added Scan',
|
||||
};
|
||||
}
|
||||
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||
messages['jellyfin-full-scan'] = {
|
||||
id: 'jellyfin-full-scan',
|
||||
defaultMessage: 'Emby Full Library Scan',
|
||||
};
|
||||
}
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
@@ -576,7 +561,7 @@ const SettingsJobs = () => {
|
||||
</tr>
|
||||
<tr>
|
||||
<Table.TD>
|
||||
{intl.formatMessage(messages.usersavatars)} (avatar)
|
||||
{intl.formatMessage(messages.useravatars)} (avatar)
|
||||
</Table.TD>
|
||||
<Table.TD>
|
||||
{intl.formatNumber(
|
||||
|
||||
@@ -5,6 +5,7 @@ import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import getConfig from 'next/config';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.Settings', {
|
||||
@@ -25,6 +26,7 @@ type SettingsLayoutProps = {
|
||||
|
||||
const SettingsLayout = ({ children }: SettingsLayoutProps) => {
|
||||
const intl = useIntl();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const settings = useSettings();
|
||||
const settingsRoutes: SettingsRoute[] = [
|
||||
{
|
||||
@@ -87,11 +89,7 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
|
||||
function getAvailableMediaServerName() {
|
||||
return intl.formatMessage(messages.menuJellyfinSettings, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: undefined,
|
||||
publicRuntimeConfig.JELLYFIN_TYPE === 'emby' ? 'Emby' : 'Jellyfin',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { MainSettings } from '@server/lib/settings';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import getConfig from 'next/config';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
@@ -41,20 +42,12 @@ const SettingsUsers = () => {
|
||||
mutate: revalidate,
|
||||
} = useSWR<MainSettings>('/api/v1/settings/main');
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
const mediaServerFormatValues = {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
@@ -128,10 +121,16 @@ const SettingsUsers = () => {
|
||||
<label htmlFor="localLogin" className="checkbox-label">
|
||||
{intl.formatMessage(messages.localLogin)}
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(
|
||||
messages.localLoginTip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{intl.formatMessage(messages.localLoginTip, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.JELLYFIN
|
||||
? 'Jellyfin'
|
||||
: 'Emby',
|
||||
})}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
@@ -147,15 +146,25 @@ const SettingsUsers = () => {
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="newPlexLogin" className="checkbox-label">
|
||||
{intl.formatMessage(
|
||||
messages.newPlexLogin,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{intl.formatMessage(messages.newPlexLogin, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: 'Jellyfin',
|
||||
})}
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(
|
||||
messages.newPlexLoginTip,
|
||||
mediaServerFormatValues
|
||||
)}
|
||||
{intl.formatMessage(messages.newPlexLoginTip, {
|
||||
mediaServerName:
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
: 'Jellyfin',
|
||||
})}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
|
||||
@@ -1,39 +1,32 @@
|
||||
import Button from '@app/components/Common/Button';
|
||||
import Accordion from '@app/components/Common/Accordion';
|
||||
import JellyfinLogin from '@app/components/Login/JellyfinLogin';
|
||||
import PlexLoginButton from '@app/components/PlexLoginButton';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import getConfig from 'next/config';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.Setup', {
|
||||
welcome: 'Welcome to Jellyseerr',
|
||||
signinMessage: 'Get started by signing in',
|
||||
signin: 'Sign in to your account',
|
||||
signinWithJellyfin: 'Enter your Jellyfin details',
|
||||
signinWithEmby: 'Enter your Emby details',
|
||||
signinWithPlex: 'Enter your Plex details',
|
||||
back: 'Go back',
|
||||
signinWithJellyfin: 'Use your {mediaServerName} account',
|
||||
signinWithPlex: 'Use your Plex account',
|
||||
});
|
||||
|
||||
interface LoginWithMediaServerProps {
|
||||
serverType: MediaServerType;
|
||||
onCancel: () => void;
|
||||
onComplete: () => void;
|
||||
onComplete: (onComplete: MediaServerType) => void;
|
||||
}
|
||||
|
||||
const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
|
||||
serverType,
|
||||
onCancel,
|
||||
onComplete,
|
||||
}) => {
|
||||
const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
|
||||
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
|
||||
const [mediaServerType, setMediaServerType] = useState<MediaServerType>(
|
||||
MediaServerType.NOT_CONFIGURED
|
||||
);
|
||||
const { user, revalidate } = useUser();
|
||||
|
||||
const intl = useIntl();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
// Effect that is triggered when the `authToken` comes back from the Plex OAuth
|
||||
// We take the token and attempt to login. If we get a success message, we will
|
||||
// ask swr to revalidate the user which _shouid_ come back with a valid user.
|
||||
@@ -63,60 +56,71 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
onComplete();
|
||||
onComplete(mediaServerType);
|
||||
}
|
||||
}, [user, mediaServerType, onComplete]);
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div>
|
||||
<div className="mb-2 flex justify-center text-xl font-bold">
|
||||
<FormattedMessage {...messages.signin} />
|
||||
<FormattedMessage {...messages.welcome} />
|
||||
</div>
|
||||
<div className="mb-2 flex justify-center pb-6 text-sm">
|
||||
{serverType === MediaServerType.JELLYFIN ? (
|
||||
<FormattedMessage {...messages.signinWithJellyfin} />
|
||||
) : serverType === MediaServerType.EMBY ? (
|
||||
<FormattedMessage {...messages.signinWithEmby} />
|
||||
) : (
|
||||
<FormattedMessage {...messages.signinWithPlex} />
|
||||
)}
|
||||
<FormattedMessage {...messages.signinMessage} />
|
||||
</div>
|
||||
{serverType === MediaServerType.PLEX && (
|
||||
<>
|
||||
<div
|
||||
className="px-10 py-8"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.3)' }}
|
||||
>
|
||||
<PlexLoginButton
|
||||
onAuthToken={(authToken) => {
|
||||
setMediaServerType(MediaServerType.PLEX);
|
||||
setAuthToken(authToken);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Button buttonType="default" onClick={() => onCancel()}>
|
||||
<FormattedMessage {...messages.back} />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{serverType === MediaServerType.JELLYFIN && (
|
||||
<JellyfinLogin
|
||||
initial={true}
|
||||
revalidate={revalidate}
|
||||
serverType={serverType}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
)}
|
||||
{serverType === MediaServerType.EMBY && (
|
||||
<JellyfinLogin
|
||||
initial={true}
|
||||
revalidate={revalidate}
|
||||
serverType={serverType}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
)}
|
||||
<Accordion single atLeastOne>
|
||||
{({ openIndexes, handleClick, AccordionContent }) => (
|
||||
<>
|
||||
<button
|
||||
className={`w-full cursor-default bg-gray-900 py-2 text-center text-sm text-gray-400 transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700 focus:outline-none sm:rounded-t-lg ${
|
||||
openIndexes.includes(0) && 'text-indigo-500'
|
||||
} ${openIndexes.includes(1) && 'border-b border-gray-500'}`}
|
||||
onClick={() => handleClick(0)}
|
||||
>
|
||||
<FormattedMessage {...messages.signinWithPlex} />
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(0)}>
|
||||
<div
|
||||
className="px-10 py-8"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.3)' }}
|
||||
>
|
||||
<PlexLoginButton
|
||||
onAuthToken={(authToken) => {
|
||||
setMediaServerType(MediaServerType.PLEX);
|
||||
setAuthToken(authToken);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
<div>
|
||||
<button
|
||||
className={`w-full cursor-default bg-gray-900 py-2 text-center text-sm text-gray-400 transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700 focus:outline-none ${
|
||||
openIndexes.includes(1)
|
||||
? 'text-indigo-500'
|
||||
: 'sm:rounded-b-lg'
|
||||
}`}
|
||||
onClick={() => handleClick(1)}
|
||||
>
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.signinWithJellyfin, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
: intl.formatMessage(messages.signinWithJellyfin, {
|
||||
mediaServerName: 'Jellyfin',
|
||||
})}
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(1)}>
|
||||
<div
|
||||
className="rounded-b-lg px-10 py-8"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.3)' }}
|
||||
>
|
||||
<JellyfinLogin initial={true} revalidate={revalidate} />
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import EmbyLogo from '@app/assets/services/emby.svg';
|
||||
import JellyfinLogo from '@app/assets/services/jellyfin.svg';
|
||||
import PlexLogo from '@app/assets/services/plex.svg';
|
||||
import AppDataWarning from '@app/components/AppDataWarning';
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import ImageFader from '@app/components/Common/ImageFader';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
@@ -11,30 +9,26 @@ import SettingsPlex from '@app/components/Settings/SettingsPlex';
|
||||
import SettingsServices from '@app/components/Settings/SettingsServices';
|
||||
import SetupSteps from '@app/components/Setup/SetupSteps';
|
||||
import useLocale from '@app/hooks/useLocale';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import SetupLogin from './SetupLogin';
|
||||
|
||||
const messages = defineMessages('components.Setup', {
|
||||
welcome: 'Welcome to Jellyseerr',
|
||||
subtitle: 'Get started by choosing your media server',
|
||||
configjellyfin: 'Configure Jellyfin',
|
||||
configplex: 'Configure Plex',
|
||||
configemby: 'Configure Emby',
|
||||
setup: 'Setup',
|
||||
finish: 'Finish Setup',
|
||||
finishing: 'Finishing…',
|
||||
continue: 'Continue',
|
||||
servertype: 'Choose Server Type',
|
||||
signin: 'Sign In',
|
||||
configuremediaserver: 'Configure Media Server',
|
||||
configureservices: 'Configure Services',
|
||||
tip: 'Tip',
|
||||
scanbackground:
|
||||
'Scanning will run in the background. You can continue the setup process in the meantime.',
|
||||
});
|
||||
|
||||
const Setup = () => {
|
||||
@@ -48,7 +42,6 @@ const Setup = () => {
|
||||
);
|
||||
const router = useRouter();
|
||||
const { locale } = useLocale();
|
||||
const settings = useSettings();
|
||||
|
||||
const finishSetup = async () => {
|
||||
setIsUpdating(true);
|
||||
@@ -83,25 +76,6 @@ const Setup = () => {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (settings.currentSettings.initialized) {
|
||||
router.push('/');
|
||||
}
|
||||
if (
|
||||
settings.currentSettings.mediaServerType !==
|
||||
MediaServerType.NOT_CONFIGURED
|
||||
) {
|
||||
setCurrentStep(3);
|
||||
setMediaServerType(settings.currentSettings.mediaServerType);
|
||||
}
|
||||
}, [
|
||||
settings.currentSettings.mediaServerType,
|
||||
settings.currentSettings.initialized,
|
||||
router,
|
||||
]);
|
||||
|
||||
if (settings.currentSettings.initialized) return <></>;
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen flex-col justify-center bg-gray-900 py-12">
|
||||
<PageTitle title={intl.formatMessage(messages.setup)} />
|
||||
@@ -127,120 +101,58 @@ const Setup = () => {
|
||||
>
|
||||
<SetupSteps
|
||||
stepNumber={1}
|
||||
description={intl.formatMessage(messages.servertype)}
|
||||
description={intl.formatMessage(messages.signin)}
|
||||
active={currentStep === 1}
|
||||
completed={currentStep > 1}
|
||||
/>
|
||||
<SetupSteps
|
||||
stepNumber={2}
|
||||
description={intl.formatMessage(messages.signin)}
|
||||
description={intl.formatMessage(messages.configuremediaserver)}
|
||||
active={currentStep === 2}
|
||||
completed={currentStep > 2}
|
||||
/>
|
||||
<SetupSteps
|
||||
stepNumber={3}
|
||||
description={intl.formatMessage(messages.configuremediaserver)}
|
||||
active={currentStep === 3}
|
||||
completed={currentStep > 3}
|
||||
/>
|
||||
<SetupSteps
|
||||
stepNumber={4}
|
||||
description={intl.formatMessage(messages.configureservices)}
|
||||
active={currentStep === 4}
|
||||
active={currentStep === 3}
|
||||
isLastStep
|
||||
/>
|
||||
</ul>
|
||||
</nav>
|
||||
<div className="mt-10 w-full rounded-md border border-gray-600 bg-gray-800 bg-opacity-50 p-4 text-white">
|
||||
{currentStep === 1 && (
|
||||
<div className="flex flex-col items-center pb-6">
|
||||
<div className="mb-2 flex justify-center text-xl font-bold">
|
||||
{intl.formatMessage(messages.welcome)}
|
||||
</div>
|
||||
<div className="mb-2 flex justify-center pb-6 text-sm">
|
||||
{intl.formatMessage(messages.subtitle)}
|
||||
</div>
|
||||
<div className="grid grid-cols-3">
|
||||
<div className="flex flex-col divide-y divide-gray-600 rounded-l border border-gray-600 py-2">
|
||||
<div className="mb-2 flex flex-1 items-center justify-center py-2 px-2">
|
||||
<JellyfinLogo className="h-10" />
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setMediaServerType(MediaServerType.JELLYFIN);
|
||||
setCurrentStep(2);
|
||||
}}
|
||||
className="button-md relative z-10 inline-flex h-full w-full items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out hover:z-20 hover:border-gray-200 focus:z-20 focus:border-gray-100 focus:outline-none active:border-gray-100"
|
||||
>
|
||||
{intl.formatMessage(messages.configjellyfin)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col divide-y divide-gray-600 border-y border-gray-600 py-2">
|
||||
<div className="mb-2 flex flex-1 items-center justify-center py-2 px-2">
|
||||
<PlexLogo className="h-8" />
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setMediaServerType(MediaServerType.PLEX);
|
||||
setCurrentStep(2);
|
||||
}}
|
||||
className="button-md relative z-10 inline-flex h-full w-full items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out hover:z-20 hover:border-gray-200 focus:z-20 focus:border-gray-100 focus:outline-none active:border-gray-100"
|
||||
>
|
||||
{intl.formatMessage(messages.configplex)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col divide-y divide-gray-600 rounded-r border border-gray-600 py-2">
|
||||
<div className="mb-2 flex flex-1 items-center justify-center py-2 px-2">
|
||||
<EmbyLogo className="h-9" />
|
||||
</div>
|
||||
<div className="px-2 pt-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setMediaServerType(MediaServerType.EMBY);
|
||||
setCurrentStep(2);
|
||||
}}
|
||||
className="button-md relative z-10 inline-flex h-full w-full items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 py-2 text-sm font-medium leading-5 text-white transition duration-150 ease-in-out hover:z-20 hover:border-gray-200 focus:z-20 focus:border-gray-100 focus:outline-none active:border-gray-100"
|
||||
>
|
||||
{intl.formatMessage(messages.configemby)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<SetupLogin
|
||||
serverType={mediaServerType}
|
||||
onCancel={() => {
|
||||
setMediaServerType(MediaServerType.NOT_CONFIGURED);
|
||||
setCurrentStep(1);
|
||||
onComplete={(mServerType) => {
|
||||
setMediaServerType(mServerType);
|
||||
setCurrentStep(2);
|
||||
}}
|
||||
onComplete={() => setCurrentStep(3)}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<div className="p-2">
|
||||
{currentStep === 2 && (
|
||||
<div>
|
||||
{mediaServerType === MediaServerType.PLEX ? (
|
||||
<SettingsPlex
|
||||
onComplete={() => setMediaServerSettingsComplete(true)}
|
||||
/>
|
||||
) : (
|
||||
<SettingsJellyfin
|
||||
isSetupSettings
|
||||
showAdvancedSettings={false}
|
||||
onComplete={() => setMediaServerSettingsComplete(true)}
|
||||
/>
|
||||
)}
|
||||
<div className="mt-4 text-sm text-gray-500">
|
||||
<span className="mr-2">
|
||||
<Badge>{intl.formatMessage(messages.tip)}</Badge>
|
||||
</span>
|
||||
{intl.formatMessage(messages.scanbackground)}
|
||||
</div>
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
disabled={!mediaServerSettingsComplete}
|
||||
onClick={() => setCurrentStep(4)}
|
||||
onClick={() => setCurrentStep(3)}
|
||||
>
|
||||
{intl.formatMessage(messages.continue)}
|
||||
</Button>
|
||||
@@ -249,7 +161,7 @@ const Setup = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
{currentStep === 3 && (
|
||||
<div>
|
||||
<SettingsServices />
|
||||
<div className="actions">
|
||||
|
||||
@@ -9,6 +9,7 @@ import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaStatus } from '@server/constants/media';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { DownloadingItem } from '@server/lib/downloadtracker';
|
||||
import getConfig from 'next/config';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages('components.StatusBadge', {
|
||||
@@ -17,7 +18,6 @@ const messages = defineMessages('components.StatusBadge', {
|
||||
playonplex: 'Play on {mediaServerName}',
|
||||
openinarr: 'Open in {arr}',
|
||||
managemedia: 'Manage {mediaType}',
|
||||
seasonnumber: 'S{seasonNumber}',
|
||||
seasonepisodenumber: 'S{seasonNumber}E{episodeNumber}',
|
||||
});
|
||||
|
||||
@@ -47,6 +47,7 @@ const StatusBadge = ({
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
|
||||
let mediaLink: string | undefined;
|
||||
let mediaLinkDescription: string | undefined;
|
||||
@@ -84,7 +85,7 @@ const StatusBadge = ({
|
||||
mediaLink = plexUrl;
|
||||
mediaLinkDescription = intl.formatMessage(messages.playonplex, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: settings.currentSettings.mediaServerType === MediaServerType.PLEX
|
||||
? 'Plex'
|
||||
@@ -106,34 +107,22 @@ const StatusBadge = ({
|
||||
}
|
||||
}
|
||||
|
||||
const tooltipContent =
|
||||
mediaType === 'tv' &&
|
||||
downloadItem.length > 1 &&
|
||||
downloadItem.every(
|
||||
(item) =>
|
||||
item.downloadId && item.downloadId === downloadItem[0].downloadId
|
||||
) ? (
|
||||
<DownloadBlock
|
||||
downloadItem={downloadItem[0]}
|
||||
title={Array.isArray(title) ? title[0] : title}
|
||||
is4k={is4k}
|
||||
/>
|
||||
) : (
|
||||
<ul>
|
||||
{downloadItem.map((status, index) => (
|
||||
<li
|
||||
key={`dl-status-${status.externalId}-${index}`}
|
||||
className="border-b border-gray-700 last:border-b-0"
|
||||
>
|
||||
<DownloadBlock
|
||||
downloadItem={status}
|
||||
title={Array.isArray(title) ? title[index] : title}
|
||||
is4k={is4k}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
const tooltipContent = (
|
||||
<ul>
|
||||
{downloadItem.map((status, index) => (
|
||||
<li
|
||||
key={`dl-status-${status.externalId}-${index}`}
|
||||
className="border-b border-gray-700 last:border-b-0"
|
||||
>
|
||||
<DownloadBlock
|
||||
downloadItem={status}
|
||||
title={Array.isArray(title) ? title[index] : title}
|
||||
is4k={is4k}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
const badgeDownloadProgress = (
|
||||
<div
|
||||
@@ -188,27 +177,14 @@ const StatusBadge = ({
|
||||
</span>
|
||||
{inProgress && (
|
||||
<>
|
||||
{mediaType === 'tv' &&
|
||||
downloadItem[0].episode &&
|
||||
(downloadItem.length > 1 &&
|
||||
downloadItem.every(
|
||||
(item) =>
|
||||
item.downloadId &&
|
||||
item.downloadId === downloadItem[0].downloadId
|
||||
) ? (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonnumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
))}
|
||||
{mediaType === 'tv' && downloadItem[0].episode && (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
<Spinner className="ml-1 h-3 w-3" />
|
||||
</>
|
||||
)}
|
||||
@@ -254,27 +230,14 @@ const StatusBadge = ({
|
||||
</span>
|
||||
{inProgress && (
|
||||
<>
|
||||
{mediaType === 'tv' &&
|
||||
downloadItem[0].episode &&
|
||||
(downloadItem.length > 1 &&
|
||||
downloadItem.every(
|
||||
(item) =>
|
||||
item.downloadId &&
|
||||
item.downloadId === downloadItem[0].downloadId
|
||||
) ? (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonnumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
))}
|
||||
{mediaType === 'tv' && downloadItem[0].episode && (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
<Spinner className="ml-1 h-3 w-3" />
|
||||
</>
|
||||
)}
|
||||
@@ -320,27 +283,14 @@ const StatusBadge = ({
|
||||
</span>
|
||||
{inProgress && (
|
||||
<>
|
||||
{mediaType === 'tv' &&
|
||||
downloadItem[0].episode &&
|
||||
(downloadItem.length > 1 &&
|
||||
downloadItem.every(
|
||||
(item) =>
|
||||
item.downloadId &&
|
||||
item.downloadId === downloadItem[0].downloadId
|
||||
) ? (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonnumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
))}
|
||||
{mediaType === 'tv' && downloadItem[0].episode && (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
<Spinner className="ml-1 h-3 w-3" />
|
||||
</>
|
||||
)}
|
||||
@@ -360,17 +310,6 @@ const StatusBadge = ({
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
case MediaStatus.BLACKLISTED:
|
||||
return (
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="danger" href={mediaLink}>
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
status: intl.formatMessage(globalMessages.blacklisted),
|
||||
})}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import Spinner from '@app/assets/spinner.svg';
|
||||
import BlacklistModal from '@app/components/BlacklistModal';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
import StatusBadgeMini from '@app/components/Common/StatusBadgeMini';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import RequestModal from '@app/components/RequestModal';
|
||||
import ErrorCard from '@app/components/TitleCard/ErrorCard';
|
||||
import Placeholder from '@app/components/TitleCard/Placeholder';
|
||||
@@ -15,8 +13,6 @@ import { withProperties } from '@app/utils/typeHelpers';
|
||||
import { Transition } from '@headlessui/react';
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
EyeIcon,
|
||||
EyeSlashIcon,
|
||||
MinusCircleIcon,
|
||||
StarIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
@@ -24,7 +20,7 @@ import { MediaStatus } from '@server/constants/media';
|
||||
import type { Watchlist } from '@server/entity/Watchlist';
|
||||
import type { MediaType } from '@server/models/Search';
|
||||
import Link from 'next/link';
|
||||
import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Fragment, useCallback, useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import { mutate } from 'swr';
|
||||
@@ -69,7 +65,7 @@ const TitleCard = ({
|
||||
}: TitleCardProps) => {
|
||||
const isTouch = useIsTouch();
|
||||
const intl = useIntl();
|
||||
const { user, hasPermission } = useUser();
|
||||
const { hasPermission } = useUser();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [currentStatus, setCurrentStatus] = useState(status);
|
||||
const [showDetail, setShowDetail] = useState(false);
|
||||
@@ -78,8 +74,6 @@ const TitleCard = ({
|
||||
const [toggleWatchlist, setToggleWatchlist] = useState<boolean>(
|
||||
!isAddedToWatchlist
|
||||
);
|
||||
const [showBlacklistModal, setShowBlacklistModal] = useState(false);
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Just to get the year from the date
|
||||
if (year) {
|
||||
@@ -100,11 +94,6 @@ const TitleCard = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const closeBlacklistModal = useCallback(
|
||||
() => setShowBlacklistModal(false),
|
||||
[]
|
||||
);
|
||||
|
||||
const onClickWatchlistBtn = async (): Promise<void> => {
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
@@ -177,99 +166,6 @@ const TitleCard = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onClickHideItemBtn = async (): Promise<void> => {
|
||||
setIsUpdating(true);
|
||||
const topNode = cardRef.current;
|
||||
|
||||
if (topNode) {
|
||||
const res = await fetch('/api/v1/blacklist', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tmdbId: id,
|
||||
mediaType,
|
||||
title,
|
||||
user: user?.id,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.BLACKLISTED);
|
||||
} else if (res.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
setIsUpdating(false);
|
||||
closeBlacklistModal();
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onClickShowBlacklistBtn = async (): Promise<void> => {
|
||||
setIsUpdating(true);
|
||||
const topNode = cardRef.current;
|
||||
|
||||
if (topNode) {
|
||||
const res = await fetch('/api/v1/blacklist/' + id, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (res.status === 204) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
|
||||
title,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
setCurrentStatus(MediaStatus.UNKNOWN);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
setIsUpdating(false);
|
||||
};
|
||||
|
||||
const closeModal = useCallback(() => setShowRequestModal(false), []);
|
||||
|
||||
const showRequestButton = hasPermission(
|
||||
@@ -282,15 +178,10 @@ const TitleCard = ({
|
||||
{ type: 'or' }
|
||||
);
|
||||
|
||||
const showHideButton = hasPermission([Permission.MANAGE_BLACKLIST], {
|
||||
type: 'or',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={canExpand ? 'w-full' : 'w-36 sm:w-36 md:w-44'}
|
||||
data-testid="title-card"
|
||||
ref={cardRef}
|
||||
>
|
||||
<RequestModal
|
||||
tmdbId={id}
|
||||
@@ -306,20 +197,6 @@ const TitleCard = ({
|
||||
onUpdating={requestUpdating}
|
||||
onCancel={closeModal}
|
||||
/>
|
||||
<BlacklistModal
|
||||
tmdbId={id}
|
||||
type={
|
||||
mediaType === 'movie'
|
||||
? 'movie'
|
||||
: mediaType === 'collection'
|
||||
? 'collection'
|
||||
: 'tv'
|
||||
}
|
||||
show={showBlacklistModal}
|
||||
onCancel={closeBlacklistModal}
|
||||
onComplete={onClickHideItemBtn}
|
||||
isUpdating={isUpdating}
|
||||
/>
|
||||
<div
|
||||
className={`relative transform-gpu cursor-default overflow-hidden rounded-xl bg-gray-800 bg-cover outline-none ring-1 transition duration-300 ${
|
||||
showDetail
|
||||
@@ -358,7 +235,7 @@ const TitleCard = ({
|
||||
/>
|
||||
<div className="absolute left-0 right-0 flex items-center justify-between p-2">
|
||||
<div
|
||||
className={`pointer-events-none z-40 self-start rounded-full border bg-opacity-80 shadow-md ${
|
||||
className={`pointer-events-none z-40 rounded-full border bg-opacity-80 shadow-md ${
|
||||
mediaType === 'movie' || mediaType === 'collection'
|
||||
? 'border-blue-500 bg-blue-600'
|
||||
: 'border-purple-600 bg-purple-600'
|
||||
@@ -372,8 +249,8 @@ const TitleCard = ({
|
||||
: intl.formatMessage(globalMessages.tvshow)}
|
||||
</div>
|
||||
</div>
|
||||
{showDetail && currentStatus !== MediaStatus.BLACKLISTED && (
|
||||
<div className="flex flex-col gap-1">
|
||||
{showDetail && (
|
||||
<>
|
||||
{toggleWatchlist ? (
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
@@ -392,49 +269,15 @@ const TitleCard = ({
|
||||
<MinusCircleIcon className={'h-3'} />
|
||||
</Button>
|
||||
)}
|
||||
{showHideButton &&
|
||||
currentStatus !== MediaStatus.PROCESSING &&
|
||||
currentStatus !== MediaStatus.AVAILABLE &&
|
||||
currentStatus !== MediaStatus.PARTIALLY_AVAILABLE &&
|
||||
currentStatus !== MediaStatus.PENDING && (
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
className="z-40"
|
||||
buttonSize={'sm'}
|
||||
onClick={() => setShowBlacklistModal(true)}
|
||||
>
|
||||
<EyeSlashIcon className={'h-3'} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{showDetail &&
|
||||
showHideButton &&
|
||||
currentStatus == MediaStatus.BLACKLISTED && (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(
|
||||
globalMessages.removefromBlacklist
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
className="z-40"
|
||||
buttonSize={'sm'}
|
||||
onClick={() => onClickShowBlacklistBtn()}
|
||||
>
|
||||
<EyeIcon className={'h-3'} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{currentStatus && currentStatus !== MediaStatus.UNKNOWN && (
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="pointer-events-none z-40 flex">
|
||||
<StatusBadgeMini
|
||||
status={currentStatus}
|
||||
inProgress={inProgress}
|
||||
shrink
|
||||
/>
|
||||
</div>
|
||||
<div className="pointer-events-none z-40 flex items-center">
|
||||
<StatusBadgeMini
|
||||
status={currentStatus}
|
||||
inProgress={inProgress}
|
||||
shrink
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import RTFresh from '@app/assets/rt_fresh.svg';
|
||||
import RTRotten from '@app/assets/rt_rotten.svg';
|
||||
import Spinner from '@app/assets/spinner.svg';
|
||||
import TmdbLogo from '@app/assets/tmdb_logo.svg';
|
||||
import BlacklistModal from '@app/components/BlacklistModal';
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import CachedImage from '@app/components/Common/CachedImage';
|
||||
@@ -39,7 +38,6 @@ import {
|
||||
ArrowRightCircleIcon,
|
||||
CogIcon,
|
||||
ExclamationTriangleIcon,
|
||||
EyeSlashIcon,
|
||||
FilmIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
@@ -61,9 +59,10 @@ import type { Crew } from '@server/models/common';
|
||||
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
|
||||
import { countries } from 'country-flag-icons';
|
||||
import 'country-flag-icons/3x2/flags.css';
|
||||
import getConfig from 'next/config';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
@@ -127,9 +126,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
const [toggleWatchlist, setToggleWatchlist] = useState<boolean>(
|
||||
!tv?.onUserWatchlist
|
||||
);
|
||||
const [isBlacklistUpdating, setIsBlacklistUpdating] =
|
||||
useState<boolean>(false);
|
||||
const [showBlacklistModal, setShowBlacklistModal] = useState(false);
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { addToast } = useToasts();
|
||||
|
||||
const {
|
||||
@@ -160,11 +157,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
setShowManager(router.query.manage == '1' ? true : false);
|
||||
}, [router.query.manage]);
|
||||
|
||||
const closeBlacklistModal = useCallback(
|
||||
() => setShowBlacklistModal(false),
|
||||
[]
|
||||
);
|
||||
|
||||
const { mediaUrl: plexUrl, mediaUrl4k: plexUrl4k } = useDeepLinks({
|
||||
mediaUrl: data?.mediaInfo?.mediaUrl,
|
||||
mediaUrl4k: data?.mediaInfo?.mediaUrl4k,
|
||||
@@ -308,7 +300,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
?.flatrate ?? [];
|
||||
|
||||
function getAvalaibleMediaServerName() {
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
|
||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||
}
|
||||
|
||||
@@ -320,15 +312,15 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
}
|
||||
|
||||
function getAvalaible4kMediaServerName() {
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.EMBY) {
|
||||
return intl.formatMessage(messages.play, { mediaServerName: 'Emby' });
|
||||
if (publicRuntimeConfig.JELLYFIN_TYPE === 'emby') {
|
||||
return intl.formatMessage(messages.play4k, { mediaServerName: 'Emby' });
|
||||
}
|
||||
|
||||
if (settings.currentSettings.mediaServerType === MediaServerType.PLEX) {
|
||||
return intl.formatMessage(messages.play4k, { mediaServerName: 'Plex' });
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages.play, { mediaServerName: 'Jellyfin' });
|
||||
return intl.formatMessage(messages.play4k, { mediaServerName: 'Jellyfin' });
|
||||
}
|
||||
|
||||
const onClickWatchlistBtn = async (): Promise<void> => {
|
||||
@@ -407,60 +399,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const onClickHideItemBtn = async (): Promise<void> => {
|
||||
setIsBlacklistUpdating(true);
|
||||
|
||||
const res = await fetch('/api/v1/blacklist', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
tmdbId: tv?.id,
|
||||
mediaType: 'tv',
|
||||
title: tv?.name,
|
||||
user: user?.id,
|
||||
}),
|
||||
});
|
||||
|
||||
if (res.status === 201) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistSuccess, {
|
||||
title: tv?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'success', autoDismiss: true }
|
||||
);
|
||||
|
||||
revalidate();
|
||||
} else if (res.status === 412) {
|
||||
addToast(
|
||||
<span>
|
||||
{intl.formatMessage(globalMessages.blacklistDuplicateError, {
|
||||
title: tv?.name,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
})}
|
||||
</span>,
|
||||
{ appearance: 'info', autoDismiss: true }
|
||||
);
|
||||
} else {
|
||||
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
||||
appearance: 'error',
|
||||
autoDismiss: true,
|
||||
});
|
||||
}
|
||||
|
||||
setIsBlacklistUpdating(false);
|
||||
closeBlacklistModal();
|
||||
};
|
||||
|
||||
const showHideButton = hasPermission([Permission.MANAGE_BLACKLIST], {
|
||||
type: 'or',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className="media-page"
|
||||
@@ -487,14 +425,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
</div>
|
||||
)}
|
||||
<PageTitle title={data.name} />
|
||||
<BlacklistModal
|
||||
tmdbId={data.id}
|
||||
type="tv"
|
||||
show={showBlacklistModal}
|
||||
onCancel={closeBlacklistModal}
|
||||
onComplete={onClickHideItemBtn}
|
||||
isUpdating={isBlacklistUpdating}
|
||||
/>
|
||||
<IssueModal
|
||||
onCancel={() => setShowIssueModal(false)}
|
||||
show={showIssueModal}
|
||||
@@ -600,61 +530,40 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="media-actions">
|
||||
{showHideButton &&
|
||||
data?.mediaInfo?.status !== MediaStatus.PROCESSING &&
|
||||
data?.mediaInfo?.status !== MediaStatus.AVAILABLE &&
|
||||
data?.mediaInfo?.status !== MediaStatus.PARTIALLY_AVAILABLE &&
|
||||
data?.mediaInfo?.status !== MediaStatus.PENDING &&
|
||||
data?.mediaInfo?.status !== MediaStatus.BLACKLISTED && (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(globalMessages.addToBlacklist)}
|
||||
>
|
||||
<>
|
||||
{toggleWatchlist ? (
|
||||
<Tooltip content={intl.formatMessage(messages.addtowatchlist)}>
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={() => setShowBlacklistModal(true)}
|
||||
onClick={onClickWatchlistBtn}
|
||||
>
|
||||
<EyeSlashIcon className={'h-3'} />
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<StarIcon className={'h-3 text-amber-300'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.removefromwatchlist)}
|
||||
>
|
||||
<Button
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={onClickDeleteWatchlistBtn}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<MinusCircleIcon className={'h-3'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{data?.mediaInfo?.status !== MediaStatus.BLACKLISTED && (
|
||||
<>
|
||||
{toggleWatchlist ? (
|
||||
<Tooltip content={intl.formatMessage(messages.addtowatchlist)}>
|
||||
<Button
|
||||
buttonType={'ghost'}
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={onClickWatchlistBtn}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<StarIcon className={'h-3 text-amber-300'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
content={intl.formatMessage(messages.removefromwatchlist)}
|
||||
>
|
||||
<Button
|
||||
className="z-40 mr-2"
|
||||
buttonSize={'md'}
|
||||
onClick={onClickDeleteWatchlistBtn}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<Spinner className="h-3" />
|
||||
) : (
|
||||
<MinusCircleIcon className={'h-3'} />
|
||||
)}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
<PlayButton links={mediaLinks} />
|
||||
<RequestButton
|
||||
mediaType="tv"
|
||||
|
||||
@@ -4,8 +4,8 @@ import Modal from '@app/components/Common/Modal';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import getConfig from 'next/config';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
@@ -36,6 +36,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { addToast } = useToasts();
|
||||
const [isImporting, setImporting] = useState(false);
|
||||
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
|
||||
@@ -80,9 +81,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
userCount: createdUsers.length,
|
||||
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
@@ -97,9 +96,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
addToast(
|
||||
intl.formatMessage(messages.importfromJellyfinerror, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
}),
|
||||
{
|
||||
autoDismiss: true,
|
||||
@@ -137,9 +134,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
loading={!data && !error}
|
||||
title={intl.formatMessage(messages.importfromJellyfin, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
})}
|
||||
onOk={() => {
|
||||
importUsers();
|
||||
@@ -156,8 +151,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.newJellyfinsigninenabled, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
strong: (msg: React.ReactNode) => (
|
||||
@@ -251,7 +245,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
<div className="flex items-center">
|
||||
<CachedImage
|
||||
className="h-10 w-10 flex-shrink-0 rounded-full"
|
||||
src={user.thumb}
|
||||
src={`/avatarproxy/${user.thumb}`}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
@@ -283,9 +277,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
<Alert
|
||||
title={intl.formatMessage(messages.noJellyfinuserstoimport, {
|
||||
mediaServerName:
|
||||
settings.currentSettings.mediaServerType === MediaServerType.EMBY
|
||||
? 'Emby'
|
||||
: 'Jellyfin',
|
||||
publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? 'Emby' : 'Jellyfin',
|
||||
})}
|
||||
type="info"
|
||||
/>
|
||||
|
||||
@@ -29,6 +29,7 @@ import { MediaServerType } from '@server/constants/server';
|
||||
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
|
||||
import { hasPermission } from '@server/lib/permissions';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import getConfig from 'next/config';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -89,6 +90,7 @@ const UserList = () => {
|
||||
const intl = useIntl();
|
||||
const router = useRouter();
|
||||
const settings = useSettings();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { addToast } = useToasts();
|
||||
const { user: currentUser, hasPermission: currentHasPermission } = useUser();
|
||||
const [currentSort, setCurrentSort] = useState<Sort>('displayname');
|
||||
@@ -533,8 +535,7 @@ const UserList = () => {
|
||||
>
|
||||
<InboxArrowDownIcon />
|
||||
<span>
|
||||
{settings.currentSettings.mediaServerType ===
|
||||
MediaServerType.EMBY
|
||||
{publicRuntimeConfig.JELLYFIN_TYPE == 'emby'
|
||||
? intl.formatMessage(messages.importfrommediaserver, {
|
||||
mediaServerName: 'Emby',
|
||||
})
|
||||
@@ -635,7 +636,7 @@ const UserList = () => {
|
||||
>
|
||||
<CachedImage
|
||||
className="h-10 w-10 rounded-full object-cover"
|
||||
src={user.avatar}
|
||||
src={`/avatarproxy/${user.avatar}`}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
@@ -689,7 +690,7 @@ const UserList = () => {
|
||||
<Badge badgeType="default">
|
||||
{intl.formatMessage(messages.localuser)}
|
||||
</Badge>
|
||||
) : user.userType === UserType.EMBY ? (
|
||||
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
|
||||
<Badge badgeType="success">
|
||||
{intl.formatMessage(messages.mediaServerUser, {
|
||||
mediaServerName: 'Emby',
|
||||
|
||||
@@ -44,7 +44,7 @@ const ProfileHeader = ({ user, isSettingsPage }: ProfileHeaderProps) => {
|
||||
<div className="relative">
|
||||
<CachedImage
|
||||
className="h-24 w-24 rounded-full bg-gray-600 object-cover ring-1 ring-gray-700"
|
||||
src={user.avatar}
|
||||
src={`/avatarproxy/${user.avatar}`}
|
||||
alt=""
|
||||
width={96}
|
||||
height={96}
|
||||
|
||||
@@ -14,9 +14,9 @@ import globalMessages from '@app/i18n/globalMessages';
|
||||
import ErrorPage from '@app/pages/_error';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
import { ApiErrorCode } from '@server/constants/error';
|
||||
import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import getConfig from 'next/config';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
@@ -43,7 +43,6 @@ const messages = defineMessages(
|
||||
user: 'User',
|
||||
toastSettingsSuccess: 'Settings saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
toastSettingsFailureEmail: 'This email is already taken!',
|
||||
region: 'Discover Region',
|
||||
regionTip: 'Filter content by regional availability',
|
||||
originallanguage: 'Discover Language',
|
||||
@@ -70,6 +69,7 @@ const messages = defineMessages(
|
||||
|
||||
const UserGeneralSettings = () => {
|
||||
const intl = useIntl();
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { addToast } = useToasts();
|
||||
const { locale, setLocale } = useLocale();
|
||||
const [movieQuotaEnabled, setMovieQuotaEnabled] = useState(false);
|
||||
@@ -180,7 +180,7 @@ const UserGeneralSettings = () => {
|
||||
watchlistSyncTv: values.watchlistSyncTv,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error(res.statusText, { cause: res });
|
||||
if (!res.ok) throw new Error();
|
||||
|
||||
if (currentUser?.id === user?.id && setLocale) {
|
||||
setLocale(
|
||||
@@ -195,24 +195,10 @@ const UserGeneralSettings = () => {
|
||||
appearance: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await e.cause?.text();
|
||||
errorData = JSON.parse(errorData);
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
if (errorData?.message === ApiErrorCode.InvalidEmail) {
|
||||
addToast(intl.formatMessage(messages.toastSettingsFailureEmail), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} else {
|
||||
addToast(intl.formatMessage(messages.toastSettingsFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
}
|
||||
addToast(intl.formatMessage(messages.toastSettingsFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
} finally {
|
||||
revalidate();
|
||||
revalidateUser();
|
||||
@@ -243,7 +229,7 @@ const UserGeneralSettings = () => {
|
||||
<Badge badgeType="default">
|
||||
{intl.formatMessage(messages.localuser)}
|
||||
</Badge>
|
||||
) : user?.userType === UserType.EMBY ? (
|
||||
) : publicRuntimeConfig.JELLYFIN_TYPE == 'emby' ? (
|
||||
<Badge badgeType="success">
|
||||
{intl.formatMessage(messages.mediaServerUser, {
|
||||
mediaServerName: 'Emby',
|
||||
|
||||
@@ -55,16 +55,6 @@ const globalMessages = defineMessages('i18n', {
|
||||
noresults: 'No results.',
|
||||
open: 'Open',
|
||||
resolved: 'Resolved',
|
||||
blacklist: 'Blacklist',
|
||||
blacklisted: 'Blacklisted',
|
||||
blacklistSuccess: '<strong>{title}</strong> was successfully blacklisted.',
|
||||
blacklistError: 'Something went wrong try again.',
|
||||
blacklistDuplicateError:
|
||||
'<strong>{title}</strong> has already been blacklisted.',
|
||||
removeFromBlacklistSuccess:
|
||||
'<strong>{title}</strong> was successfully removed from the Blacklist.',
|
||||
addToBlacklist: 'Add to Blacklist',
|
||||
removefromBlacklist: 'Remove from Blacklist',
|
||||
});
|
||||
|
||||
export default globalMessages;
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
"components.Discover.TvGenreSlider.tvgenres": "Gèneres de Sèries",
|
||||
"components.Discover.TvGenreList.seriesgenres": "Gèneres de Sèries",
|
||||
"components.Discover.StudioSlider.studios": "Estudis",
|
||||
"components.Discover.NetworkSlider.networks": "Emissors",
|
||||
"components.Discover.NetworkSlider.networks": "Plataformes",
|
||||
"components.Discover.MovieGenreSlider.moviegenres": "Gèneres de Pel·lícules",
|
||||
"components.Discover.MovieGenreList.moviegenres": "Gèneres de Pel·lícules",
|
||||
"components.Discover.DiscoverTvLanguage.languageSeries": "Sèries en {language}",
|
||||
@@ -397,7 +397,7 @@
|
||||
"components.TvDetails.originaltitle": "Títol original",
|
||||
"components.TvDetails.originallanguage": "Idioma original",
|
||||
"components.TvDetails.nextAirDate": "Pròxima data d'emissió",
|
||||
"components.TvDetails.network": "{networkCount, plural, one {Emissor} other {Emissors}}",
|
||||
"components.TvDetails.network": "{networkCount, plural, one {Plataforma} other {Plataformes}}",
|
||||
"components.TvDetails.firstAirDate": "Primera data d'emissió",
|
||||
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minuts",
|
||||
"components.TvDetails.episodeRuntime": "Duració de l'episodi",
|
||||
@@ -494,7 +494,7 @@
|
||||
"components.Settings.SonarrModal.validationNameRequired": "Heu de proporcionar un nom de servidor",
|
||||
"components.Settings.SonarrModal.validationLanguageProfileRequired": "Heu de seleccionar un perfil d'idioma",
|
||||
"components.Settings.SonarrModal.validationHostnameRequired": "Heu de proporcionar un nom d’amfitrió o una adreça IP vàlides",
|
||||
"components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "L'URL base no pot acabar amb una barra inclinada final",
|
||||
"components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "L'URL base no ha d'acabar amb una barra inclinada final",
|
||||
"components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "L'URL base ha de tenir una barra inclinada",
|
||||
"components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "L'URL no pot acabar amb una barra inclinada final",
|
||||
"components.Settings.SonarrModal.validationApplicationUrl": "Heu de proporcionar un URL vàlid",
|
||||
@@ -1135,7 +1135,7 @@
|
||||
"components.Discover.CreateSlider.needresults": "Cal tenir almenys 1 resultat.",
|
||||
"components.Discover.CreateSlider.nooptions": "Sense resultats.",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Proporciona un ID de categoria TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Proporciona l'ID d'emissor TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Proporciona l'ID de la plataforma TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbstudio": "Proporciona l'ID d'estudi TMDB",
|
||||
"components.Discover.CreateSlider.searchGenres": "Cercar per gènere…",
|
||||
"components.Discover.CreateSlider.searchKeywords": "Cercar per paraules clau…",
|
||||
@@ -1167,7 +1167,7 @@
|
||||
"components.Discover.networks": "Emissors",
|
||||
"components.Discover.resetwarning": "Restablir tots els controls lliscants al valor predeterminat. Això també suprimirà els controls lliscants personalitzats!",
|
||||
"components.Discover.tmdbmoviekeyword": "Paraula clau de pel·lícula TMDB",
|
||||
"components.Discover.tmdbnetwork": "Emissors TMDB",
|
||||
"components.Discover.tmdbnetwork": "Plataformes TMDB",
|
||||
"components.Discover.FilterSlideover.tmdbuserscore": "Puntuació d'usuaris TMDB",
|
||||
"components.Discover.tvgenres": "Gèneres de sèries",
|
||||
"components.Discover.DiscoverTvKeyword.keywordSeries": "Sèries {keywordTitle}",
|
||||
@@ -1241,11 +1241,18 @@
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Sincronització de disponibilitat de contingut",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Serveis de transmissió de pel·lícules TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Serveis de transmissió de TV TMDB",
|
||||
"components.Layout.UserWarnings.emailRequired": "És requereix un n correu electrònic.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Es requereix una contrasenya.",
|
||||
"components.Login.description": "Com que és la primera vegada que inicieu sessió a {applicationName}, es necessita afegir un correu electrònic vàlid.",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Recompte de vots d'usuaris de TMDB",
|
||||
"components.Discover.FilterSlideover.voteCount": "Nombre de vots entre {minValue} i {maxValue}",
|
||||
"components.Layout.UserWarnings.emailInvalid": "El correu electrònic no és vàlid.",
|
||||
"components.Login.credentialerror": "El nom d'usuari o la contrasenya són incorrectes."
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Recompte de vots dels usuaris de TMDB",
|
||||
"components.Discover.FilterSlideover.voteCount": "Número de vots entre {minValue} i {maxValue}",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "So per a les notificacions",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Dispositiu per defecte",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "So per a les notificacions",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Tipus d'Anime",
|
||||
"components.Settings.SonarrModal.seriesType": "Tipus de sèrie",
|
||||
"components.Settings.SonarrModal.tagRequests": "Sol·licituds d'etiquetes",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Dispositiu per defecte",
|
||||
"i18n.collection": "Col·lecció",
|
||||
"components.MovieDetails.imdbuserscore": "Puntuació dels usuaris de IMDB",
|
||||
"components.Settings.RadarrModal.tagRequests": "Sol·licituds d'etiqueta",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Automàticament afegeix una etiqueta addicional amb el nom d'usuari i nom complet del sol·licitant",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Automàticament afegeix una etiqueta addicional amb el nom d'usuari i nom complet del sol·licitant"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,7 @@
|
||||
{
|
||||
"component.BlacklistBlock.blacklistdate": "Blacklisted date",
|
||||
"component.BlacklistBlock.blacklistedby": "Blacklisted By",
|
||||
"component.BlacklistModal.blacklisting": "Blacklisting",
|
||||
"components.AirDateBadge.airedrelative": "Aired {relativeTime}",
|
||||
"components.AirDateBadge.airsrelative": "Airing {relativeTime}",
|
||||
"components.AppDataWarning.dockerVolumeMissingDescription": "The <code>{appDataPath}</code> volume mount was not configured properly. All data will be cleared when the container is stopped or restarted.",
|
||||
"components.Blacklist.blacklistNotFoundError": "<strong>{title}</strong> is not blacklisted.",
|
||||
"components.Blacklist.blacklistSettingsDescription": "Manage blacklisted media.",
|
||||
"components.Blacklist.blacklistdate": "date",
|
||||
"components.Blacklist.blacklistedby": "{date} by {user}",
|
||||
"components.Blacklist.blacklistsettings": "Blacklist Settings",
|
||||
"components.Blacklist.mediaName": "Name",
|
||||
"components.Blacklist.mediaTmdbId": "tmdb Id",
|
||||
"components.Blacklist.mediaType": "Type",
|
||||
"components.CollectionDetails.numberofmovies": "{count} Movies",
|
||||
"components.CollectionDetails.overview": "Overview",
|
||||
"components.CollectionDetails.requestcollection": "Request Collection",
|
||||
@@ -84,7 +73,6 @@
|
||||
"components.Discover.FilterSlideover.releaseDate": "Release Date",
|
||||
"components.Discover.FilterSlideover.runtime": "Runtime",
|
||||
"components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} minute runtime",
|
||||
"components.Discover.FilterSlideover.status": "Status",
|
||||
"components.Discover.FilterSlideover.streamingservices": "Streaming Services",
|
||||
"components.Discover.FilterSlideover.studio": "Studio",
|
||||
"components.Discover.FilterSlideover.tmdbuserscore": "TMDB User Score",
|
||||
@@ -211,7 +199,6 @@
|
||||
"components.LanguageSelector.originalLanguageDefault": "All Languages",
|
||||
"components.Layout.LanguagePicker.displaylanguage": "Display Language",
|
||||
"components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV",
|
||||
"components.Layout.Sidebar.blacklist": "Blacklist",
|
||||
"components.Layout.Sidebar.browsemovies": "Movies",
|
||||
"components.Layout.Sidebar.browsetv": "Series",
|
||||
"components.Layout.Sidebar.dashboard": "Discover",
|
||||
@@ -233,7 +220,6 @@
|
||||
"components.Layout.VersionStatus.streamdevelop": "Jellyseerr Develop",
|
||||
"components.Layout.VersionStatus.streamstable": "Jellyseerr Stable",
|
||||
"components.Login.adminerror": "You must use an admin account to sign in.",
|
||||
"components.Login.back": "Go back",
|
||||
"components.Login.credentialerror": "The username or password is incorrect.",
|
||||
"components.Login.description": "Since this is your first time logging into {applicationName}, you are required to add a valid email address.",
|
||||
"components.Login.email": "Email Address",
|
||||
@@ -249,7 +235,6 @@
|
||||
"components.Login.port": "Port",
|
||||
"components.Login.save": "Add",
|
||||
"components.Login.saving": "Adding…",
|
||||
"components.Login.servertype": "Server Type",
|
||||
"components.Login.signin": "Sign In",
|
||||
"components.Login.signingin": "Signing In…",
|
||||
"components.Login.signinheader": "Sign in to continue",
|
||||
@@ -271,7 +256,6 @@
|
||||
"components.Login.validationhostformat": "Valid URL required",
|
||||
"components.Login.validationhostrequired": "{mediaServerName} URL required",
|
||||
"components.Login.validationpasswordrequired": "You must provide a password",
|
||||
"components.Login.validationservertyperequired": "Please select a server type",
|
||||
"components.Login.validationusernamerequired": "Username required",
|
||||
"components.ManageSlideOver.alltime": "All Time",
|
||||
"components.ManageSlideOver.downloadstatus": "Downloads",
|
||||
@@ -298,7 +282,6 @@
|
||||
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {play} other {plays}}",
|
||||
"components.ManageSlideOver.removearr": "Remove from {arr}",
|
||||
"components.ManageSlideOver.removearr4k": "Remove from 4K {arr}",
|
||||
"components.RequestList.RequestItem.removearr": "Remove from {arr}",
|
||||
"components.ManageSlideOver.tvshow": "series",
|
||||
"components.MediaSlider.ShowMoreCard.seemore": "See More",
|
||||
"components.MovieDetails.MovieCast.fullcast": "Full Cast",
|
||||
@@ -400,12 +383,8 @@
|
||||
"components.PermissionEdit.autorequestMoviesDescription": "Grant permission to automatically submit requests for non-4K movies via Plex Watchlist.",
|
||||
"components.PermissionEdit.autorequestSeries": "Auto-Request Series",
|
||||
"components.PermissionEdit.autorequestSeriesDescription": "Grant permission to automatically submit requests for non-4K series via Plex Watchlist.",
|
||||
"components.PermissionEdit.blacklistedItems": "Blacklist media.",
|
||||
"components.PermissionEdit.blacklistedItemsDescription": "Grant permission to blacklist media.",
|
||||
"components.PermissionEdit.createissues": "Report Issues",
|
||||
"components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.",
|
||||
"components.PermissionEdit.manageblacklist": "Manage Blacklist",
|
||||
"components.PermissionEdit.manageblacklistDescription": "Grant permission to manage blacklisted media.",
|
||||
"components.PermissionEdit.manageissues": "Manage Issues",
|
||||
"components.PermissionEdit.manageissuesDescription": "Grant permission to manage media issues.",
|
||||
"components.PermissionEdit.managerequests": "Manage Requests",
|
||||
@@ -424,8 +403,6 @@
|
||||
"components.PermissionEdit.requestTvDescription": "Grant permission to submit requests for non-4K series.",
|
||||
"components.PermissionEdit.users": "Manage Users",
|
||||
"components.PermissionEdit.usersDescription": "Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.",
|
||||
"components.PermissionEdit.viewblacklistedItems": "View blacklisted media.",
|
||||
"components.PermissionEdit.viewblacklistedItemsDescription": "Grant permission to view blacklisted media.",
|
||||
"components.PermissionEdit.viewissues": "View Issues",
|
||||
"components.PermissionEdit.viewissuesDescription": "Grant permission to view media issues reported by other users.",
|
||||
"components.PermissionEdit.viewrecent": "View Recently Added",
|
||||
@@ -578,16 +555,9 @@
|
||||
"components.ResetPassword.validationpasswordrequired": "You must provide a password",
|
||||
"components.Search.search": "Search",
|
||||
"components.Search.searchresults": "Search Results",
|
||||
"components.Selector.canceled": "Canceled",
|
||||
"components.Selector.ended": "Ended",
|
||||
"components.Selector.inProduction": "In Production",
|
||||
"components.Selector.nooptions": "No results.",
|
||||
"components.Selector.pilot": "Pilot",
|
||||
"components.Selector.planned": "Planned",
|
||||
"components.Selector.returningSeries": "Returning Series",
|
||||
"components.Selector.searchGenres": "Select genres…",
|
||||
"components.Selector.searchKeywords": "Search keywords…",
|
||||
"components.Selector.searchStatus": "Select status...",
|
||||
"components.Selector.searchStudios": "Search studios…",
|
||||
"components.Selector.showless": "Show Less",
|
||||
"components.Selector.showmore": "Show More",
|
||||
@@ -850,7 +820,7 @@
|
||||
"components.Settings.SettingsJobsCache.runnow": "Run Now",
|
||||
"components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr Scan",
|
||||
"components.Settings.SettingsJobsCache.unknownJob": "Unknown Job",
|
||||
"components.Settings.SettingsJobsCache.usersavatars": "Users' Avatars",
|
||||
"components.Settings.SettingsJobsCache.useravatars": "User Avatars",
|
||||
"components.Settings.SettingsLogs.copiedLogMessage": "Copied log message to clipboard.",
|
||||
"components.Settings.SettingsLogs.copyToClipboard": "Copy to Clipboard",
|
||||
"components.Settings.SettingsLogs.extraData": "Additional Data",
|
||||
@@ -970,7 +940,6 @@
|
||||
"components.Settings.address": "Address",
|
||||
"components.Settings.addsonarr": "Add Sonarr Server",
|
||||
"components.Settings.advancedTooltip": "Incorrectly configuring this setting may result in broken functionality",
|
||||
"components.Settings.apiKey": "API key",
|
||||
"components.Settings.cancelscan": "Cancel Scan",
|
||||
"components.Settings.copied": "Copied API key to clipboard.",
|
||||
"components.Settings.currentlibrary": "Current Library: {name}",
|
||||
@@ -1031,7 +1000,6 @@
|
||||
"components.Settings.save": "Save Changes",
|
||||
"components.Settings.saving": "Saving…",
|
||||
"components.Settings.scan": "Sync Libraries",
|
||||
"components.Settings.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
|
||||
"components.Settings.scanning": "Syncing…",
|
||||
"components.Settings.serverLocal": "local",
|
||||
"components.Settings.serverRemote": "remote",
|
||||
@@ -1052,7 +1020,6 @@
|
||||
"components.Settings.tautulliSettings": "Tautulli Settings",
|
||||
"components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.",
|
||||
"components.Settings.timeout": "Timeout",
|
||||
"components.Settings.tip": "Tip",
|
||||
"components.Settings.toastPlexConnecting": "Attempting to connect to Plex…",
|
||||
"components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.",
|
||||
"components.Settings.toastPlexConnectingSuccess": "Plex connection established successfully!",
|
||||
@@ -1073,29 +1040,23 @@
|
||||
"components.Settings.webAppUrlTip": "Optionally direct users to the web app on your server instead of the \"hosted\" web app",
|
||||
"components.Settings.webhook": "Webhook",
|
||||
"components.Settings.webpush": "Web Push",
|
||||
"components.Setup.back": "Go back",
|
||||
"components.Setup.configemby": "Configure Emby",
|
||||
"components.Setup.configjellyfin": "Configure Jellyfin",
|
||||
"components.Setup.configplex": "Configure Plex",
|
||||
"components.Setup.configuremediaserver": "Configure Media Server",
|
||||
"components.Setup.configureservices": "Configure Services",
|
||||
"components.Setup.continue": "Continue",
|
||||
"components.Setup.finish": "Finish Setup",
|
||||
"components.Setup.finishing": "Finishing…",
|
||||
"components.Setup.servertype": "Choose Server Type",
|
||||
"components.Setup.scanbackground": "Scanning will run in the background. You can continue the setup process in the meantime.",
|
||||
"components.Setup.setup": "Setup",
|
||||
"components.Setup.signin": "Sign in to your account",
|
||||
"components.Setup.signin": "Sign In",
|
||||
"components.Setup.signinMessage": "Get started by signing in",
|
||||
"components.Setup.signinWithEmby": "Enter your Emby details",
|
||||
"components.Setup.signinWithJellyfin": "Enter your Jellyfin details",
|
||||
"components.Setup.signinWithPlex": "Enter your Plex details",
|
||||
"components.Setup.subtitle": "Get started by choosing your media server",
|
||||
"components.Setup.signinWithJellyfin": "Use your {mediaServerName} account",
|
||||
"components.Setup.signinWithPlex": "Use your Plex account",
|
||||
"components.Setup.tip": "Tip",
|
||||
"components.Setup.welcome": "Welcome to Jellyseerr",
|
||||
"components.StatusBadge.managemedia": "Manage {mediaType}",
|
||||
"components.StatusBadge.openinarr": "Open in {arr}",
|
||||
"components.StatusBadge.playonplex": "Play on {mediaServerName}",
|
||||
"components.StatusBadge.seasonepisodenumber": "S{seasonNumber}E{episodeNumber}",
|
||||
"components.StatusBadge.seasonnumber": "S{seasonNumber}",
|
||||
"components.StatusBadge.status": "{status}",
|
||||
"components.StatusBadge.status4k": "4K {status}",
|
||||
"components.StatusChecker.appUpdated": "{applicationTitle} Updated",
|
||||
@@ -1234,7 +1195,6 @@
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Saving…",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Series Request Limit",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Something went wrong while saving settings.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailureEmail": "This email is already taken!",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Settings saved successfully!",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.user": "User",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.validationDiscordId": "You must provide a valid Discord user ID",
|
||||
@@ -1314,7 +1274,6 @@
|
||||
"components.UserProfile.seriesrequest": "Series Requests",
|
||||
"components.UserProfile.totalrequests": "Total Requests",
|
||||
"components.UserProfile.unlimited": "Unlimited",
|
||||
"i18n.addToBlacklist": "Add to Blacklist",
|
||||
"i18n.advanced": "Advanced",
|
||||
"i18n.all": "All",
|
||||
"i18n.approve": "Approve",
|
||||
@@ -1322,11 +1281,6 @@
|
||||
"i18n.areyousure": "Are you sure?",
|
||||
"i18n.available": "Available",
|
||||
"i18n.back": "Back",
|
||||
"i18n.blacklist": "Blacklist",
|
||||
"i18n.blacklistDuplicateError": "<strong>{title}</strong> has already been blacklisted.",
|
||||
"i18n.blacklistError": "Something went wrong try again.",
|
||||
"i18n.blacklistSuccess": "<strong>{title}</strong> was successfully blacklisted.",
|
||||
"i18n.blacklisted": "Blacklisted",
|
||||
"i18n.cancel": "Cancel",
|
||||
"i18n.canceling": "Canceling…",
|
||||
"i18n.close": "Close",
|
||||
@@ -1352,8 +1306,6 @@
|
||||
"i18n.pending": "Pending",
|
||||
"i18n.previous": "Previous",
|
||||
"i18n.processing": "Processing",
|
||||
"i18n.removeFromBlacklistSuccess": "<strong>{title}</strong> was successfully removed from the Blacklist.",
|
||||
"i18n.removefromBlacklist": "Remove from Blacklist",
|
||||
"i18n.request": "Request",
|
||||
"i18n.request4k": "Request in 4K",
|
||||
"i18n.requested": "Requested",
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
"components.Settings.SonarrModal.animerootfolder": "Carpeta raíz de anime",
|
||||
"components.Settings.SonarrModal.animequalityprofile": "Perfil de calidad de anime",
|
||||
"components.Settings.SettingsAbout.timezone": "Zona horaria",
|
||||
"components.Settings.SettingsAbout.supportoverseerr": "Apoya a Overseerr",
|
||||
"components.Settings.SettingsAbout.supportoverseerr": "Apoya a Jellyseerr",
|
||||
"components.Settings.SettingsAbout.helppaycoffee": "Ayúdame invitándome a un café",
|
||||
"components.Settings.SettingsAbout.Releases.viewongithub": "Ver en GitHub",
|
||||
"components.Settings.SettingsAbout.Releases.viewchangelog": "Ver registro de cambios",
|
||||
@@ -299,14 +299,14 @@
|
||||
"components.RequestButton.viewrequest": "Ver Solicitud",
|
||||
"components.RequestButton.requestmore4k": "Solicitar más en 4K",
|
||||
"components.RequestButton.requestmore": "Solicitar más",
|
||||
"components.RequestButton.declinerequests": "Rechazar {requestCount, plural, one {Request} other {{requestCount} Requests}}",
|
||||
"components.RequestButton.declinerequests": "Rechazar {requestCount, plural, one {solicitud} other {{requestCount} solicitudes}}",
|
||||
"components.RequestButton.declinerequest4k": "Rechazar Solicitud 4K",
|
||||
"components.RequestButton.declinerequest": "Rechazar Solicitud",
|
||||
"components.RequestButton.decline4krequests": "Rechazar {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
|
||||
"components.RequestButton.approverequests": "Aprobar {requestCount, plural, one {Request} other {{requestCount} Requests}}",
|
||||
"components.RequestButton.decline4krequests": "Rechazar {requestCount, plural, one {solicitud en 4K} other {{requestCount} solicitudes en 4K}}",
|
||||
"components.RequestButton.approverequests": "Aprobar {requestCount, plural, one {solicitud} other {{requestCount} solicitudes}}",
|
||||
"components.RequestButton.approverequest4k": "Aprobar Solicitud 4K",
|
||||
"components.RequestButton.approverequest": "Aprobar Solicitud",
|
||||
"components.RequestButton.approve4krequests": "Aprobar {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
|
||||
"components.RequestButton.approve4krequests": "Aprobar {requestCount, plural, one {petición en 4K} other {requestCount} peticiones en 4K}}",
|
||||
"components.RequestBlock.server": "Servidor de Destino",
|
||||
"components.RequestBlock.rootfolder": "Carpeta Raíz",
|
||||
"components.RequestBlock.profilechanged": "Perfil de Calidad",
|
||||
@@ -656,7 +656,7 @@
|
||||
"components.QuotaSelector.unlimited": "Ilimitadas",
|
||||
"components.MovieDetails.originaltitle": "Título Original",
|
||||
"components.LanguageSelector.originalLanguageDefault": "Todos los Idiomas",
|
||||
"components.LanguageSelector.languageServerDefault": "({language}) por defecto",
|
||||
"components.LanguageSelector.languageServerDefault": "({Language}) por defecto",
|
||||
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {cambio} other {cambios}} por detrás",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Tu cuenta no tiene configurada una contraseña actualmente. Configure una contraseña a continuación para habilitar el acceso como \"usuario local\" utilizando tu dirección de email.",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Esta cuenta de usuario no tiene configurada una contraseña actualmente. Configure una contraseña a continuación para habilitar el acceso como \"usuario local\"",
|
||||
@@ -784,7 +784,7 @@
|
||||
"components.DownloadBlock.estimatedtime": "Estimación de {time}",
|
||||
"components.Settings.Notifications.encryptionOpportunisticTls": "Usa siempre STARTTLS",
|
||||
"components.TvDetails.streamingproviders": "Emisión Actual en",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "({language}) por defecto",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "{{Language}} por defecto",
|
||||
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Para recibir notificaciones web push, Jellyseerr debe servirse mediante HTTPS.",
|
||||
"components.Settings.Notifications.NotificationsWebhook.validationTypes": "Debes seleccionar, al menos, un tipo de notificación",
|
||||
"components.Settings.Notifications.validationTypes": "Debes seleccionar, al menos, un tipo de notificación",
|
||||
@@ -816,10 +816,10 @@
|
||||
"components.Settings.Notifications.encryptionTip": "Normalmente, TLS Implícito usa el puerto 465 y STARTTLS usa el puerto 587",
|
||||
"components.UserList.localLoginDisabled": "El ajuste para <strong>Habilitar el Inicio de Sesión Local</strong> está actualmente deshabilitado.",
|
||||
"components.Settings.SettingsUsers.defaultPermissionsTip": "Permisos iniciales asignados a nuevos usuarios",
|
||||
"components.Settings.SettingsAbout.runningDevelop": "Estás utilizando la rama de <code>desarrollo</code> de Jellyseerr, la cual solo se recomienda para aquellos que contribuyen al desarrollo o al soporte de las pruebas de nuevos desarrollos.",
|
||||
"components.Settings.SettingsAbout.runningDevelop": "Estás utilizando la rama de <code>develop</code> de Jellyseerr, la cual solo se recomienda para aquellos que contribuyen al desarrollo o al soporte de las pruebas de nuevos desarrollos.",
|
||||
"components.StatusBadge.status": "{status}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Cada {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Cada {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Cada {jobScheduleMinutes, plural, one {minuto} other {{jobScheduleMinutes} minutos}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Cada {jobScheduleHours, plural, one {hora} other {{jobScheduleHours} horas}}",
|
||||
"components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Algo fue mal al guardar la tarea programada.",
|
||||
"components.Settings.SettingsJobsCache.editJobSchedule": "Modificar tarea programada",
|
||||
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Nueva frecuencia",
|
||||
@@ -848,7 +848,7 @@
|
||||
"components.IssueDetails.nocomments": "Sin comentarios.",
|
||||
"components.IssueDetails.openedby": "#{issueId} abierta {relativeTime} por {username}",
|
||||
"components.IssueDetails.openin4karr": "Abrir en {arr} 4K",
|
||||
"components.IssueDetails.openinarr": "Abrir en {arr}",
|
||||
"components.IssueDetails.openinarr": "Abierta en {arr}",
|
||||
"components.IssueDetails.play4konplex": "Ver en 4K en {mediaServerName}",
|
||||
"components.IssueDetails.playonplex": "Ver en {mediaServerName}",
|
||||
"components.IssueDetails.problemepisode": "Episodio Afectado",
|
||||
@@ -1193,7 +1193,7 @@
|
||||
"components.RequestList.RequestItem.tvdbid": "Identificador de TheTVDB",
|
||||
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Limpieza de la caché de imágenes",
|
||||
"components.Settings.SettingsJobsCache.imagecache": "Caché de imágenes",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Cada {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Cada {jobScheduleSeconds, plural, one {segundo} other {{jobScheduleSeconds} segundos}}",
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Sincronización de la disponibilidad de medios",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Servicios de streaming de películas TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Servicios de TV en streaming TMDB",
|
||||
@@ -1205,110 +1205,10 @@
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Añadir automáticamente una etiqueta adicional con el nombre de usuario y el nombre para mostrar del solicitante",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Añadir automáticamente una etiqueta adicional con el nombre de usuario y el nombre para mostrar del solicitante",
|
||||
"components.MovieDetails.imdbuserscore": "Puntuación de los usuarios de IMDB",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Se requiere una contraseña.",
|
||||
"components.Login.credentialerror": "El usuario o contraseña es incorrecto.",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.initialsignin": "Conectar",
|
||||
"components.Login.initialsigningin": "Conectando…",
|
||||
"components.Login.emailtooltip": "No es necesario asociar la dirección con su instancia de {mediaServerName}.",
|
||||
"components.Login.saving": "Añadiendo…",
|
||||
"components.Login.title": "Añadir Email",
|
||||
"components.Login.username": "Nombre de usuario",
|
||||
"components.Login.validationEmailFormat": "El email es inválido",
|
||||
"components.Login.validationEmailRequired": "Debes proporcional un email",
|
||||
"components.Login.validationemailformat": "Se requiere de un email válido",
|
||||
"components.Login.validationhostformat": "Se requiere de una URL válida",
|
||||
"components.Login.validationusernamerequired": "Se requiere un nombre de usuario",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Esto eliminará de manera irreversible esta {mediaType} de {arr}, incluyendo todos los archivos.",
|
||||
"i18n.open": "Abierto",
|
||||
"components.MovieDetails.downloadstatus": "Estado de la descarga",
|
||||
"components.MovieDetails.openradarr": "Abrir Película en Radarr",
|
||||
"components.MovieDetails.openradarr4k": "Abrir Película 4K en Radarr",
|
||||
"components.MovieDetails.play": "Reproducir en {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Reproducir 4K en {mediaServerName}",
|
||||
"components.NotificationTypeSelector.issueresolved": "Incidencia Resuelta",
|
||||
"components.NotificationTypeSelector.userissuecommentDescription": "Notificame cuando haya nuevos comentarios en incidencias que haya abierto.",
|
||||
"components.NotificationTypeSelector.userissuecreatedDescription": "Notificame cuando otros usuarios reporten incidencias.",
|
||||
"components.PermissionEdit.viewissues": "Ver incidencias",
|
||||
"components.PermissionEdit.manageissuesDescription": "Dar permiso para administrar incidencias.",
|
||||
"components.PermissionEdit.viewissuesDescription": "Dar permiso para ver incidencias reportadas por otros usuarios.",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Sonido de Notificacion",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Escanear Añadidos Recientemente de Jellyfin",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Escaneo Completo de la libreria de Jellyfin",
|
||||
"components.Settings.jellyfinSettings": "Ajustes de {mediaServerName}",
|
||||
"components.Settings.jellyfinsettings": "Ajustes de {mediaServerName}",
|
||||
"components.Settings.jellyfinSettingsFailure": "Algo salió mal al guardar la configuración de {mediaServerName}.",
|
||||
"components.Settings.jellyfinSettingsSuccess": "¡La configuración de {mediaServerName} se guardó correctamente!",
|
||||
"components.Settings.jellyfinlibraries": "Bibliotecas {mediaServerName}",
|
||||
"components.Settings.jellyfinlibrariesDescription": "La biblioteca {mediaServerName} busca títulos. Haga clic en el botón a continuación si no aparece ninguna biblioteca.",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Normalmente, esto sólo se ejecutará una vez cada 24 horas. Jellyseerr comprobará de forma más agresiva los añadidos recientemente de su servidor {mediaServerName}. ¡Si es la primera vez que configura Jellyseerr, se recomienda un escaneo manual completo de la biblioteca!",
|
||||
"components.Settings.save": "Guardar Cambios",
|
||||
"components.Settings.saving": "Guardando…",
|
||||
"components.Settings.syncing": "Sincronizando",
|
||||
"components.Settings.timeout": "Tiempo agotado",
|
||||
"components.Setup.signinWithPlex": "Usa tu cuenta de Plex",
|
||||
"components.Setup.configuremediaserver": "Configurar servidor multimedia",
|
||||
"components.TitleCard.addToWatchList": "Añadir a lista de seguimiento",
|
||||
"components.TitleCard.watchlistCancel": "Lista de seguimiento para <strong>{title}</strong> cancelada.",
|
||||
"components.TitleCard.watchlistError": "Algo salió mal, intenta de nuevo.",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> añadido correctamente a la lista de seguimiento!",
|
||||
"components.TvDetails.play": "Reproducir en {mediaServerName}",
|
||||
"components.UserList.importfromJellyfin": "Importar Usuarios de {mediaServerName}",
|
||||
"components.UserList.mediaServerUser": "Usuario de {mediaServerName}",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} importado correctamente!",
|
||||
"components.UserList.importfromJellyfinerror": "Se produjo un error al importar usuarios de {mediaServerName}.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Guardar Cambios",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "Email",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Dispositivo Predeterminado",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Sonido de Notificacion",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Fallo al guardar los ajustes de la notificación Pushbullet.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "¡Los ajustes de notificación Pushbullet se han guardado con éxito!",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Token de aplicación API",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Clave de usuario o grupo",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Tu <UsersGroupsLink>identificador de usuario o grupo</UsersGroupsLink> de 30 caracteres",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "¡Se han guardado los ajustes de notificación de Pushover!",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "<ApplicationRegistrationLink>Registrar una aplicación</ApplicationRegistrationLink> para usar con {applicationTitle}",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Debes proporcionar una clave de usuario o grupo válida",
|
||||
"components.Login.validationhostrequired": "{mediaServerName} URL requerida",
|
||||
"i18n.resolved": "Resuelto",
|
||||
"components.UserList.importfrommediaserver": "Importar Usuarios de {mediaServerName}",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Dispositivo Predeterminado",
|
||||
"components.ManageSlideOver.removearr": "Eliminar de {arr}",
|
||||
"components.NotificationTypeSelector.issuereopenedDescription": "Enviar notificación cuando se reabran incidencias.",
|
||||
"components.Layout.UserWarnings.emailRequired": "Se requiere una dirección de email.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Token de Acceso",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Debes proporcionar un token de aplicación válido",
|
||||
"components.Settings.syncJellyfin": "Sincronizar Bibliotecas",
|
||||
"components.Layout.UserWarnings.emailInvalid": "La dirección de correo es inválida.",
|
||||
"components.Login.description": "Como es la primera vez que inicias sesión en {applicationName}, es necesario añadir una dirección de email válida.",
|
||||
"components.Login.save": "Añadir",
|
||||
"components.NotificationTypeSelector.issueresolvedDescription": "Enviar notificación cuando se resuelvan incidencias.",
|
||||
"components.PermissionEdit.manageissues": "Administrar incidencias",
|
||||
"components.PermissionEdit.createissues": "Notificar incidencia",
|
||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Guardando…",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Debes indicar un token de acceso",
|
||||
"components.Login.signinwithjellyfin": "Utiliza tu cuenta de {mediaServerName}",
|
||||
"components.ManageSlideOver.removearr4k": "Eliminar de {arr} 4K",
|
||||
"components.Settings.internalUrl": "URL Interna",
|
||||
"components.TvDetails.play4k": "Reproducir 4K en {mediaServerName}",
|
||||
"components.Setup.signin": "Iniciar Sesión",
|
||||
"components.Setup.signinWithJellyfin": "Utiliza tu cuenta de {mediaServerName}",
|
||||
"components.UserList.noJellyfinuserstoimport": "No hay usuarios de {mediaServerName} que importar.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "Usuario de {mediaServerName}",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Crea un token desde tu <PushbulletSettingsLink>Opciones de Cuenta</PushbulletSettingsLink>",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "No se pudo guardar la configuración de notificaciones de Pushover.",
|
||||
"components.NotificationTypeSelector.userissuereopenedDescription": "Recibir notificaciones cuando se vuelvan a abrir incidencias que haya reportado.",
|
||||
"components.NotificationTypeSelector.userissueresolvedDescription": "Reciba notificaciones cuando se resuelvan las incidencias que haya reportado.",
|
||||
"components.PermissionEdit.createissuesDescription": "Dar permiso para informar incidencias.",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Apoya a Jellyseerr",
|
||||
"components.Settings.Notifications.userEmailRequired": "Requerir email de usuario",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Tipo de Serie Anime",
|
||||
"components.Settings.SonarrModal.seriesType": "Tipo Serie",
|
||||
"components.Settings.jellyfinSettingsDescription": "Opcionalmente, configure los puntos finales internos y externos para su servidor {mediaServerName}. En la mayoría de los casos, la URL externa es diferente a la URL interna. También se puede configurar una URL de restablecimiento de contraseña personalizada para el inicio de sesión de {mediaServerName}, en caso de que desee redirigir a una página de restablecimiento de contraseña diferente.",
|
||||
"components.Settings.jellyfinsettingsDescription": "Configure los ajustes para su servidor {mediaServerName}. {mediaServerName} escanea sus bibliotecas de {mediaServerName} para ver qué contenido está disponible.",
|
||||
"components.Settings.manualscanJellyfin": "Escanear Libreria Manualmente",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Eliminado correctamente de la lista de seguimiento!",
|
||||
"components.UserList.newJellyfinsigninenabled": "La configuración <strong>Habilitar nuevo inicio de sesión de {mediaServerName}</strong> está actualmente habilitada. Los usuarios de {mediaServerName} con acceso a la biblioteca no necesitan ser importados para poder iniciar sesión.",
|
||||
"components.UserProfile.localWatchlist": "Lista de seguimiento de {username}"
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Tipo de anime",
|
||||
"components.Settings.SonarrModal.seriesType": "Tipo de series",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Sonido para las notificaciones",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Dispositivo predeterminado",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Sonido para las notificaciones",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Dispositivo predeterminado"
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"components.Discover.DiscoverTvGenre.genreSeries": "Séries {genre}",
|
||||
"components.Discover.DiscoverTvKeyword.keywordSeries": "{keywordTitle} Séries",
|
||||
"components.Discover.DiscoverTvLanguage.languageSeries": "Séries en {language}",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Votre watchlist",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Votre watchlist Plex",
|
||||
"components.Discover.DiscoverWatchlist.watchlist": "Watchlist Plex",
|
||||
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# Filtre actif} other {# Filtres actifs}}",
|
||||
"components.Discover.FilterSlideover.clearfilters": "Effacer les filtres actifs",
|
||||
@@ -176,7 +176,7 @@
|
||||
"components.Settings.RadarrModal.port": "Port",
|
||||
"components.Settings.RadarrModal.qualityprofile": "Profil de qualité",
|
||||
"components.Settings.RadarrModal.rootfolder": "Dossier racine",
|
||||
"components.Settings.RadarrModal.selectMinimumAvailability": "Sélectionner une disponibilté minimale",
|
||||
"components.Settings.RadarrModal.selectMinimumAvailability": "Sélectionner une disponibilité minimale",
|
||||
"components.Settings.RadarrModal.selectQualityProfile": "Sélectionner un profil qualité",
|
||||
"components.Settings.RadarrModal.selectRootFolder": "Sélectionner un dossier racine",
|
||||
"components.Settings.RadarrModal.server4k": "Serveur 4K",
|
||||
@@ -277,7 +277,7 @@
|
||||
"i18n.tvshows": "Séries",
|
||||
"i18n.unavailable": "Indisponible",
|
||||
"pages.oops": "Oups",
|
||||
"pages.returnHome": "Retourner à l'acceuil",
|
||||
"pages.returnHome": "Retourner à l'accueil",
|
||||
"components.TvDetails.TvCast.fullseriescast": "Casting complet de la série",
|
||||
"components.MovieDetails.MovieCast.fullcast": "Casting complet",
|
||||
"components.Settings.Notifications.emailsettingssaved": "Paramètres de notification par e-mail enregistrés avec succès !",
|
||||
@@ -323,7 +323,7 @@
|
||||
"components.Settings.SettingsAbout.Releases.viewchangelog": "Voir le journal des modifications",
|
||||
"components.Settings.SettingsAbout.Releases.versionChangelog": "Journal des modifications de la version {version}",
|
||||
"components.Settings.SettingsAbout.Releases.releases": "Versions",
|
||||
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Les données de version sont actuellement indisponible.",
|
||||
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Les données de version sont actuellement indisponibles.",
|
||||
"components.Settings.SettingsAbout.Releases.latestversion": "Dernière version",
|
||||
"components.Settings.SettingsAbout.Releases.currentversion": "Actuelle",
|
||||
"components.UserList.importfromplexerror": "Une erreur s'est produite durant l'importation des utilisateurs de Plex.",
|
||||
@@ -1071,7 +1071,7 @@
|
||||
"components.ManageSlideOver.manageModalMedia4k": "Média(s) 4K",
|
||||
"components.ManageSlideOver.markallseasons4kavailable": "Marquer toutes les saisons comme disponibles en 4K",
|
||||
"components.ManageSlideOver.playedby": "Joué par",
|
||||
"components.Settings.validationUrlTrailingSlash": "L'URL ne doit pas ce terminer par un slash",
|
||||
"components.Settings.validationUrlTrailingSlash": "L'URL ne doit pas se terminer par un slash",
|
||||
"components.Settings.externalUrl": "URL externe",
|
||||
"components.Settings.tautulliApiKey": "Clé API",
|
||||
"components.Settings.tautulliSettings": "Paramètres Tautulli",
|
||||
@@ -1252,70 +1252,10 @@
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Ajouter automatiquement un tag supplémentaire avec l'ID utilisateur et le nom d'affichage du demandeur",
|
||||
"i18n.collection": "Collection",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Ajouter automatiquement un tag supplémentaire avec l'ID utilisateur et le nom d'affichage du demandeur",
|
||||
"components.IssueModal.issueVideo": "Vidéo",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Son de notification",
|
||||
"components.Settings.jellyfinSettings": "Paramètres pour {mediaServerName}",
|
||||
"components.Settings.jellyfinSettingsFailure": "Une erreur est survenue lors de l'enregistrement des paramètres pour {mediaServerName}.",
|
||||
"components.Settings.jellyfinSettingsSuccess": "Les paramètres pour {mediaServerName} ont été enregistrés avec succès !",
|
||||
"components.Settings.jellyfinlibraries": "Bibliothèques {mediaServerName}",
|
||||
"components.Settings.jellyfinlibrariesDescription": "Les bibliothèques de {mediaServerName} sont en cours d'analyze. Cliquez sur le bouton ci-dessous si aucune bibliothèque n'est répertoriée.",
|
||||
"components.Settings.jellyfinsettings": "Paramètres pour {mediaServerName}",
|
||||
"components.Settings.manualscanJellyfin": "Analyse manuelle de la bibliothèque",
|
||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||
"components.Settings.save": "Enregistrer les modifications",
|
||||
"components.Settings.saving": "Sauvegarde en cours…",
|
||||
"components.Settings.syncing": "Synchronisation en cours",
|
||||
"components.Setup.signin": "Se connecter",
|
||||
"components.Setup.signinWithPlex": "Utilisez votre compte Plex",
|
||||
"components.StatusBadge.managemedia": "Gérer {mediaType}",
|
||||
"components.StatusBadge.openinarr": "Ouvrir dans {arr}",
|
||||
"components.StatusBadge.playonplex": "Lire sur {mediaServerName}",
|
||||
"components.TitleCard.addToWatchList": "Ajouter à votre watchlist",
|
||||
"components.TitleCard.watchlistCancel": "Watchlist pour <strong>{title}</strong> annulée.",
|
||||
"components.TitleCard.watchlistError": "Une erreur est survenue. Veuillez réessayer.",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> a été ajouté à votre watchlist avec succès !",
|
||||
"components.TvDetails.Season.somethingwentwrong": "Une erreur est survenue lors de la récupération des données de la saison.",
|
||||
"components.TvDetails.manageseries": "Gérer les séries",
|
||||
"components.TvDetails.play": "Jouer sur {mediaServerName}",
|
||||
"components.TvDetails.play4k": "Jouer en 4K sur {mediaServerName}",
|
||||
"components.TvDetails.rtcriticsscore": "Tomatometer sur Rotten Tomatoes",
|
||||
"components.TvDetails.seasonnumber": "Saison {seasonNumber}",
|
||||
"components.TvDetails.seasonstitle": "Saisons",
|
||||
"components.TvDetails.status4k": "{status} 4K",
|
||||
"components.TvDetails.tmdbuserscore": "Score utilisateur sur TMDB",
|
||||
"components.UserList.importfromJellyfin": "Importer les utilisateurs de {mediaServerName}",
|
||||
"components.UserList.importfromJellyfinerror": "Une erreur est survenue lors de l'importation des utilisateurs de {mediaServerName}.",
|
||||
"components.UserList.importfrommediaserver": "Importer les utilisateurs de {mediaServerName}",
|
||||
"components.UserList.noJellyfinuserstoimport": "Il n'y a aucun utilisateur à importer pour {mediaServerName}.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Sauvegarde en cours…",
|
||||
"components.UserProfile.plexwatchlist": "Watchlist Plex",
|
||||
"components.Settings.syncJellyfin": "Synchroniser les bibliothèques",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> a été retiré de votre watchlist avec succès !",
|
||||
"components.IssueModal.issueSubtitles": "Sous-titre",
|
||||
"components.Login.emailtooltip": "L'adresse ne nécessite pas d'être associée avec votre instance {mediaServerName}.",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Appareil par défaut",
|
||||
"components.Settings.Notifications.userEmailRequired": "E-mail utilisateur requis",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Soutenez Jellyseerr",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Scan complet des bibliothèques Jellyfin",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Scan des ajouts récents aux bibliothèques Jellyfin",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Type de série anime",
|
||||
"components.Settings.SonarrModal.seriesType": "Type de série",
|
||||
"components.Settings.internalUrl": "URL interne",
|
||||
"components.Settings.jellyfinsettingsDescription": "Configurez les paramètres de votre serveur {mediaServerName}. {mediaServerName} analyse vos bibliothèques {mediaServerName} pour voir quel contenu est disponible.",
|
||||
"components.Settings.jellyfinSettingsDescription": "Configurez facultativement les URL internes et externes pour votre serveur {mediaServerName}. Dans la plupart des cas, l'URL externe est différente de l'URL interne. Vous pouvez également définir une URL de réinitialisation de mot de passe personnalisée pour la connexion à {mediaServerName}, au cas où vous souhaiteriez rediriger vers une page de réinitialisation de mot de passe différente.",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Normalement, cette tâche est executée qu'une fois toutes les 24 heures. Jellyseerr vérifiera plus agressivement les éléments récemment ajoutés à votre serveur {mediaServerName}. Si c'est la première fois que vous configurez Jellyseerr, une analyse complète manuelle de la bibliothèque est recommandée !",
|
||||
"components.Settings.timeout": "Temps écoulé",
|
||||
"components.Setup.configuremediaserver": "Configurer le serveur multimédia",
|
||||
"components.TvDetails.rtaudiencescore": "Score de l'audience sur Rotten Tomatoes",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "Utilisateur pour {mediaServerName}",
|
||||
"components.Setup.signinWithJellyfin": "Utilisez votre compte {mediaServerName}",
|
||||
"components.UserList.mediaServerUser": "Utilisateur de {mediaServerName}",
|
||||
"components.TvDetails.episodeCount": "{episodeCount, plural, one {# Épisode} other {# Épisodes}}",
|
||||
"components.UserList.newJellyfinsigninenabled": "Le paramètre <strong>Activer la nouvelle connexion à {mediaServerName}</strong> est actuellement activé. Les utilisateurs de {mediaServerName} avec accès à la bibliothèque n'ont pas besoin d'être importés pour se connecter.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "E-mail",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Enregistrer les modifications",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Types d'anime",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Appareil par défaut",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Appareil par défaut",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} importé(s) avec succès !",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Son de notification",
|
||||
"components.UserProfile.localWatchlist": "Watchlist de {username}"
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Son de notification"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,12 @@
|
||||
"components.Discover.discovertv": "Populaire series",
|
||||
"components.Discover.popularmovies": "Populaire films",
|
||||
"components.Discover.populartv": "Populaire series",
|
||||
"components.Discover.recentlyAdded": "Onlangs toegevoegd",
|
||||
"components.Discover.recentlyAdded": "Recent toegevoegd",
|
||||
"components.Discover.recentrequests": "Recente verzoeken",
|
||||
"components.Discover.trending": "Trending",
|
||||
"components.Discover.upcoming": "Verwachte films",
|
||||
"components.Discover.upcomingmovies": "Verwachte films",
|
||||
"components.Layout.SearchInput.searchPlaceholder": "Films en series zoeken",
|
||||
"components.Layout.SearchInput.searchPlaceholder": "Zoek films en series",
|
||||
"components.Layout.Sidebar.dashboard": "Ontdekken",
|
||||
"components.Layout.Sidebar.requests": "Verzoeken",
|
||||
"components.Layout.Sidebar.settings": "Instellingen",
|
||||
@@ -16,7 +16,7 @@
|
||||
"components.Layout.UserDropdown.signout": "Uitloggen",
|
||||
"components.MovieDetails.budget": "Budget",
|
||||
"components.MovieDetails.cast": "Cast",
|
||||
"components.MovieDetails.originallanguage": "Oorspronkelijke taal",
|
||||
"components.MovieDetails.originallanguage": "Originele taal",
|
||||
"components.MovieDetails.overview": "Overzicht",
|
||||
"components.MovieDetails.overviewunavailable": "Overzicht niet beschikbaar.",
|
||||
"components.MovieDetails.recommendations": "Aanbevelingen",
|
||||
@@ -114,7 +114,7 @@
|
||||
"components.Settings.hostname": "Hostnaam of IP-adres",
|
||||
"components.Settings.librariesRemaining": "Resterende bibliotheken: {count}",
|
||||
"components.Settings.manualscan": "Handmatige bibliotheekscan",
|
||||
"components.Settings.manualscanDescription": "Normaliter wordt dit eenmaal per 24 uur uitgevoerd. Jellyseerr zal de lijst met onlangs toegevoegde media op je Plex-server vaker controleren. Als dit de eerste keer is dat je Jellyseerr instelt, wordt aanbevolen eenmalig een handmatige, volledige bibliotheekscan uit te voeren!",
|
||||
"components.Settings.manualscanDescription": "Normaal wordt dit eens elke 24 uur uitgevoerd. Jellyseerr controleert de recent toegevoegde items van je Plex-server agressiever. Als je Plex voor de eerste keer configureert, is een eenmalige handmatige volledige bibliotheekscan aanbevolen!",
|
||||
"components.Settings.menuAbout": "Over",
|
||||
"components.Settings.menuGeneralSettings": "Algemeen",
|
||||
"components.Settings.menuJobs": "Taken en cache",
|
||||
@@ -127,7 +127,7 @@
|
||||
"components.Settings.plexlibraries": "Plex-bibliotheken",
|
||||
"components.Settings.plexlibrariesDescription": "De bibliotheken die Jellyseerr scant voor titels. Stel je Plex-verbinding in en sla ze op. Klik daarna op de onderstaande knop als er geen bibliotheken staan.",
|
||||
"components.Settings.plexsettings": "Plex-instellingen",
|
||||
"components.Settings.plexsettingsDescription": "Configureer de instellingen voor je Plex-server. Jellyseerr scant je Plex-bibliotheken om te zien welke inhoud beschikbaar is.",
|
||||
"components.Settings.plexsettingsDescription": "Configureer de instellingen voor je Plex-server. Jellyseerr scant je Plex-bibliotheken om te zien welke content beschikbaar is.",
|
||||
"components.Settings.port": "Poort",
|
||||
"components.Settings.radarrsettings": "Radarr-instellingen",
|
||||
"components.Settings.sonarrsettings": "Sonarr-instellingen",
|
||||
@@ -137,12 +137,12 @@
|
||||
"components.Setup.configureservices": "Diensten configureren",
|
||||
"components.Setup.continue": "Doorgaan",
|
||||
"components.Setup.finish": "Installatie voltooien",
|
||||
"components.Setup.finishing": "Voltooien…",
|
||||
"components.Setup.finishing": "Bezig met voltooien…",
|
||||
"components.Setup.loginwithplex": "Inloggen met Plex",
|
||||
"components.Setup.signinMessage": "Ga aan de slag door je aan te melden",
|
||||
"components.Setup.signinMessage": "Ga aan de slag door in te loggen met je Plex-account",
|
||||
"components.Setup.welcome": "Welkom bij Jellyseerr",
|
||||
"components.TvDetails.cast": "Cast",
|
||||
"components.TvDetails.originallanguage": "Oorspronkelijke taal",
|
||||
"components.TvDetails.originallanguage": "Originele taal",
|
||||
"components.TvDetails.overview": "Overzicht",
|
||||
"components.TvDetails.overviewunavailable": "Overzicht niet beschikbaar.",
|
||||
"components.TvDetails.recommendations": "Aanbevelingen",
|
||||
@@ -164,7 +164,7 @@
|
||||
"i18n.movies": "Films",
|
||||
"i18n.partiallyavailable": "Deels beschikbaar",
|
||||
"i18n.pending": "In behandeling",
|
||||
"i18n.processing": "Verwerken",
|
||||
"i18n.processing": "Bezig met verwerken",
|
||||
"i18n.tvshows": "Series",
|
||||
"i18n.unavailable": "Niet beschikbaar",
|
||||
"pages.oops": "Oeps",
|
||||
@@ -187,14 +187,14 @@
|
||||
"components.Setup.tip": "Tip",
|
||||
"components.Settings.SonarrModal.testFirstRootFolders": "Test verbinding om hoofdmappen te laden",
|
||||
"components.Settings.SonarrModal.testFirstQualityProfiles": "Test verbinding om kwaliteitsprofielen te laden",
|
||||
"components.Settings.SonarrModal.loadingrootfolders": "Hoofdmappen laden…",
|
||||
"components.Settings.SonarrModal.loadingprofiles": "Kwaliteitsprofielen laden…",
|
||||
"components.Settings.SonarrModal.loadingrootfolders": "Bezig met laden van hoofdmappen…",
|
||||
"components.Settings.SonarrModal.loadingprofiles": "Bezig met laden van kwaliteitsprofielen…",
|
||||
"components.Settings.SettingsAbout.gettingsupport": "Ondersteuning krijgen",
|
||||
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Je moet een minimale beschikbaarheid selecteren",
|
||||
"components.Settings.RadarrModal.testFirstRootFolders": "Test verbinding om hoofdmappen te laden",
|
||||
"components.Settings.RadarrModal.testFirstQualityProfiles": "Test verbinding om kwaliteitsprofielen te laden",
|
||||
"components.Settings.RadarrModal.loadingrootfolders": "Hoofdmappen laden…",
|
||||
"components.Settings.RadarrModal.loadingprofiles": "Kwaliteitsprofielen laden…",
|
||||
"components.Settings.RadarrModal.loadingrootfolders": "Bezig met laden van hoofdmappen…",
|
||||
"components.Settings.RadarrModal.loadingprofiles": "Bezig met laden van kwaliteitsprofielen…",
|
||||
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Versiegegevens zijn momenteel niet beschikbaar.",
|
||||
"components.Settings.SettingsAbout.Releases.latestversion": "Nieuwste",
|
||||
"components.Settings.SettingsAbout.Releases.currentversion": "Huidig",
|
||||
@@ -208,7 +208,7 @@
|
||||
"i18n.retry": "Opnieuw proberen",
|
||||
"i18n.requested": "Aangevraagd",
|
||||
"i18n.failed": "Mislukt",
|
||||
"i18n.deleting": "Verwijderen…",
|
||||
"i18n.deleting": "Bezig met verwijderen…",
|
||||
"i18n.close": "Sluiten",
|
||||
"components.UserList.userdeleteerror": "Er ging iets mis bij het verwijderen van de gebruiker.",
|
||||
"components.UserList.userdeleted": "Gebruiker succesvol verwijderd!",
|
||||
@@ -223,7 +223,7 @@
|
||||
"components.TvDetails.network": "{networkCount, plural, one {Netwerk} other {Netwerken}}",
|
||||
"components.TvDetails.firstAirDate": "Datum eerste uitzending",
|
||||
"components.TvDetails.anime": "Anime",
|
||||
"components.StatusChacker.reloadJellyseerr": "Herladen",
|
||||
"components.StatusChacker.reloadOverseerr": "Herladen",
|
||||
"components.StatusChacker.newversionavailable": "Toepassingsupdate",
|
||||
"components.StatusChacker.newversionDescription": "Jellyseerr is geüpdatet! Klik op de onderstaande knop om de pagina opnieuw te laden.",
|
||||
"components.Settings.toastSettingsSuccess": "Instellingen succesvol opgeslagen!",
|
||||
@@ -303,10 +303,10 @@
|
||||
"components.UserList.create": "Aanmaken",
|
||||
"components.UserList.createlocaluser": "Lokale gebruiker aanmaken",
|
||||
"components.UserList.usercreatedfailed": "Er ging iets mis bij het aanmaken van de gebruiker.",
|
||||
"components.UserList.creating": "Aanmaken…",
|
||||
"components.UserList.creating": "Bezig met aanmaken…",
|
||||
"components.UserList.validationpasswordminchars": "Wachtwoord is te kort; moet minimaal 8 tekens bevatten",
|
||||
"components.UserList.usercreatedsuccess": "Gebruiker succesvol aangemaakt!",
|
||||
"components.UserList.passwordinfodescription": "Stel een applicatie-URL in en schakel e-mailmeldingen in om automatische wachtwoordgeneratie mogelijk te maken.",
|
||||
"components.UserList.passwordinfodescription": "Configureer een applicatie-URL en schakel e-mailmeldingen in om automatische wachtwoordgeneratie mogelijk te maken.",
|
||||
"components.UserList.password": "Wachtwoord",
|
||||
"components.UserList.localuser": "Lokale gebruiker",
|
||||
"components.UserList.email": "E-mailadres",
|
||||
@@ -340,23 +340,23 @@
|
||||
"components.RequestModal.SearchByNameModal.notvdbiddescription": "We kunnen deze serie niet automatisch matchen. Selecteer hieronder de juiste match.",
|
||||
"components.Login.signinwithplex": "Plex-account gebruiken",
|
||||
"components.Login.signinheader": "Log in om verder te gaan",
|
||||
"components.Login.signingin": "Aanmelden…",
|
||||
"components.Login.signingin": "Bezig met inloggen…",
|
||||
"components.Login.signin": "Inloggen",
|
||||
"components.Settings.notificationAgentSettingsDescription": "Meldingsagenten configureren en inschakelen.",
|
||||
"components.PlexLoginButton.signinwithplex": "Inloggen",
|
||||
"components.PlexLoginButton.signingin": "Aanmelden…",
|
||||
"components.PlexLoginButton.signingin": "Bezig met inloggen…",
|
||||
"components.PermissionEdit.advancedrequest": "Geavanceerde aanvragen",
|
||||
"components.PermissionEdit.admin": "Beheerder",
|
||||
"components.UserList.userssaved": "Gebruikersrechten succesvol opgeslagen!",
|
||||
"components.Settings.toastPlexRefreshSuccess": "Serverlijst van Plex succesvol opgehaald!",
|
||||
"components.Settings.toastPlexRefresh": "Bezig met serverlijst ophalen van Plex…",
|
||||
"components.Settings.toastPlexConnecting": "Verbinden met Plex…",
|
||||
"components.Settings.toastPlexConnecting": "Bezig met verbinden met Plex-server…",
|
||||
"components.UserList.bulkedit": "Meerdere bewerken",
|
||||
"components.Settings.toastPlexRefreshFailure": "Kan serverlijst van Plex niet ophalen.",
|
||||
"components.Settings.toastPlexConnectingSuccess": "Succesvol verbonden met Plex-server!",
|
||||
"components.Settings.toastPlexConnectingFailure": "Kan geen verbinding maken met Plex.",
|
||||
"components.Settings.settingUpPlexDescription": "Om Plex in te stellen, kan je de gegevens handmatig invoeren of een server selecteren die is opgehaald van <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Druk op de knop rechts van de vervolgkeuzelijst om de lijst van beschikbare servers op te halen.",
|
||||
"components.Settings.serverpresetRefreshing": "Servers ophalen…",
|
||||
"components.Settings.serverpresetRefreshing": "Bezig met servers ophalen…",
|
||||
"components.Settings.serverpresetManualMessage": "Handmatige configuratie",
|
||||
"components.Settings.serverpresetLoad": "Klik op de knop om de beschikbare servers te laden",
|
||||
"components.Settings.serverpreset": "Server",
|
||||
@@ -431,7 +431,7 @@
|
||||
"components.RequestModal.AdvancedRequester.requestas": "Aanvragen als",
|
||||
"components.Discover.discover": "Ontdekken",
|
||||
"components.Settings.validationApplicationTitle": "Je moet een toepassingstitel opgeven",
|
||||
"components.AppDataWarning.dockerVolumeMissingDescription": "De volumekoppeling <code>{appDataPath}</code> is niet correct geconfigureerd. Alle gegevens zullen worden gewist wanneer de container wordt gestopt of opnieuw wordt gestart.",
|
||||
"components.AppDataWarning.dockerVolumeMissingDescription": "De volumekoppeling <code>{appDataPath}</code> was niet correct geconfigureerd. Alle gegevens zullen worden gewist wanneer de container wordt gestopt of opnieuw wordt gestart.",
|
||||
"components.Settings.validationApplicationUrlTrailingSlash": "URL mag niet eindigen op een schuine streep",
|
||||
"components.Settings.validationApplicationUrl": "Je moet een geldige URL opgeven",
|
||||
"components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL mag niet eindigen op een schuine streep",
|
||||
@@ -520,7 +520,7 @@
|
||||
"components.Layout.UserDropdown.myprofile": "Profiel",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Je moet een geldige gebruikers-ID opgeven",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "Het <FindDiscordIdLink>meercijferige ID-nummer</FindDiscordIdLink> van je gebruikersaccount",
|
||||
"components.CollectionDetails.requestcollection4k": "Collectie aanvragen in 4K",
|
||||
"components.CollectionDetails.requestcollection4k": "Collectie in 4K aanvragen",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Inhoud filteren op regionale beschikbaarheid",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.region": "Regio van Ontdekken",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Inhoud filteren op oorspronkelijke taal",
|
||||
@@ -546,7 +546,7 @@
|
||||
"components.Settings.SettingsJobsCache.download-sync-reset": "Reset download sync",
|
||||
"components.Settings.SettingsJobsCache.download-sync": "Synchronisatie downloads",
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# seizoen} other {# seizoenen}}",
|
||||
"i18n.loading": "Laden…",
|
||||
"i18n.loading": "Bezig met laden…",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Je moet een geldige chat-ID opgeven",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "<TelegramBotLink>Een chat starten</TelegramBotLink>, <GetIdBotLink>@get_id_bot</GetIdBotLink> toevoegen en de opdracht <code>/my_id</code> geven",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Chat-ID",
|
||||
@@ -558,14 +558,14 @@
|
||||
"components.Discover.DiscoverNetwork.networkSeries": "Series van {network}",
|
||||
"components.Discover.DiscoverMovieGenre.genreMovies": "{genre} films",
|
||||
"components.Setup.scanbackground": "Het scannen wordt op de achtergrond uitgevoerd. Je kunt in de tussentijd doorgaan met het installatieproces.",
|
||||
"components.Settings.scanning": "Synchroniseren…",
|
||||
"components.Settings.scanning": "Bezig met synchroniseren…",
|
||||
"components.Settings.scan": "Bibliotheken synchroniseren",
|
||||
"components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr-scan",
|
||||
"components.Settings.SettingsJobsCache.radarr-scan": "Radarr-scan",
|
||||
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex recent toegevoegde scan",
|
||||
"components.Settings.SettingsJobsCache.plex-full-scan": "Plex volledige bibliotheekscan",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "volledige bibliotheekscan Jellyfin",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Scan van 'onlangs toegevoegd' in Jellyfin",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Jellyfin recent toegevoegde scan",
|
||||
"components.Settings.Notifications.validationUrl": "Je moet een geldige URL opgeven",
|
||||
"components.Settings.Notifications.botAvatarUrl": "URL bot-avatar",
|
||||
"components.RequestList.RequestItem.requested": "Aangevraagd",
|
||||
@@ -670,27 +670,27 @@
|
||||
"components.QuotaSelector.unlimited": "Onbeperkt",
|
||||
"i18n.view": "Bekijken",
|
||||
"i18n.tvshow": "Serie",
|
||||
"i18n.testing": "Testen…",
|
||||
"i18n.testing": "Bezig met testen…",
|
||||
"i18n.test": "Test",
|
||||
"i18n.status": "Status",
|
||||
"i18n.showingresults": "<strong>{from}</strong> tot <strong>{to}</strong> van de <strong>{total}</strong> resultaten worden weergegeven",
|
||||
"i18n.saving": "Opslaan…",
|
||||
"i18n.saving": "Bezig met opslaan…",
|
||||
"i18n.save": "Wijzigingen opslaan",
|
||||
"i18n.resultsperpage": "{pageSize} resultaten per pagina weergeven",
|
||||
"i18n.requesting": "Aanvragen…",
|
||||
"i18n.requesting": "Bezig met aanvragen…",
|
||||
"i18n.request4k": "Aanvragen in 4K",
|
||||
"i18n.previous": "Vorige",
|
||||
"i18n.notrequested": "Niet aangevraagd",
|
||||
"i18n.noresults": "Geen resultaten.",
|
||||
"i18n.next": "Volgende",
|
||||
"i18n.movie": "Film",
|
||||
"i18n.canceling": "Annuleren…",
|
||||
"i18n.canceling": "Bezig met annuleren…",
|
||||
"i18n.back": "Terug",
|
||||
"i18n.areyousure": "Weet je het zeker?",
|
||||
"i18n.all": "Alle",
|
||||
"components.RequestModal.QuotaDisplay.requiredquotaUser": "Deze gebruiker heeft nog minstens <strong>{seasons}</strong> {seasons, plural, one {seizoensverzoek} other {seizoensverzoeken}} nodig om deze serie aan te vragen.",
|
||||
"components.TvDetails.originaltitle": "Oorspronkelijke titel",
|
||||
"components.MovieDetails.originaltitle": "Oorspronkelijke titel",
|
||||
"components.TvDetails.originaltitle": "Originele titel",
|
||||
"components.MovieDetails.originaltitle": "Originele titel",
|
||||
"components.LanguageSelector.originalLanguageDefault": "Alle talen",
|
||||
"components.LanguageSelector.languageServerDefault": "Standaard ({language})",
|
||||
"components.Settings.SonarrModal.testFirstTags": "Test de verbinding om labels te laden",
|
||||
@@ -733,9 +733,9 @@
|
||||
"components.RequestModal.pendingapproval": "Je verzoek is in afwachting van goedkeuring.",
|
||||
"components.RequestList.RequestItem.cancelRequest": "Verzoek annuleren",
|
||||
"components.NotificationTypeSelector.notificationTypes": "Meldingtypes",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Er is voor jouw account momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord om in te kunnen loggen als een \"lokale gebruiker\" met je e-mailadres.",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Er is voor jouw account momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord om in te kunnen loggen als een \"lokale gebruiker\" met uw e-mailadres.",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Deze gebruikersaccount heeft momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord zodat deze account in staat is om zich aan te melden als een \"lokale gebruiker\".",
|
||||
"components.Settings.serviceSettingsDescription": "Stel je {serverType}-server(s) hieronder in. Je kunt meerdere {serverType}-servers verbinden, maar slechts twee ervan kunnen als standaard worden gemarkeerd (één niet-4K en één 4K). Beheerders kunnen vóór goedkeuring de server aanpassen die voor nieuwe aanvragen gebruikt wordt.",
|
||||
"components.Settings.serviceSettingsDescription": "Configureer je {serverType} server(s) hieronder. Je kunt meerdere {serverType} servers verbinden, maar slechts twee ervan kunnen als standaard worden gemarkeerd (één niet-4K en één 4K). Beheerders kunnen vóór de goedkeuring de server die gebruikt wordt om nieuwe aanvragen te verwerken aanpassen.",
|
||||
"components.Settings.noDefaultServer": "Ten minste één {serverType} server moet als standaard worden gemarkeerd om {mediaType}verzoeken te kunnen verwerken.",
|
||||
"components.Settings.noDefaultNon4kServer": "Als je slechts één enkele {serverType} server hebt voor zowel niet-4K als 4K-inhoud (of als je alleen 4K-inhoud downloadt), dan moet je {serverType} server <strong>NIET</strong> aangeduid worden als een 4K-server.",
|
||||
"components.Settings.mediaTypeSeries": "serie",
|
||||
@@ -747,7 +747,7 @@
|
||||
"components.Layout.VersionStatus.outofdate": "Verouderd",
|
||||
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} achter",
|
||||
"components.UserList.autogeneratepasswordTip": "Een door de server gegenereerd wachtwoord naar de gebruiker e-mailen",
|
||||
"i18n.retrying": "Opnieuw proberen…",
|
||||
"i18n.retrying": "Bezig met opnieuw proberen…",
|
||||
"components.Settings.serverSecure": "veilig",
|
||||
"components.UserList.usercreatedfailedexisting": "Het opgegeven e-mailadres wordt al gebruikt door een andere gebruiker.",
|
||||
"components.RequestModal.edit": "Verzoek bewerken",
|
||||
@@ -847,7 +847,7 @@
|
||||
"components.NotificationTypeSelector.usermediadeclinedDescription": "Een melding ontvangen wanneer je mediaverzoeken worden geweigerd.",
|
||||
"components.NotificationTypeSelector.usermediaavailableDescription": "Een melding ontvangen wanneer je mediaverzoeken beschikbaar zijn.",
|
||||
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Een melding ontvangen wanneer andere gebruikers nieuwe mediaverzoeken indienen die automatisch worden goedgekeurd.",
|
||||
"components.Settings.SettingsAbout.betawarning": "Dit is BETA-software. Functies kunnen kapot en/of instabiel zijn. Meld eventuele problemen op GitHub!",
|
||||
"components.Settings.SettingsAbout.betawarning": "Dit is BETA software. Functies kunnen kapot en/of instabiel zijn. Meld eventuele problemen op GitHub!",
|
||||
"components.Layout.LanguagePicker.displaylanguage": "Weergavetaal",
|
||||
"components.MovieDetails.showmore": "Meer tonen",
|
||||
"components.MovieDetails.showless": "Minder tonen",
|
||||
@@ -971,7 +971,7 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Je moet een geldige gebruikers- of groepssleutel opgeven",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Instellingen voor Pushbullet-meldingen succesvol opgeslagen!",
|
||||
"components.IssueDetails.playonplex": "Afspelen op {mediaServerName}",
|
||||
"components.IssueDetails.play4konplex": "Afspelen op {mediaServerName} in 4K",
|
||||
"components.IssueDetails.play4konplex": "Afspelen in 4K op {mediaServerName}",
|
||||
"components.IssueDetails.openin4karr": "Openen in 4K {arr}",
|
||||
"components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {aflevering} other {afleveringen}}",
|
||||
"components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {seizoen} other {seizoenen}}",
|
||||
@@ -982,7 +982,7 @@
|
||||
"components.NotificationTypeSelector.adminissuereopenedDescription": "Ontvang een melding wanneer problemen door andere gebruikers opnieuw worden ingediend.",
|
||||
"components.NotificationTypeSelector.issuereopenedDescription": "Stuur meldingen wanneer problemen opnieuw worden ingediend.",
|
||||
"components.NotificationTypeSelector.userissuereopenedDescription": "Ontvang een bericht wanneer problemen die jij hebt gemeld, opnieuw worden ingediend.",
|
||||
"components.RequestModal.requestseasons4k": "{seasonCount} {seasonCount, plural, one {seizoen} other {seizoenen}} aanvragen in 4K",
|
||||
"components.RequestModal.requestseasons4k": "{seasonCount} {seasonCount, plural, one {seizoen} other {seizoenen}} in 4K aanvragen",
|
||||
"components.RequestModal.requestmovies": "{count} {count, plural, one {film} other {films}} aanvragen",
|
||||
"components.RequestModal.selectmovies": "Film(s) selecteren",
|
||||
"components.MovieDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}",
|
||||
@@ -998,7 +998,7 @@
|
||||
"components.Settings.Notifications.NotificationsGotify.agentenabled": "Agent inschakelen",
|
||||
"components.Settings.Notifications.NotificationsGotify.gotifysettingssaved": "Instellingen voor meldingen Gotify succesvol opgeslagen!",
|
||||
"components.Settings.Notifications.NotificationsGotify.token": "Toepassingstoken",
|
||||
"i18n.importing": "Importeren…",
|
||||
"i18n.importing": "Bezig met importeren…",
|
||||
"components.Settings.Notifications.NotificationsGotify.gotifysettingsfailed": "Instellingen voor meldingen Gotify niet opgeslagen.",
|
||||
"components.Settings.Notifications.NotificationsGotify.toastGotifyTestFailed": "Testmelding Gotify niet verzonden.",
|
||||
"components.Settings.Notifications.NotificationsGotify.toastGotifyTestSuccess": "Testmelding Gotify verzonden!",
|
||||
@@ -1012,7 +1012,7 @@
|
||||
"components.UserList.newplexsigninenabled": "De instelling <strong>Nieuwe Plex-aanmelding inschakelen</strong> is momenteel ingeschakeld. Plex-gebruikers met bibliotheektoegang hoeven niet te worden geïmporteerd om in te loggen.",
|
||||
"components.ManageSlideOver.manageModalAdvanced": "Geavanceerd",
|
||||
"components.ManageSlideOver.alltime": "Altijd",
|
||||
"components.ManageSlideOver.markallseasons4kavailable": "Alle seizoenen markeren als beschikbaar in 4K",
|
||||
"components.ManageSlideOver.markallseasons4kavailable": "Alle seizoenen als beschikbaar in 4K markeren",
|
||||
"components.ManageSlideOver.opentautulli": "In Tautulli openen",
|
||||
"components.ManageSlideOver.pastdays": "Afgelopen {days, number} dagen",
|
||||
"components.ManageSlideOver.playedby": "Afgespeeld door",
|
||||
@@ -1046,8 +1046,8 @@
|
||||
"components.UserProfile.emptywatchlist": "Media die zijn toegevoegd aan je <PlexWatchlistSupportLink>Plex Kijklijst</PlexWatchlistSupportLink> verschijnen hier.",
|
||||
"components.MovieDetails.digitalrelease": "Digitale release",
|
||||
"i18n.restartRequired": "Opnieuw opstarten vereist",
|
||||
"components.PermissionEdit.viewrecentDescription": "Toestemming geven om de lijst met onlangs toegevoegde media weer te geven.",
|
||||
"components.PermissionEdit.viewrecent": "Onlangs toegevoegd weergeven",
|
||||
"components.PermissionEdit.viewrecentDescription": "Toestemming geven om de lijst met recent toegevoegde media te bekijken.",
|
||||
"components.PermissionEdit.viewrecent": "Recent toegevoegd bekijken",
|
||||
"components.Settings.deleteServer": "{serverType}-server verwijderen",
|
||||
"components.StatusChecker.appUpdated": "{applicationTitle} bijgewerkt",
|
||||
"components.RequestList.RequestItem.tmdbid": "TMDB ID",
|
||||
@@ -1072,8 +1072,8 @@
|
||||
"components.TvDetails.seasonnumber": "Seizoen {seasonNumber}",
|
||||
"components.TvDetails.Season.somethingwentwrong": "Er ging iets mis bij het ophalen van de seizoensgegevens.",
|
||||
"components.TvDetails.seasonstitle": "Seizoenen",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Jouw kijklijst",
|
||||
"components.Discover.plexwatchlist": "Jouw kijklijst",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Je Plex-kijklijst",
|
||||
"components.Discover.plexwatchlist": "Je Plex Kijklijst",
|
||||
"components.MovieDetails.physicalrelease": "Fysieke release",
|
||||
"components.PermissionEdit.autorequest": "Automatisch aanvragen",
|
||||
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Plex Kijklijst synchroniseren",
|
||||
@@ -1099,7 +1099,7 @@
|
||||
"components.TvDetails.manageseries": "Serie beheren",
|
||||
"components.MovieDetails.managemovie": "Film beheren",
|
||||
"components.MovieDetails.reportissue": "Probleem melden",
|
||||
"components.PermissionEdit.autorequestMoviesDescription": "Toestemming geven om niet-4K films in je Plex-kijklijst automatisch aan te vragen.",
|
||||
"components.PermissionEdit.autorequestMoviesDescription": "Toestemming geven om niet-4K films in je Plex Kijklijst automatisch aan te vragen.",
|
||||
"components.PermissionEdit.autorequestSeries": "Series automatisch aanvragen",
|
||||
"components.PermissionEdit.autorequestMovies": "Films automatisch aanvragen",
|
||||
"components.Settings.experimentalTooltip": "Deze instelling inschakelen, kan leiden tot onverwacht gedrag van de toepassing",
|
||||
@@ -1152,8 +1152,8 @@
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Verwijderen",
|
||||
"components.Discover.resetfailed": "Er is iets fout gegaan bij het resetten van de instellingen van Ontdekken.",
|
||||
"components.Discover.PlexWatchlistSlider.emptywatchlist": "Media die zijn toegevoegd aan je <PlexWatchlistSupportLink>Plex Kijklijst</PlexWatchlistSupportLink> verschijnen hier.",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Jouw kijklijst",
|
||||
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Onlangs toegevoegd",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Je Plex Kijklijst",
|
||||
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Recent toegevoegd",
|
||||
"components.Discover.networks": "Netwerken",
|
||||
"components.Discover.CreateSlider.searchStudios": "Studio's zoeken…",
|
||||
"components.Discover.CreateSlider.starttyping": "Begin met typen om te zoeken.",
|
||||
@@ -1184,8 +1184,8 @@
|
||||
"components.Discover.DiscoverMovies.sortPopularityAsc": "Populariteit oplopend",
|
||||
"components.Discover.DiscoverMovies.sortPopularityDesc": "Populariteit aflopend",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Releasedatum oplopend",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Titel oplopend (A-Z)",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Titel aflopend (Z-A)",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Titel (A-Z) oplopend",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Titel (Z-A) aflopend",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "TMDB-beoordeling oplopend",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "TMDB-beoordeling aflopend",
|
||||
"components.Discover.DiscoverSliderEdit.deletefail": "Slider verwijderen mislukt.",
|
||||
@@ -1202,7 +1202,7 @@
|
||||
"components.Discover.FilterSlideover.from": "Van",
|
||||
"components.Discover.FilterSlideover.genres": "Genres",
|
||||
"components.Discover.FilterSlideover.keywords": "Trefwoorden",
|
||||
"components.Discover.FilterSlideover.originalLanguage": "Oorspronkelijke taal",
|
||||
"components.Discover.FilterSlideover.originalLanguage": "Originele taal",
|
||||
"components.Discover.FilterSlideover.ratingText": "Beoordelingen tussen {minValue} en {maxValue}",
|
||||
"components.Discover.FilterSlideover.releaseDate": "Releasedatum",
|
||||
"components.Discover.FilterSlideover.runtime": "Duur",
|
||||
@@ -1262,87 +1262,18 @@
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Synchronisatie van mediabeschikbaarheid",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Streamingdiensten voor films TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Streamingdiensten voor series TMDB",
|
||||
"components.Login.validationhostrequired": "{mediaServerName}-URL vereist",
|
||||
"components.Layout.UserWarnings.emailInvalid": "E-mailadres is ongeldig.",
|
||||
"components.Login.description": "Aangezien dit de eerste keer is dat je je aanmeldt bij {applicationName}, dien je een geldig e-mailadres op te geven.",
|
||||
"components.Login.saving": "Toevoegen…",
|
||||
"components.ManageSlideOver.removearr": "Verwijderen van {arr}",
|
||||
"components.Settings.RadarrModal.tagRequests": "Aanvragen taggen",
|
||||
"components.MovieDetails.openradarr4k": "Film openen in 4K-Radarr",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Automatisch een extra label toevoegen met de gebruikers-id en weergavenaam van de aanvrager",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Serietype anime",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Automatisch een extra label toevoegen met de gebruikers-id en weergavenaam van de aanvrager",
|
||||
"components.Settings.internalUrl": "Interne URL",
|
||||
"components.Settings.jellyfinsettings": "{mediaServerName}-instellingen",
|
||||
"components.Settings.jellyfinlibrariesDescription": "De {mediaServerName}-bibliotheken die op titels worden gescand. Klik op onderstaande knop als er geen bibliotheken in de lijst staan.",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Normaliter wordt dit eenmaal per 24 uur uitgevoerd. Jellyseerr zal de lijst met onlangs toegevoegde media op je {mediaServerName}-server vaker controleren. Als dit de eerste keer is dat je Jellyseerr instelt, wordt aanbevolen eenmalig een handmatige, volledige bibliotheekscan uit te voeren!",
|
||||
"components.Settings.save": "Wijzigingen opslaan",
|
||||
"components.Settings.syncJellyfin": "Bibliotheken synchoniseren",
|
||||
"components.TvDetails.play": "Afspelen op {mediaServerName}",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Aantal gebruikersstemmen TMDB",
|
||||
"components.Login.save": "Toevoegen",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Hiermee wordt deze {mediaType} onomkeerbaar verwijderd van {arr}, inclusief alle bestanden.",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Apparaatstandaard",
|
||||
"components.Settings.Notifications.userEmailRequired": "Gebruikerse-mail vereisen",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Jellyseerr ondersteunen",
|
||||
"components.Settings.SonarrModal.seriesType": "Serietype",
|
||||
"components.Settings.jellyfinSettings": "{mediaServerName}-instellingen",
|
||||
"components.Setup.configuremediaserver": "Mediaserver instellen",
|
||||
"components.TvDetails.play4k": "Afspelen op {mediaServerName} in 4K",
|
||||
"components.UserList.mediaServerUser": "{mediaServerName}-gebruiker",
|
||||
"components.UserList.noJellyfinuserstoimport": "Er zijn geen {mediaServerName}-gebruikers om te importeren.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "E-mail",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Apparaatstandaard",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Meldingsgeluid",
|
||||
"components.Login.signinwithjellyfin": "{mediaServerName}-account gebruiken",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Aantal stemmen TMDB-gebruikers",
|
||||
"components.Discover.FilterSlideover.voteCount": "Aantal stemmen tussen {minValue} en {maxValue}",
|
||||
"components.Layout.UserWarnings.emailRequired": "Een e-mailadres is vereist.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Een wachtwoord is vereist.",
|
||||
"components.Login.credentialerror": "Gebruikersnaam of wachtwoord is onjuist.",
|
||||
"components.Login.emailtooltip": "Het adres hoeft niet gelieerd te zijn aan je {mediaServerName}-instantie.",
|
||||
"components.Login.host": "{mediaServerName}-URL",
|
||||
"components.Login.initialsignin": "Verbinden",
|
||||
"components.Login.initialsigningin": "Verbinden…",
|
||||
"components.Login.title": "E-mail toevoegen",
|
||||
"components.Login.username": "Gebruikersnaam",
|
||||
"components.Login.validationEmailRequired": "Je moet een e-mailadres opgeven",
|
||||
"components.Login.validationEmailFormat": "Ongeldig e-mailadres",
|
||||
"components.Login.validationemailformat": "Geldig e-mailadres vereist",
|
||||
"components.Login.validationhostformat": "Geldige URL vereist",
|
||||
"components.Login.validationusernamerequired": "Gebruikersnaam vereist",
|
||||
"components.ManageSlideOver.removearr4k": "Verwijderen van 4K-{arr}",
|
||||
"components.MovieDetails.downloadstatus": "Downloadstatus",
|
||||
"components.MovieDetails.imdbuserscore": "Gebruikersbeoordeling IMDB",
|
||||
"components.MovieDetails.openradarr": "Film openen in Radarr",
|
||||
"components.MovieDetails.play": "Afspelen op {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Afspelen op {mediaServerName} in 4K",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Meldingsgeluid",
|
||||
"components.Settings.SonarrModal.tagRequests": "Aanvragen taggen",
|
||||
"components.Settings.jellyfinSettingsFailure": "Er is iets misgegaan bij het opslaan van de {mediaServerName}-instellingen.",
|
||||
"components.Settings.jellyfinSettingsSuccess": "{mediaServerName}-instellingen opgeslagen!",
|
||||
"components.Settings.jellyfinlibraries": "{mediaServerName}-bibliotheken",
|
||||
"components.Settings.manualscanJellyfin": "Handmatige bibliotheekscan",
|
||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||
"components.Settings.saving": "Opslaan…",
|
||||
"components.Settings.syncing": "Synchroniseren",
|
||||
"components.Settings.timeout": "Time-out",
|
||||
"components.Setup.signin": "Aanmelden",
|
||||
"components.Setup.signinWithJellyfin": "{mediaServerName}-account gebruiken",
|
||||
"components.TitleCard.addToWatchList": "Toevoegen aan kijklijst",
|
||||
"components.TitleCard.watchlistError": "Er is iets misgegaan. Probeer het opnieuw.",
|
||||
"components.UserList.importfromJellyfin": "{mediaServerName}-gebruikers importeren",
|
||||
"components.UserList.importfromJellyfinerror": "Er is iets misgegaan bij het importeren van {mediaServerName}-gebruikers.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "{mediaServerName}-gebruiker",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Wijzigingen opslaan",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Opslaan…",
|
||||
"components.Settings.RadarrModal.tagRequests": "Tagverzoeken",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Voeg automatisch een extra tag toe met de gebruikers-ID en weergavenaam van de aanvrager",
|
||||
"components.MovieDetails.imdbuserscore": "Gebruikersscore IMDB",
|
||||
"components.Settings.SonarrModal.tagRequests": "Tagverzoeken",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Voeg automatisch een extra tag toe met de gebruikers-ID en weergavenaam van de aanvrager",
|
||||
"i18n.collection": "Collectie",
|
||||
"components.UserProfile.localWatchlist": "Kijklijst van {username}",
|
||||
"components.Setup.signinWithPlex": "Plex-account gebruiken",
|
||||
"components.Settings.jellyfinSettingsDescription": "Optioneel, configureer de interne en externe eindpunten voor uw {mediaServerName} server. In de meeste gevallen verschilt de externe URL met de interne URL. Een aangepaste wachtwoord reset URL kan ook gebruikt worden voor de {mediaServerName} login, voor het geval dat u doorverwezen wilt worden naar een andere wachtwoord reset pagina.",
|
||||
"components.Settings.jellyfinsettingsDescription": "Configureer de instellingen voor uw {mediaServerName} server. {mediaServerName} scanned uw {mediaServerName} bibliotheken om te zien welke content beschikbaar is.",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Is succesvol verwijderd van de kijklijst!",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> succesvol toegevoegd aan de kijklijst!",
|
||||
"components.TitleCard.watchlistCancel": "kijklijst voor <strong>{title}</strong> is geannuleerd.",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} succesvol geimporteerd!",
|
||||
"components.UserList.newJellyfinsigninenabled": "De <strong>Gebruik Nieuwe {mediaServerName} Login</strong> instelling staat momenteel aan. {mediaServerName} gebruikers met toegang tot de bibliotheek, hoeven niet geïmporteerd te worden om in te kunnen loggen."
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Meldingsgeluid",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Apparaatstandaard",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Serietype anime",
|
||||
"components.Settings.SonarrModal.seriesType": "Serietype",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Meldingsgeluid",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Apparaatstandaard"
|
||||
}
|
||||
|
||||
@@ -1061,212 +1061,196 @@
|
||||
"components.MovieDetails.rtaudiencescore": "Ocena Rotten Tomatoes",
|
||||
"components.MovieDetails.rtcriticsscore": "Tomatometer Rotten Tomatoes",
|
||||
"components.MovieDetails.tmdbuserscore": "Ocena użytkowników TMDB",
|
||||
"components.Discover.CreateSlider.addSlider": "Dodaj suwak",
|
||||
"components.Discover.CreateSlider.addcustomslider": "Utwórz niestandardowy suwak",
|
||||
"components.Discover.CreateSlider.addfail": "Nie udało się utworzyć nowego suwaka.",
|
||||
"components.Discover.CreateSlider.addsuccess": "Stworzony nowy suwak i zapisano dostosowywania odkrywania.",
|
||||
"components.Discover.CreateSlider.editSlider": "Edytuj suwak",
|
||||
"components.Discover.CreateSlider.editfail": "Nie udało się edytować suwaka.",
|
||||
"components.Discover.CreateSlider.needresults": "Musisz mieć przynajmniej 1 wynik.",
|
||||
"components.Discover.CreateSlider.nooptions": "Brak wyników.",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Podaj ID gatunku TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Podaj ID stacji TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbsearch": "Podaj zapytanie wyszukiwania",
|
||||
"components.Discover.CreateSlider.providetmdbstudio": "Podaj ID studia TMDB",
|
||||
"components.Discover.CreateSlider.searchGenres": "Szukaj gatunków…",
|
||||
"components.Discover.CreateSlider.searchStudios": "Wyszukaj studia…",
|
||||
"components.Discover.CreateSlider.slidernameplaceholder": "Nazwa suwaka",
|
||||
"components.Discover.CreateSlider.starttyping": "Zacznij pisać aby wyszukać.",
|
||||
"components.Discover.CreateSlider.validationDatarequired": "Należy wprowadzić wartość danych.",
|
||||
"components.Discover.CreateSlider.validationTitlerequired": "Należy podać tytuł.",
|
||||
"components.Discover.DiscoverMovieKeyword.keywordMovies": "Filmy: {keywordTitle}",
|
||||
"components.Discover.DiscoverMovies.discovermovies": "Filmy",
|
||||
"components.Discover.DiscoverMovies.sortPopularityAsc": "Popularność rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortPopularityDesc": "Popularność malejąco",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Data wydania malejąco",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Tytuł (A-Z) rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Tytuł (Z-A) malejąco",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "Ocena TMDB rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "Ocena TMDB malejąco",
|
||||
"components.Discover.DiscoverSliderEdit.deletefail": "Nie udało się usunąć suwaka.",
|
||||
"components.Discover.DiscoverSliderEdit.deletesuccess": "Pomyślnie usunięto suwak.",
|
||||
"components.Discover.DiscoverSliderEdit.enable": "Przełącz widoczność",
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Usuń",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Data premiery rosnąco",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Data premiery malejąco",
|
||||
"components.Discover.DiscoverTv.sortPopularityAsc": "Popularność rosnąco",
|
||||
"components.Discover.DiscoverTv.sortPopularityDesc": "Popularność malejąco",
|
||||
"components.Discover.DiscoverTv.sortTitleAsc": "Tytuł (A-Z) rosnąco",
|
||||
"components.Discover.DiscoverTv.sortTitleDesc": "Tytuł (Z-A) malejąco",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingDesc": "Ocena TMDB malejąco",
|
||||
"components.Discover.FilterSlideover.clearfilters": "Wyczyść aktywne filtry",
|
||||
"components.Discover.FilterSlideover.filters": "Filtry",
|
||||
"components.Discover.FilterSlideover.firstAirDate": "Data pierwszego wyemitowania",
|
||||
"components.Discover.FilterSlideover.from": "Od",
|
||||
"components.Discover.FilterSlideover.genres": "Gatunki",
|
||||
"components.Discover.FilterSlideover.keywords": "Słowa kluczowe",
|
||||
"components.Discover.FilterSlideover.ratingText": "Oceny między {minValue} a {maxValue}",
|
||||
"components.Discover.CreateSlider.providetmdbkeywordid": "Podaj ID słowa kluczowego TMDB",
|
||||
"components.Discover.CreateSlider.searchKeywords": "Wyszukaj słowa kluczowe…",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Data wydania rosnąco",
|
||||
"components.Discover.DiscoverTv.discovertv": "Seriale",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "Ocena TMDB rosnąco",
|
||||
"components.Discover.DiscoverTvKeyword.keywordSeries": "Seriale: {keywordTitle}",
|
||||
"components.Discover.FilterSlideover.originalLanguage": "Oryginalny język",
|
||||
"components.Discover.FilterSlideover.releaseDate": "Data wydania",
|
||||
"components.Discover.FilterSlideover.runtime": "Długość",
|
||||
"components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} minut trwania",
|
||||
"components.Discover.FilterSlideover.streamingservices": "Serwisy streamingowe",
|
||||
"components.Discover.FilterSlideover.studio": "Studio",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Liczba głosów użytkowników TMDB",
|
||||
"components.Discover.FilterSlideover.to": "Do",
|
||||
"components.Discover.FilterSlideover.voteCount": "Liczba głosów między {minValue} a {maxValue}",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Twoja lista obserwowanych Plex",
|
||||
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Niedawno dodane",
|
||||
"components.Discover.createnewslider": "Dodaj nowy suwak",
|
||||
"components.Discover.customizediscover": "Dostosuj Odkryj",
|
||||
"components.Discover.PlexWatchlistSlider.emptywatchlist": "Media dodane do twojej <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink> zostaną wyświetlone tutaj.",
|
||||
"components.Discover.emptywatchlist": "Media dodane do twojej <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink> zostaną wyświetlone tutaj.",
|
||||
"components.Discover.tmdbstudio": "Studio TMDB",
|
||||
"components.Discover.tmdbtvkeyword": "Słowo kluczowe serialu TMDB",
|
||||
"components.Discover.tmdbtvgenre": "Gatunek serialu TMDB",
|
||||
"components.Discover.tmdbsearch": "Wyszukiwanie TMDB",
|
||||
"components.Discover.FilterSlideover.tmdbuserscore": "Ocena użytkownika TMDB",
|
||||
"components.Discover.moviegenres": "Gatunki filmu",
|
||||
"components.Discover.networks": "Kanały",
|
||||
"components.Discover.CreateSlider.editsuccess": "Edytowano suwak i zapisano ustawienia personalizacji.",
|
||||
"components.Discover.stopediting": "Zakończ edytowanie",
|
||||
"components.Discover.studios": "Studia",
|
||||
"components.DownloadBlock.formattedTitle": "{title}: Sezon {seasonNumber} Odcinek {episodeNumber}",
|
||||
"components.Discover.tvgenres": "Gatunki seriali",
|
||||
"components.Discover.updatefailed": "Coś poszło nie tak podczas aktualizacji ustawień personalizacji wykrywania.",
|
||||
"components.Discover.updatesuccess": "Zaktualizowano ustawienia personalizacji wykrywania.",
|
||||
"components.Discover.resettodefault": "Przywróć domyślne",
|
||||
"components.Discover.resetfailed": "Coś poszło nie tak podczas resetowania ustawień personalizacji wykrywania.",
|
||||
"components.Discover.resetsuccess": "Pomyślnie zresetowano ustawienia wykrywania.",
|
||||
"components.Discover.tmdbmoviegenre": "Gatunek filmu TMDB",
|
||||
"components.Layout.Sidebar.browsetv": "Seriale",
|
||||
"components.Layout.UserWarnings.emailInvalid": "Adres e-mail jest nieprawidłowy.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Wymagane jest podanie hasła.",
|
||||
"components.Login.credentialerror": "Nazwa użytkownika lub hasło są nieprawidłowe.",
|
||||
"components.Login.emailtooltip": "Adres nie musi być powiązany z instancją {mediaServerName}.",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.initialsignin": "Połącz",
|
||||
"components.Login.initialsigningin": "Łączenie…",
|
||||
"components.Login.save": "Reklama",
|
||||
"components.Login.saving": "Dodaję…",
|
||||
"components.Login.signinwithjellyfin": "Użyj swojego konta {mediaServerName}",
|
||||
"components.Login.title": "Dodaj e-mail",
|
||||
"components.Login.username": "Nazwa użytkownika",
|
||||
"components.Login.validationEmailRequired": "Musisz podać adres e-mail",
|
||||
"components.Login.validationemailformat": "Wymagany poprawny adres e-mail",
|
||||
"components.Login.validationhostformat": "Wymagany poprawny adres URL",
|
||||
"components.Login.validationhostrequired": "{mediaServerName} URL wymagany",
|
||||
"components.Login.validationusernamerequired": "Wymagana nazwa użytkownika",
|
||||
"components.ManageSlideOver.removearr4k": "Usuń z 4k {arr}",
|
||||
"components.MovieDetails.downloadstatus": "Status pobierania",
|
||||
"components.MovieDetails.openradarr4k": "Otwórz film 4k w Radarr",
|
||||
"components.MovieDetails.play": "Odtwórz na {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Odtwórz w 4K na {mediaServerName}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Bieżąca częstotliwość",
|
||||
"components.TvDetails.seasonnumber": "Sezon {seasonNumber}",
|
||||
"components.TvDetails.seasonstitle": "Sezony",
|
||||
"components.Settings.SettingsJobsCache.imagecache": "Pamięć podręczna obrazów",
|
||||
"components.PermissionEdit.viewrecent": "Wyświetl ostatnio dodane",
|
||||
"components.PermissionEdit.viewrecentDescription": "Zezwolenie na wyświetlanie listy ostatnio dodanych multimediów.",
|
||||
"components.RequestBlock.approve": "Zatwierdź żądanie",
|
||||
"components.RequestBlock.decline": "Odrzuć żądanie",
|
||||
"components.RequestBlock.delete": "Usuń żądanie",
|
||||
"components.RequestBlock.lastmodifiedby": "Ostatnio modyfikowany przez",
|
||||
"components.RequestBlock.requestdate": "Data żądania",
|
||||
"components.RequestBlock.requestedby": "Żądany przez",
|
||||
"components.PermissionEdit.viewrecentDescription": "Przyznaj uprawnienia do przeglądania listy ostatnio dodanych multimediów.",
|
||||
"components.TitleCard.cleardata": "Wyczyść dane",
|
||||
"components.RequestList.RequestItem.tmdbid": "Identyfikator TMDB",
|
||||
"components.RequestList.RequestItem.tvdbid": "Identyfikator TVDB",
|
||||
"components.TitleCard.tmdbid": "Identyfikator TMDB",
|
||||
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Synchronizacja listy obserwowanych Plex",
|
||||
"components.TitleCard.mediaerror": "Nie znaleziono {mediaType}",
|
||||
"components.TitleCard.tvdbid": "Identyfikator TVDB",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Automatyczne zamawianie filmów z <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink>",
|
||||
"components.PermissionEdit.autorequestSeriesDescription": "Udziel zgody na automatyczne przesyłanie próśb dotyczących multimediów innych niż 4K za pośrednictwem listy obserwowanych Plex.",
|
||||
"components.PermissionEdit.viewwatchlists": "Wyświetlanie list obserwacyjnych Plex",
|
||||
"components.PermissionEdit.viewwatchlistsDescription": "Przyznaj uprawnienia do przeglądania list obserwowanych Plex innych użytkowników.",
|
||||
"components.RequestCard.tmdbid": "Identyfikator TMDB",
|
||||
"components.RequestCard.tvdbid": "Identyfikator TVDB",
|
||||
"components.Settings.SettingsLogs.viewdetails": "Zobacz szczegóły",
|
||||
"components.Settings.restartrequiredTooltip": "Overseerr musi zostać ponownie uruchomiony, aby zmiany tego ustawienia zaczęły obowiązywać",
|
||||
"components.TvDetails.reportissue": "Zgłoś problem",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Automatyczna prośba o serial",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Automatyczne zamawianie filmów z <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.plexwatchlist": "Lista obserwowanych Plex",
|
||||
"components.RequestCard.cancelrequest": "Anuluj prośbę",
|
||||
"components.RequestCard.declinerequest": "Odrzuć prośbę",
|
||||
"components.RequestCard.approverequest": "Zatwierdź prośbę",
|
||||
"components.DownloadBlock.formattedTitle": "{title}: Sezon {seasonNumber} Odcinek {episodeNumber}",
|
||||
"components.RequestBlock.approve": "Zatwierdź prośbę",
|
||||
"components.RequestBlock.decline": "Odrzuć prośbę",
|
||||
"components.RequestBlock.delete": "Usuń prośbę",
|
||||
"components.RequestBlock.edit": "Edytuj prośbę",
|
||||
"components.RequestBlock.lastmodifiedby": "Ostatnio zmodyfikowane przez",
|
||||
"components.RequestBlock.requestdate": "Data złożenia prośby",
|
||||
"components.RequestBlock.requestedby": "Prośba zgłoszona przez",
|
||||
"components.RequestCard.editrequest": "Edytuj prośbę",
|
||||
"components.RequestCard.unknowntitle": "Nieznany tytuł",
|
||||
"components.RequestModal.requestcollection4ktitle": "Poproś o kolekcję w 4K",
|
||||
"components.RequestModal.requestcollectiontitle": "Poproś o kolekcję",
|
||||
"components.RequestModal.requestmovie4ktitle": "Poproś o film w 4K",
|
||||
"components.RequestModal.requestmovietitle": "Poproś o film",
|
||||
"components.RequestModal.requestseries4ktitle": "Poproś o serial w 4K",
|
||||
"components.RequestModal.requestseriestitle": "Poproś o serial",
|
||||
"components.Selector.searchGenres": "Wybierz gatunki…",
|
||||
"components.Selector.searchKeywords": "Wyszukaj słowa kluczowe…",
|
||||
"components.Selector.searchStudios": "Szukaj studiów…",
|
||||
"components.Selector.showless": "Pokaż mniej",
|
||||
"components.Selector.starttyping": "Rozpocznij wpisywanie, aby wyszukać.",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Ostatnio dodane skanowanie Jellyfin",
|
||||
"components.Settings.SettingsMain.apikey": "Klucz API",
|
||||
"components.Settings.SettingsMain.applicationTitle": "Nazwa aplikacji",
|
||||
"components.Settings.SettingsMain.applicationurl": "URL aplikacji",
|
||||
"components.Settings.SettingsMain.csrfProtection": "Włącz ochronę CSRF",
|
||||
"components.Settings.SettingsMain.csrfProtectionHoverTip": "NIE włączaj tego ustawienia, jeśli nie rozumiesz, co robisz!",
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Ustawienie dostępu do zewnętrznego API tylko do odczytu (wymaga HTTPS)",
|
||||
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Czyszczenie pamięci podręcznej obrazów",
|
||||
"components.Settings.SettingsJobsCache.imagecacheDescription": "Po włączeniu w ustawieniach Overseerr będzie pośredniczyć i buforować obrazy ze wstępnie skonfigurowanych źródeł zewnętrznych. Obrazy z pamięci podręcznej są zapisywane w folderze konfiguracji. Możesz znaleźć pliki w <code>{appDataPath}/cache/images</code>.",
|
||||
"components.Settings.SettingsJobsCache.imagecachecount": "Obrazy zapisane w pamięci podręcznej",
|
||||
"components.Settings.SettingsJobsCache.imagecachesize": "Całkowity rozmiar pamięci podręcznej",
|
||||
"components.Settings.advancedTooltip": "Nieprawidłowe skonfigurowanie tego ustawienia może spowodować nieprawidłowe działanie",
|
||||
"components.Settings.experimentalTooltip": "Włączenie tego ustawienia może spowodować nieoczekiwane zachowanie aplikacji",
|
||||
"components.TvDetails.rtaudiencescore": "Ocena publiczności Rotten Tomatoes",
|
||||
"components.TvDetails.rtcriticsscore": "Rotten Tomatoes Tomatometr",
|
||||
"components.TvDetails.status4k": "4K {status}",
|
||||
"components.TvDetails.tmdbuserscore": "Ocena użytkowników TMDB",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Filmy z próśb automatycznych",
|
||||
"components.UserProfile.emptywatchlist": "W tym miejscu pojawią się multimedia dodane do <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink>.",
|
||||
"components.RequestCard.unknowntitle": "Nieznany tytuł",
|
||||
"components.RequestList.RequestItem.unknowntitle": "Nieznany tytuł",
|
||||
"components.StatusBadge.playonplex": "Odtwórz na Plex",
|
||||
"components.StatusBadge.seasonepisodenumber": "S{seasonNumber}E{episodeNumber}",
|
||||
"components.TvDetails.Season.noepisodes": "Lista odcinków jest niedostępna.",
|
||||
"components.TvDetails.Season.somethingwentwrong": "Coś poszło nie tak podczas pobierania danych o sezonie.",
|
||||
"components.TvDetails.manageseries": "Zarządzaj serialem",
|
||||
"components.Discover.emptywatchlist": "W tym miejscu pojawią się multimedia dodane do <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink>.",
|
||||
"components.RequestModal.SearchByNameModal.nomatches": "Nie udało nam się znaleźć dopasowania do tej serii.",
|
||||
"components.StatusBadge.managemedia": "Zarządzaj {mediaType}",
|
||||
"components.StatusBadge.openinarr": "Otwórz w {arr}",
|
||||
"components.TvDetails.episodeCount": "{episodeCount, plural, one {# Odcinek} other {# Odcinki}}",
|
||||
"components.Discover.CreateSlider.addSlider": "Dodaj Suwak",
|
||||
"components.Discover.CreateSlider.addcustomslider": "Utwórz niestandardowy Suwak",
|
||||
"components.Discover.CreateSlider.addfail": "Nie udało się utworzyć nowego Suwaka.",
|
||||
"components.Discover.CreateSlider.addsuccess": "Utworzono nowy Suwaki zapisano ustawienia dostosowywania odnajdywania.",
|
||||
"components.Discover.CreateSlider.editSlider": "Edytuj Suwak",
|
||||
"components.Discover.CreateSlider.editfail": "Nie udało się edytować Suwaka.",
|
||||
"components.Discover.CreateSlider.editsuccess": "Edytowano Suwak i zapisano ustawienia odkrywania.",
|
||||
"components.Discover.CreateSlider.needresults": "Musisz mieć co najmniej 1 wynik.",
|
||||
"components.Discover.CreateSlider.nooptions": "Brak wyników.",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Podaj identyfikator gatunku TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbkeywordid": "Podaj identyfikator słowa kluczowego TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Podaj identyfikator platformy TMDB",
|
||||
"components.Discover.CreateSlider.providetmdbsearch": "Podaj zapytanie wyszukiwania",
|
||||
"components.Discover.CreateSlider.providetmdbstudio": "Podaj identyfikator studia TMDB",
|
||||
"components.Discover.CreateSlider.searchGenres": "Szukaj gatunków…",
|
||||
"components.Discover.CreateSlider.searchKeywords": "Szukaj słów kluczowych…",
|
||||
"components.Discover.CreateSlider.slidernameplaceholder": "Nazwa slidera",
|
||||
"components.Discover.CreateSlider.starttyping": "Zacznij pisać, aby wyszukać.",
|
||||
"components.Discover.CreateSlider.validationDatarequired": "Musisz uzyskać wartość danych.",
|
||||
"components.Discover.CreateSlider.validationTitlerequired": "Musisz podać tytuł.",
|
||||
"components.Discover.DiscoverMovieKeyword.keywordMovies": "{keywordTitle} Filmy",
|
||||
"components.Discover.DiscoverSliderEdit.deletefail": "Nie udało się usunąć slidera.",
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Usuń",
|
||||
"components.Discover.DiscoverTvKeyword.keywordSeries": "Serial {keywordTitle}",
|
||||
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# aktywny filtr} other {# aktywnych filtrów}}",
|
||||
"components.Discover.DiscoverMovies.discovermovies": "Filmy",
|
||||
"components.Discover.DiscoverMovies.sortPopularityAsc": "Popularność rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortPopularityDesc": "Popularność malejąco",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Data wydania rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Data wydania malejąco",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Tytuł (A-Z) rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Tytuł (Z-A) malejąco",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "Ocena TMDB rosnąco",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "Ocena TMDB malejąco",
|
||||
"components.Discover.DiscoverSliderEdit.deletesuccess": "Pomyślnie usunięto slider.",
|
||||
"components.Discover.DiscoverSliderEdit.enable": "Przełącz widoczność",
|
||||
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# aktywny filtr} other {# aktywnych filtrów}}",
|
||||
"components.Discover.DiscoverTv.discovertv": "Seriale",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Data pierwszej emisji rosnąco",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Pierwsza data emisji malejąco",
|
||||
"components.Discover.DiscoverTv.sortPopularityAsc": "Popularność rosnąco",
|
||||
"components.Discover.DiscoverTv.sortPopularityDesc": "Popularność malejąco",
|
||||
"components.Discover.DiscoverTv.sortTitleAsc": "Tytuł (A-Z) rosnąco",
|
||||
"components.Discover.DiscoverTv.sortTitleDesc": "Tytuł (Z-A) malejąco",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "Ocena TMDB rosnąco",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingDesc": "Ocena TMDB malejąco",
|
||||
"components.Discover.CreateSlider.searchStudios": "Szukaj studiów…",
|
||||
"components.Settings.SettingsMain.general": "Ogólne",
|
||||
"components.Settings.SettingsMain.generalsettings": "Ustawienia ogólne",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Konfiguracja globalnych i domyślnych ustawień Overseerr.",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Ukryj dostępne media",
|
||||
"components.Settings.SettingsMain.locale": "Wyświetlany język",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Zezwalaj na częściowe żądania seriali",
|
||||
"components.Settings.SettingsMain.region": "Region odkrywania",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtrowanie zawartości według dostępności regionalnej",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Umożliwienie Overseerr poprawnego rejestrowania adresów IP klientów za serwerem proxy",
|
||||
"components.Settings.jellyfinSettings": "{mediaServerName} Ustawienia",
|
||||
"components.Settings.jellyfinSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień {mediaServerName}.",
|
||||
"components.Settings.jellyfinSettingsSuccess": "Ustawienia {mediaServerName} zostały pomyślnie zapisane!",
|
||||
"components.Settings.jellyfinlibrariesDescription": "Biblioteki {mediaServerName} skanują w poszukiwaniu tytułów. Kliknij poniższy przycisk, jeśli na liście nie ma żadnych bibliotek.",
|
||||
"components.Settings.jellyfinsettings": "{mediaServerName} ustawienia",
|
||||
"components.Settings.jellyfinsettingsDescription": "Konfiguracja ustawień serwera {mediaServerName}. {mediaServerName} skanuje biblioteki {mediaServerName}, aby sprawdzić, jaka zawartość jest dostępna.",
|
||||
"components.Settings.manualscanJellyfin": "Ręczne skanowanie biblioteki",
|
||||
"components.Settings.saving": "Zapisywanie…",
|
||||
"components.Settings.syncing": "Synchronizowanie",
|
||||
"components.Setup.signinWithPlex": "Użyj konta Plex",
|
||||
"components.StatusBadge.openinarr": "Otwórz w {arr}",
|
||||
"components.StatusBadge.playonplex": "Odtwórz na {mediaServerName}",
|
||||
"components.TitleCard.watchlistError": "Coś poszło nie tak, spróbuj ponownie.",
|
||||
"components.TvDetails.Season.somethingwentwrong": "Coś poszło nie tak podczas pobierania danych sezonu.",
|
||||
"components.TvDetails.seasonnumber": "Sezon {seasonNumber}",
|
||||
"components.TvDetails.seasonstitle": "Sezony",
|
||||
"components.TvDetails.status4k": "4K {status}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Automatyczne żądania filmów",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Zapisz zmiany",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Zapisywanie…",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Filtruj zawartość według języka oryginału",
|
||||
"components.Discover.PlexWatchlistSlider.emptywatchlist": "W tym miejscu pojawią się multimedia dodane do <PlexWatchlistSupportLink>listy obserwowanych Plex</PlexWatchlistSupportLink>.",
|
||||
"components.Discover.networks": "Platformy",
|
||||
"components.Discover.moviegenres": "Gatunki filmowe",
|
||||
"components.Discover.tmdbnetwork": "Platforma TMDB",
|
||||
"components.Discover.tmdbstudio": "Studio TMDB",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Twoja lista obserwowanych Plex",
|
||||
"components.Discover.createnewslider": "Utwórz nowy suwak",
|
||||
"components.Discover.customizediscover": "Dostosowywanie funkcji Odkryj",
|
||||
"components.Discover.resetfailed": "Wystąpił problem podczas resetowania ustawień odnajdywania.",
|
||||
"components.Discover.resetsuccess": "Pomyślnie zresetowano ustawienia odnajdywania.",
|
||||
"components.Discover.resetwarning": "Przywróć wszystkie Suwaki do ustawień domyślnych. Spowoduje to również usunięcie wszystkich niestandardowych Suwaków!",
|
||||
"components.Discover.resettodefault": "Przywróć ustawienia domyślne",
|
||||
"components.Discover.studios": "Studia",
|
||||
"components.Discover.tmdbmoviegenre": "Gatunek filmu TMDB",
|
||||
"components.Discover.tvgenres": "Gatunki serialu",
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Ustaw zewnętrzny dostęp api na tylko do odczytu (wymaga HTTPS)",
|
||||
"components.Settings.SettingsMain.csrfProtectionHoverTip": "NIE włączaj tego ustawienia, chyba że rozumiesz, co robisz!",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Skonfiguruj globalne i domyślne ustawienia dla Overseerr.",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Ukryj dostępne multimedia",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Pozwól Overseerr poprawnie rejestrować adresy IP klientów za serwerem proxy",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "Ustawienia zostały zapisane pomyślnie!",
|
||||
"components.Settings.SettingsMain.trustProxy": "Włącz obsługę proxy",
|
||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "Adres URL nie może kończyć się ukośnikiem",
|
||||
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# aktywny filtr} other {# aktywnych filtrów}}",
|
||||
"components.Discover.FilterSlideover.clearfilters": "Wyczyść aktywne filtry",
|
||||
"components.Discover.FilterSlideover.filters": "Filtry",
|
||||
"components.Discover.FilterSlideover.firstAirDate": "Pierwsza data emisji",
|
||||
"components.Discover.FilterSlideover.from": "Od",
|
||||
"components.Discover.FilterSlideover.genres": "Gatunki",
|
||||
"components.Discover.FilterSlideover.keywords": "Słowa kluczowe",
|
||||
"components.Discover.FilterSlideover.originalLanguage": "Język oryginalny",
|
||||
"components.Discover.FilterSlideover.ratingText": "Oceny pomiędzy {minValue} a {maxValue}",
|
||||
"components.Discover.FilterSlideover.releaseDate": "Data wydania",
|
||||
"components.Discover.FilterSlideover.runtimeText": "{minValue}-{maxValue} czas odtwarzania w minutach",
|
||||
"components.Discover.FilterSlideover.runtime": "Czas odtwarzania",
|
||||
"components.Discover.FilterSlideover.tmdbuserscore": "Ocena użytkowników TMDB",
|
||||
"components.Discover.FilterSlideover.to": "Do",
|
||||
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Ostatnio dodane",
|
||||
"components.Discover.stopediting": "Zatrzymaj edycję",
|
||||
"components.Discover.tmdbmoviekeyword": "Słowo kluczowe filmu TMDB",
|
||||
"components.Discover.tmdbsearch": "Wyszukiwanie TMDB",
|
||||
"components.Layout.Sidebar.browsemovies": "Filmy",
|
||||
"components.Login.validationEmailFormat": "Nieprawidłowy adres e-mail",
|
||||
"components.RequestModal.SearchByNameModal.nomatches": "Nie udało nam się znaleźć odpowiednika dla tego serialu.",
|
||||
"components.Layout.Sidebar.browsetv": "Seriale",
|
||||
"components.Selector.searchGenres": "Wybierz gatunki…",
|
||||
"components.Selector.searchKeywords": "Szukaj słów kluczowych…",
|
||||
"components.Selector.showmore": "Pokaż więcej",
|
||||
"components.Settings.SettingsMain.cacheImagesTip": "Pamięć podręczna obrazów pochodzących z zewnątrz (wymaga znacznej ilości miejsca na dysku)",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Filtrowanie zawartości według oryginalnego języka",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "Ustawienia zapisane pomyślnie!",
|
||||
"components.Settings.experimentalTooltip": "Włączenie tego ustawienia może spowodować nieoczekiwane zachowanie aplikacji",
|
||||
"components.UserList.mediaServerUser": "{mediaServerName} Użytkownik",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "Email",
|
||||
"components.Discover.resetwarning": "Przywróć domyślne ustawienia wszystkich suwaków. Spowoduje to również usunięcie wszystkich niestandardowych suwaków!",
|
||||
"components.Layout.UserWarnings.emailRequired": "Wymagany jest adres e-mail.",
|
||||
"components.MovieDetails.openradarr": "Otwórz film w Radarr",
|
||||
"components.RequestModal.requestcollection4ktitle": "Poproś o kolekcję w 4K",
|
||||
"components.Setup.signinWithJellyfin": "Użyj konta {mediaServerName}",
|
||||
"components.Login.description": "Ponieważ logujesz się do {applicationName} po raz pierwszy, musisz dodać prawidłowy adres e-mail.",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Spowoduje to nieodwracalne usunięcie tego {mediaType} z {arr}, w tym wszystkich plików.",
|
||||
"components.ManageSlideOver.removearr": "Usuń z {arr}",
|
||||
"components.PermissionEdit.autorequestSeriesDescription": "Zezwolenie na automatyczne przesyłanie żądań dotyczących seriali innych niż 4K za pośrednictwem listy obserwowanych Plex.",
|
||||
"components.PermissionEdit.viewwatchlists": "Wyświetl {mediaServerName} watchlistę",
|
||||
"components.RequestCard.approverequest": "Zaakceptuj prośbę",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Automatycznie dodawaj dodatkowy znacznik z identyfikatorem użytkownika i wyświetlaną nazwą użytkownika",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Wspomóż Jellyseerr",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Pełne skanowanie bibliotek Jellyfin",
|
||||
"components.Settings.SettingsLogs.viewdetails": "Zobacz szczegóły",
|
||||
"components.Settings.jellyfinSettingsDescription": "Opcjonalnie skonfiguruj wewnętrzne i zewnętrzne punkty końcowe dla serwera {mediaServerName}. W większości przypadków zewnętrzny adres URL różni się od wewnętrznego adresu URL. Niestandardowy adres URL resetowania hasła można również ustawić dla logowania {mediaServerName}, na wypadek gdybyś chciał przekierować na inną stronę resetowania hasła.",
|
||||
"components.Settings.save": "Zapisz zmiany",
|
||||
"components.Settings.syncJellyfin": "Synchronizuj biblioteki",
|
||||
"components.TvDetails.Season.noepisodes": "Lista odcinków jest niedostępna.",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Dźwięk powiadomień",
|
||||
"components.Settings.RadarrModal.tagRequests": "Żądania tagów",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Aktualna częstotliwość",
|
||||
"components.Settings.SettingsMain.validationApplicationTitle": "Musisz podać tytuł aplikacji",
|
||||
"components.Settings.SonarrModal.seriesType": "Typ seriali",
|
||||
"components.Settings.advancedTooltip": "Nieprawidłowe skonfigurowanie tego ustawienia może spowodować nieprawidłowe działanie",
|
||||
"components.TvDetails.reportissue": "Zgłoś problem",
|
||||
"components.RequestBlock.edit": "Edytuj żądanie",
|
||||
"components.RequestList.RequestItem.unknowntitle": "Nieznany tytuł",
|
||||
"components.Selector.starttyping": "Zacznij pisać, aby wyszukać.",
|
||||
"components.Settings.SettingsMain.applicationTitle": "Tytuł aplikacji",
|
||||
"components.Settings.SettingsMain.applicationurl": "Adres URL aplikacji",
|
||||
"components.Settings.SettingsMain.cacheImages": "Włącz buforowanie obrazów",
|
||||
"components.Settings.SettingsMain.csrfProtection": "Włącz ochronę CSRF",
|
||||
"components.Settings.SettingsMain.locale": "Język wyświetlania",
|
||||
"components.Settings.SettingsMain.originallanguage": "Odkryj język",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Zezwalaj na prośby o część serialu",
|
||||
"components.Settings.SettingsMain.region": "Odkryj region",
|
||||
"components.Settings.SettingsMain.regionTip": "Filtruj zawartość według dostępności regionalnej",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Coś poszło nie tak podczas generowania nowego klucza API.",
|
||||
"components.Settings.SettingsMain.toastApiKeySuccess": "Nowy klucz API został pomyślnie wygenerowany!",
|
||||
"components.Settings.SettingsMain.validationApplicationTitle": "Należy podać tytuł aplikacji",
|
||||
"components.Settings.SettingsMain.validationApplicationUrl": "Musisz podać prawidłowy adres URL",
|
||||
"components.Discover.FilterSlideover.streamingservices": "Usługi streamingowe",
|
||||
"components.Discover.FilterSlideover.studio": "Studia",
|
||||
"components.Discover.tmdbtvgenre": "Gatunek serialu TMDB",
|
||||
"components.Discover.tmdbtvkeyword": "Słowo kluczowe serialu TMDB",
|
||||
"components.Discover.updatesuccess": "Zaktualizowano ustawienia odnajdywania.",
|
||||
"components.Selector.nooptions": "Brak wyników.",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Domyślne urządzenie",
|
||||
"components.Settings.Notifications.userEmailRequired": "Wymagaj adresu e-mail użytkownika",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Zwykle będzie to uruchamiane tylko raz na 24 godziny. Jellyseerr będzie bardziej agresywnie sprawdzać ostatnio dodane biblioteki serwera {mediaServerName}. Jeśli po raz pierwszy konfigurujesz Jellyseerr, zalecane jest jednorazowe pełne ręczne skanowanie biblioteki!"
|
||||
"components.Selector.searchStudios": "Szukaj studiów…",
|
||||
"components.Selector.showless": "Pokaż mniej",
|
||||
"components.Discover.updatefailed": "Wystąpił problem podczas aktualizowania ustawień odnajdywania.",
|
||||
"components.Settings.SettingsMain.apikey": "Klucz API",
|
||||
"components.Settings.SettingsMain.cacheImagesTip": "Pamięć podręczna dla obrazów pochodzących z zewnętrznych źródeł (wymaga znacznej ilości miejsca na dysku)",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Coś poszło nie tak podczas zapisywania ustawień.",
|
||||
"i18n.collection": "Kolekcja",
|
||||
"components.MovieDetails.imdbuserscore": "Ocena użytkowników IMDB",
|
||||
"components.Settings.SonarrModal.seriesType": "Typ serialu"
|
||||
}
|
||||
|
||||
@@ -1257,35 +1257,9 @@
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Qtd de Votos de Usuários TMDB",
|
||||
"components.Discover.FilterSlideover.voteCount": "Qtd the votos entre {minValue} e {maxValue}",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Adicione automaticamente uma tag extra com o ID de usuário e o nome de exibição do solicitante",
|
||||
"components.Layout.UserWarnings.emailRequired": "Um endereço de e-mail é necessário.",
|
||||
"components.Login.credentialerror": "O nome de usuário ou senha está incorreto.",
|
||||
"components.Login.description": "Já que é sua primeira vez entrando em {applicationName}, você precisa adicionar um e-mail válido.",
|
||||
"components.Login.host": "URL de {mediaServerName}",
|
||||
"components.Login.initialsignin": "Conectar",
|
||||
"components.Login.initialsigningin": "Conectando…",
|
||||
"components.Login.save": "Adicionar",
|
||||
"components.Login.saving": "Adicionando…",
|
||||
"components.Login.signinwithjellyfin": "Use sua conta de {mediaServerName}",
|
||||
"components.Login.title": "Adicionar E-Mail",
|
||||
"components.Login.username": "Nome de usuário",
|
||||
"components.Login.validationEmailFormat": "E-mail inválido",
|
||||
"components.Login.validationEmailRequired": "Você precisa providenciar um e-mail",
|
||||
"components.Login.validationemailformat": "E-mail válido necessário",
|
||||
"components.Login.validationhostformat": "URL válido necessário",
|
||||
"components.Login.validationusernamerequired": "Nome de usuário necessário",
|
||||
"components.ManageSlideOver.removearr": "Remover de {arr}",
|
||||
"components.ManageSlideOver.removearr4k": "Remover de {arr} 4K",
|
||||
"components.MovieDetails.downloadstatus": "Status de download",
|
||||
"components.MovieDetails.imdbuserscore": "Avaliação de usuário no IMDB",
|
||||
"components.MovieDetails.openradarr": "Abrir filme no Radarr",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Som de notificação",
|
||||
"components.Login.emailtooltip": "Endereço não precisa ser associado à sua instância de {mediaServerName}.",
|
||||
"components.Layout.UserWarnings.emailInvalid": "Endereço de e-mail inválido.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Uma senha é necessária.",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Padrão do dispositivo",
|
||||
"components.Login.validationhostrequired": "URL de {mediaServerName} necessário",
|
||||
"components.MovieDetails.openradarr4k": "Abrir filme em Radarr 4K",
|
||||
"components.MovieDetails.play": "Reproduzir em {mediaServerName}",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Isto irá remover este {mediaType} de {arr}, incluindo todos os arquivos.",
|
||||
"components.MovieDetails.play4k": "Reproduzir em 4K em {mediaServerName}"
|
||||
"i18n.collection": "Coleção",
|
||||
"components.MovieDetails.imdbuserscore": "Pontuação de usuário IMDB",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Adiciona automaticamente uma tag adicional com o ID de usuário e nome de exibição de quem pediu",
|
||||
"components.Settings.SonarrModal.tagRequests": "Marcar Pedidos",
|
||||
"components.Settings.RadarrModal.tagRequests": "Marcar Pedidos"
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
"components.PlexLoginButton.signinwithplex": "Conectat",
|
||||
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{movies} per {quotaDays} {days}</quotaUnits>",
|
||||
"components.PersonDetails.lifespan": "{birthdate} – {deathdate}",
|
||||
"components.RequestBlock.seasons": "{seasonCount, plural, un {Season} alte {Seasons}}",
|
||||
"components.RequestBlock.seasons": "{seasonCount, plural, one {Sezon} other {Sezoane}}",
|
||||
"components.PermissionEdit.requestMoviesDescription": "Acordați permisiunea de a trimite solicitări pentru filme non-4K.",
|
||||
"components.PermissionEdit.viewissuesDescription": "Acordați permisiunea de a vizualiza problemele media raportate de alți utilizatori.",
|
||||
"components.PermissionEdit.viewwatchlistsDescription": "Acordați permisiunea de a vizualiza listele de urmărire Plex ale altor utilizatori.",
|
||||
@@ -403,40 +403,62 @@
|
||||
"components.RequestBlock.approve": "Aprobă Solicitarea",
|
||||
"components.RequestBlock.decline": "Respinge Solicitarea",
|
||||
"components.RequestBlock.requestedby": "Solicitat de",
|
||||
"components.RequestButton.approve4krequests": "Aprobă {requestCount, plural, o {4K Request} alte {{requestCount} 4K Requests}}",
|
||||
"components.RequestButton.approve4krequests": "Aprobă {requestCount, plural, one {Cerere 4K} other {{requestCount} Cereri 4K}}",
|
||||
"components.RequestBlock.lastmodifiedby": "Ultima Dată Modificat de",
|
||||
"components.RequestBlock.profilechanged": "Profil Calitate",
|
||||
"components.RequestBlock.requestdate": "Dată Solicitare",
|
||||
"components.Layout.UserWarnings.emailRequired": "Este necesara o adresa de email.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Este necesara o parola.",
|
||||
"components.Login.credentialerror": "Numele de utilizator sau parola sunt incorecte.",
|
||||
"components.Login.emailtooltip": "Nu este necesar ca adresa ta sa fie asociata cu instanta ta {mediaServerName} .",
|
||||
"components.Login.save": "Adauga",
|
||||
"components.Login.signinwithjellyfin": "Foloseste-ti contul de {mediaServerName}",
|
||||
"components.Login.title": "Adauga email",
|
||||
"components.Login.username": "Nume utilizator",
|
||||
"components.Login.validationEmailFormat": "Adresa email invalida",
|
||||
"components.Login.validationemailformat": "Necesar email valid",
|
||||
"components.Login.validationhostformat": "Necesar URL valid",
|
||||
"components.Login.validationhostrequired": "{mediaServerName} URL necesar",
|
||||
"components.Login.validationusernamerequired": "Nume utilizator necesar",
|
||||
"components.MovieDetails.downloadstatus": "Status descarcare",
|
||||
"components.MovieDetails.openradarr": "Deschide film in Radarr",
|
||||
"components.MovieDetails.openradarr4k": "Deschide film in 4K Radarr",
|
||||
"components.MovieDetails.play": "Ruleaza pe {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Ruleaza 4k pe {mediaServerName}",
|
||||
"components.RequestButton.declinerequest": "Refuza cerere",
|
||||
"components.RequestButton.declinerequest4k": "Refuza cerere 4k",
|
||||
"components.Layout.UserWarnings.emailInvalid": "Adresa de email este invalida.",
|
||||
"components.Login.description": "Deoarece este prima ta authentificare in {applicationName}, este necesara sa introduci o adresa de email valida.",
|
||||
"components.Login.initialsigningin": "Se conecteaza…",
|
||||
"components.Login.saving": "Se adauga…",
|
||||
"components.RequestButton.approverequest": "Aproba cerere",
|
||||
"components.RequestButton.approverequest4k": "Aproba cerere 4k",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.initialsignin": "Conecteaza-te",
|
||||
"components.ManageSlideOver.removearr4k": "Elimina din 4K {arr}",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Acesta va elimina ireversibil {mediaType} din {arr}, incluzand toate fisierele asociate.",
|
||||
"components.Login.validationEmailRequired": "Trebuie sa introduci o adresa email",
|
||||
"components.ManageSlideOver.removearr": "Elimina din {arr}"
|
||||
"components.RequestButton.approverequests": "Aprobă {requestCount, plural, one {Cerere} other {{requestCount} Cereri}}",
|
||||
"components.RequestButton.requestmore4k": "Cere mai mult în 4K",
|
||||
"components.RequestButton.approverequest4k": "Aproba Cereri 4K",
|
||||
"components.RequestCard.tmdbid": "ID TMDB",
|
||||
"components.RequestCard.failedretry": "A apărut o eroare la reîncercarea solicitării.",
|
||||
"components.RequestButton.declinerequest": "Respinge Cerere",
|
||||
"components.RequestCard.seasons": "{seasonCount, plural, one {Sezon} other {Sezoane}}",
|
||||
"components.RequestCard.declinerequest": "Respinge Cererea",
|
||||
"components.RequestButton.viewrequest": "Vezi Cerere",
|
||||
"components.RequestButton.declinerequests": "Respinge {requestCount, plural, one {Cererea} other {{requestCount} Cererile}}",
|
||||
"components.RequestCard.mediaerror": "{mediaType} Nu a fost găsit",
|
||||
"components.RequestCard.editrequest": "Editează Cererea",
|
||||
"components.RequestButton.viewrequest4k": "Vezi Cerere 4K",
|
||||
"components.RequestButton.decline4krequests": "Respinge {requestCount, plural, one {Cererea 4K} other {{requestCount} Cererile 4K}}",
|
||||
"components.RequestButton.declinerequest4k": "Respinge Cerere 4K",
|
||||
"components.RequestCard.approverequest": "Aprobă Cererea",
|
||||
"components.RequestButton.approverequest": "Cereri Aprobate",
|
||||
"components.RequestCard.deleterequest": "Șterge Cererea",
|
||||
"components.RequestCard.unknowntitle": "Titlu necunoscut",
|
||||
"components.RequestList.RequestItem.cancelRequest": "Anulează Cerere",
|
||||
"components.RequestCard.tvdbid": "ID TheTVDB",
|
||||
"components.RequestButton.requestmore": "Cere mai mult",
|
||||
"components.RequestList.RequestItem.deleterequest": "Șterge Cerere",
|
||||
"components.RequestCard.cancelrequest": "Anulează Cererea",
|
||||
"components.RequestModal.AdvancedRequester.folder": "{path} ({space})",
|
||||
"components.RequestList.RequestItem.modified": "Modificat",
|
||||
"components.RequestList.RequestItem.editrequest": "Editează Cererea",
|
||||
"components.RequestModal.AdvancedRequester.qualityprofile": "Profil de Calitate",
|
||||
"components.RequestList.requests": "Cereri",
|
||||
"components.RequestModal.AdvancedRequester.advancedoptions": "Avansat",
|
||||
"components.RequestModal.AdvancedRequester.notagoptions": "Fără etichete.",
|
||||
"components.RequestList.RequestItem.modifieduserdate": "{date} de {user}",
|
||||
"components.RequestModal.AdvancedRequester.requestas": "Cere ca",
|
||||
"components.RequestList.showallrequests": "Afișează toate cererile",
|
||||
"components.RequestList.RequestItem.tmdbid": "ID-ul TMDB",
|
||||
"components.RequestList.RequestItem.requesteddate": "Solicitat",
|
||||
"components.RequestModal.QuotaDisplay.movie": "film",
|
||||
"components.RequestList.RequestItem.failedretry": "Ceva a mers greșit în timpul reîncercării cererii.",
|
||||
"components.RequestList.RequestItem.unknowntitle": "Titlu Necunoscut",
|
||||
"components.RequestModal.AdvancedRequester.destinationserver": "Server Destinație",
|
||||
"components.RequestModal.AdvancedRequester.rootfolder": "Folder Rădăcină",
|
||||
"components.RequestList.sortAdded": "Cele Mai Recente",
|
||||
"components.RequestModal.AdvancedRequester.tags": "Etichete",
|
||||
"components.RequestList.RequestItem.mediaerror": "{mediaType} nu a fost găsit",
|
||||
"components.RequestList.sortModified": "Ultima Modificată",
|
||||
"components.RequestList.RequestItem.tvdbid": "ID-ul TheTVDB",
|
||||
"components.RequestModal.AdvancedRequester.selecttags": "Selectați Etichetele",
|
||||
"components.RequestList.RequestItem.requested": "Solicitat",
|
||||
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Nu sunt suficiente cereri de sezon rămase",
|
||||
"components.RequestModal.AdvancedRequester.default": "{name} (Implicit)",
|
||||
"components.RequestModal.AdvancedRequester.languageprofile": "Profil de Limbă",
|
||||
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "Acest utilizator are voie sa ceara <strong>{limit}</strong> {type} la fiecare <strong>{days}</strong> zile.",
|
||||
"components.RequestList.RequestItem.seasons": "",
|
||||
"components.RequestModal.QuotaDisplay.allowedRequests": "Aveți voie să cereți <strong>{limit}</strong> de {type} la fiecare <strong>{days}</strong> zile."
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"components.RequestModal.cancel": "Отменить запрос",
|
||||
"components.RequestModal.extras": "Дополнительно",
|
||||
"components.RequestModal.numberofepisodes": "# эпизодов",
|
||||
"components.RequestModal.pendingrequest": "Ожидающий запрос",
|
||||
"components.RequestModal.pendingrequest": "",
|
||||
"components.RequestModal.requestCancel": "Запрос на <strong>{title}</strong> отменён.",
|
||||
"components.RequestModal.requestSuccess": "<strong>{title}</strong> успешно запрошен!",
|
||||
"components.RequestModal.requestadmin": "Этот запрос будет одобрен автоматически.",
|
||||
@@ -170,7 +170,7 @@
|
||||
"pages.oops": "Упс",
|
||||
"pages.returnHome": "Вернуться домой",
|
||||
"components.CollectionDetails.overview": "Обзор",
|
||||
"components.CollectionDetails.numberofmovies": "{count} фильмов",
|
||||
"components.CollectionDetails.numberofmovies": "{count} {count, plural, one {фильм} few {фильма} other {фильмов}}",
|
||||
"components.CollectionDetails.requestcollection": "Запросить Коллекцию",
|
||||
"components.Login.email": "Адрес электронной почты",
|
||||
"components.UserList.users": "Пользователи",
|
||||
@@ -427,9 +427,9 @@
|
||||
"components.Settings.RadarrModal.testFirstRootFolders": "Протестировать подключение для загрузки корневых каталогов",
|
||||
"components.Settings.RadarrModal.loadingrootfolders": "Загрузка корневых каталогов…",
|
||||
"components.RequestModal.AdvancedRequester.destinationserver": "Сервер-получатель",
|
||||
"components.RequestList.RequestItem.mediaerror": "{mediaType} не найдено",
|
||||
"components.RequestList.RequestItem.mediaerror": "Название, связанное с этим запросом, больше недоступно.",
|
||||
"components.RequestList.RequestItem.failedretry": "Что-то пошло не так при попытке повторить запрос.",
|
||||
"components.RequestCard.mediaerror": "{mediaType} не найдено",
|
||||
"components.RequestCard.mediaerror": "Название, связанное с этим запросом, больше недоступно.",
|
||||
"components.RequestCard.failedretry": "Что-то пошло не так при попытке повторить запрос.",
|
||||
"components.RequestButton.viewrequest4k": "Посмотреть 4К запрос",
|
||||
"components.RequestButton.requestmore4k": "Запросить больше в 4К",
|
||||
@@ -570,7 +570,7 @@
|
||||
"components.TvDetails.seasons": "{seasonCount, plural, one {# сезон} other {# сезонов}}",
|
||||
"components.RequestModal.QuotaDisplay.requiredquotaUser": "Этому пользователю необходимо иметь по крайней мере <strong>{seasons}</strong> {seasons, plural, one {запрос на сезоны} other {запроса(ов) на сезоны}} для того, чтобы отправить запрос на этот сериал.",
|
||||
"components.RequestModal.QuotaDisplay.requiredquota": "Вам необходимо иметь по крайней мере <strong>{seasons}</strong> {seasons, plural, one {запрос на сезоны} other {запроса(ов) на сезоны}} для того, чтобы отправить запрос на этот сериал.",
|
||||
"components.RequestModal.pending4krequest": "Ожидающий 4K запрос",
|
||||
"components.RequestModal.pending4krequest": "",
|
||||
"components.RequestModal.autoapproval": "Автоматическое одобрение",
|
||||
"i18n.usersettings": "Настройки пользователя",
|
||||
"i18n.showingresults": "Показываются результаты с <strong>{from}</strong> по <strong>{to}</strong> из <strong>{total}</strong>",
|
||||
@@ -790,7 +790,7 @@
|
||||
"components.UserList.importfromplexerror": "Что-то пошло не так при импорте пользователей из Plex.",
|
||||
"components.UserList.importfrommediaserver": "Импортировать пользователей из {mediaServerName}",
|
||||
"components.UserList.importfromplex": "Импортировать пользователей из Plex",
|
||||
"components.UserList.importedfromplex": "<strong>{userCount}</strong> {userCount, plural, one {# новый пользователь} other {# новых пользователя(ей)}} успешно импортированы из Plex!",
|
||||
"components.UserList.importedfromplex": "{userCount, plural, one {# новый пользователь} other {# новых пользователя(ей)}} успешно импортированы из Plex!",
|
||||
"components.UserList.edituser": "Изменить разрешения пользователя",
|
||||
"components.UserList.displayName": "Отображаемое имя",
|
||||
"components.UserList.deleteconfirm": "Вы уверены, что хотите удалить этого пользователя? Все данные о его запросах будут удалены без возможности восстановления.",
|
||||
@@ -1018,7 +1018,7 @@
|
||||
"components.Discover.CreateSlider.addcustomslider": "Создать слайдер",
|
||||
"components.Discover.CreateSlider.nooptions": "Нет результатов.",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Введите TMDB ID жанра",
|
||||
"components.Discover.CreateSlider.needresults": "Должен быть хотя-бы 1 результат.",
|
||||
"components.Discover.CreateSlider.needresults": "Должен быть хотя бы 1 результат.",
|
||||
"components.Discover.CreateSlider.providetmdbkeywordid": "Введите TMDB ID ключевого слова",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Введите TMDB ID сети",
|
||||
"components.Discover.CreateSlider.providetmdbsearch": "Введите поисковой запрос",
|
||||
@@ -1027,7 +1027,7 @@
|
||||
"components.Discover.CreateSlider.searchKeywords": "Поиск ключевых слов…",
|
||||
"components.Discover.CreateSlider.searchStudios": "Поиск студий…",
|
||||
"components.Discover.CreateSlider.slidernameplaceholder": "Название слайдера",
|
||||
"components.Discover.CreateSlider.starttyping": "Начините писать для поиска.",
|
||||
"components.Discover.CreateSlider.starttyping": "Начните писать для поиска.",
|
||||
"components.Discover.CreateSlider.validationDatarequired": "Вы должны ввести дату.",
|
||||
"components.Discover.CreateSlider.validationTitlerequired": "Вы должны ввести заголовок.",
|
||||
"components.Discover.DiscoverMovieKeyword.keywordMovies": "Фильмы по ключевому слову \"{keywordTitle}\"",
|
||||
@@ -1059,7 +1059,7 @@
|
||||
"components.Discover.DiscoverSliderEdit.deletesuccess": "Слайдер успешно удален.",
|
||||
"components.Discover.DiscoverSliderEdit.enable": "Изменить видимость",
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Удалить",
|
||||
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Активный фильтр} other {# Активные фильтры}}",
|
||||
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Активен фильтр} other {# Активные фильтры }}",
|
||||
"components.Discover.DiscoverTv.discovertv": "Сериалы",
|
||||
"components.Discover.DiscoverTv.sortPopularityAsc": "Популярность по возрастанию",
|
||||
"components.Discover.DiscoverTv.sortPopularityDesc": "Популярность по убыванию",
|
||||
@@ -1117,7 +1117,7 @@
|
||||
"components.MovieDetails.digitalrelease": "Цифровой релиз",
|
||||
"components.MovieDetails.physicalrelease": "Физический релиз",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Что-то пошло не так при сохранении настроек.",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Разрешить Jellyseerr правильно регистрировать IP-адреса клиентов за прокси-сервером",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Разрешить Overserr правильно регистрировать IP-адреса клиентов за прокси-сервером",
|
||||
"components.Settings.experimentalTooltip": "Включение этого параметра может привести к неожиданному поведению приложения",
|
||||
"components.Settings.advancedTooltip": "Неправильная настройка этого параметра может привести к нарушению функциональности",
|
||||
"components.Settings.externalUrl": "Внешний URL-адрес",
|
||||
@@ -1131,7 +1131,7 @@
|
||||
"components.Settings.validationApiKey": "Вы должны предоставить ключ API",
|
||||
"components.TitleCard.mediaerror": "{mediaType} не найдено",
|
||||
"components.TitleCard.tmdbid": "TMDB ID",
|
||||
"components.Settings.restartrequiredTooltip": "Чтобы изменения этого параметра вступили в силу, необходимо перезапустить Jellyseerr",
|
||||
"components.Settings.restartrequiredTooltip": "Чтобы изменения этого параметра вступили в силу, необходимо перезапустить Overserr",
|
||||
"components.ManageSlideOver.alltime": "Все время",
|
||||
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {просмотр} other {просмотров}}",
|
||||
"components.Settings.Notifications.NotificationsGotify.agentenabled": "Включить агент",
|
||||
@@ -1214,13 +1214,13 @@
|
||||
"components.Settings.Notifications.NotificationsGotify.toastGotifyTestSending": "Отправка тестового уведомления Gotify…",
|
||||
"components.Settings.SettingsMain.cacheImages": "Включить кэширование изображений",
|
||||
"components.Settings.SettingsMain.generalsettings": "Общие настройки",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Настройте глобальные параметры и параметры по умолчанию для Jellyseerr.",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Настройте глобальные параметры и параметры по умолчанию для Overserr.",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Скрыть доступные медиа",
|
||||
"components.Settings.SettingsMain.regionTip": "Фильтровать контент по региональной доступности",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Что-то пошло не так при создании нового ключа API.",
|
||||
"components.Settings.SettingsMain.locale": "Язык приложения",
|
||||
"components.Settings.SettingsMain.originallanguage": "Регион поиска",
|
||||
"components.Settings.tautulliSettingsDescription": "При желании настройте параметры для вашего сервера Tautulli. Jellyseerr извлекает данные истории просмотров Plex из Tautulli.",
|
||||
"components.Settings.tautulliSettingsDescription": "При желании настройте параметры для вашего сервера Tautulli. Overserr извлекает данные истории просмотров Plex из Tautulli.",
|
||||
"components.Settings.toastTautulliSettingsFailure": "Что-то пошло не так при сохранении настроек Tautulli.",
|
||||
"components.Settings.toastTautulliSettingsSuccess": "Настройки Tautulli успешно сохранены!",
|
||||
"components.Settings.validationUrlBaseTrailingSlash": "База URL не должна заканчиваться косой чертой",
|
||||
@@ -1241,7 +1241,7 @@
|
||||
"components.Settings.Notifications.NotificationsGotify.validationTypes": "Вы должны выбрать как минимум один тип уведомления",
|
||||
"components.Settings.Notifications.NotificationsGotify.validationUrlRequired": "Вы должны указать действующий URL",
|
||||
"components.Settings.Notifications.NotificationsGotify.validationUrlTrailingSlash": "URL не должен заканчиваться слешем",
|
||||
"components.Settings.SettingsJobsCache.imagecacheDescription": "Если включено, Jellyseerr будет проксировать и кэшировать изображения из предварительно настроенных внешних источников. Кэшированные изображения сохраняются в папку конфигурации. Вы можете найти файлы в <code>{appDataPath}/cache/images</code>.",
|
||||
"components.Settings.SettingsJobsCache.imagecacheDescription": "Если включено, Overserr будет проксировать и кэшировать изображения из предварительно настроенных внешних источников. Кэшированные изображения сохраняются в папку конфигурации. Вы можете найти файлы в <code>{appDataPath}/cache/images</code>.",
|
||||
"components.Settings.tautulliSettings": "Настройки Tautulli",
|
||||
"components.StatusBadge.seasonepisodenumber": "S{seasonNumber}E{episodeNumber}",
|
||||
"components.Discover.customizediscover": "Настроить Обнаружение",
|
||||
@@ -1257,92 +1257,21 @@
|
||||
"components.Selector.showmore": "Показать больше",
|
||||
"components.Settings.SettingsJobsCache.imagecachesize": "Размер кэша",
|
||||
"components.Settings.validationUrlBaseLeadingSlash": "Базовый URL должен начинаться с косой черты",
|
||||
"components.Layout.UserWarnings.emailRequired": "Требуется указать email адрес.",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Требуется указать пароль.",
|
||||
"components.Login.emailtooltip": "Адрес не обязательно должен быть связан с вашим {mediaServerName} сервером.",
|
||||
"components.Login.initialsignin": "Подключиться",
|
||||
"components.Login.initialsigningin": "Подключение…",
|
||||
"components.Login.save": "Добавить",
|
||||
"components.Login.saving": "Добавление…",
|
||||
"components.Login.signinwithjellyfin": "Используйте свой {mediaServerName} аккаунт",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.username": "Имя пользователя",
|
||||
"components.Login.validationEmailFormat": "Неверный email",
|
||||
"components.Login.validationemailformat": "Необходим корректный email",
|
||||
"components.Login.validationhostformat": "Необходим корректный URL",
|
||||
"components.Login.validationhostrequired": "Необходим {mediaServerName} URL",
|
||||
"components.Login.validationusernamerequired": "Необходимо имя пользователя",
|
||||
"components.ManageSlideOver.removearr": "Удалить из {arr}",
|
||||
"components.ManageSlideOver.removearr4k": "Удалить из 4К {arr}",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Количество голосов пользователей TMDB",
|
||||
"components.Discover.FilterSlideover.voteCount": "Количество голосов между {minValue} и {maxValue}",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Стриминговые сервисы фильмов TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Стриминговые сервисы сериалов TMDB",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Автоматически добавлять дополнительный тег с ID и именем запросившего пользователя",
|
||||
"components.Settings.RadarrModal.tagRequests": "Теги запросов",
|
||||
"components.MovieDetails.imdbuserscore": "Оценка пользователей IMDB",
|
||||
"components.MovieDetails.openradarr": "Открыть фильм в Radarr",
|
||||
"components.MovieDetails.play": "Запустить на {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Запустить 4К на {mediaServerName}",
|
||||
"components.Settings.RadarrModal.tagRequests": "Тег запросов",
|
||||
"components.Layout.UserWarnings.emailInvalid": "Неверный email адрес.",
|
||||
"components.Settings.SonarrModal.seriesType": "Тип сериала",
|
||||
"components.Discover.FilterSlideover.voteCount": "Количество голосов от {minValue} до {maxValue}",
|
||||
"components.Login.validationEmailRequired": "Вы должны указать адрес электронной почты",
|
||||
"components.Settings.Notifications.userEmailRequired": "Требуется email пользователя",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Сканировать недавно добавленное в Jellyfin",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Сервисы потоковой передачи фильмов TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Сервисы потоковой передачи сериалов TMDB",
|
||||
"components.Login.description": "Поскольку вы впервые входите в систему {ApplicationName}, вам необходимо добавить адрес электронной почты.",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Звук уведомлений",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Автодобавление тега с именем и ID пользователя, отправившего запрос",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Тип аниме",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Количество голосов от пользователей TMDB",
|
||||
"components.Login.credentialerror": "Введено неверное имя пользователя или пароль.",
|
||||
"components.Login.title": "Добавить email",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Это приведет к необратимому удалению {MediaType} из {arr}, включая все файлы.",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Поддержать Jellyseerr",
|
||||
"components.Settings.SonarrModal.tagRequests": "Тег запросов",
|
||||
"components.MovieDetails.downloadstatus": "Статус загрузки",
|
||||
"components.MovieDetails.openradarr4k": "Открыть фильм в 4К Radarr",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Устройство по умолчанию",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Сканировать всю библиотеку Jellyfin",
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Синхронизировать доступность медиа",
|
||||
"components.Settings.jellyfinSettingsFailure": "Что-то пошло не так во время сохранения настроек {mediaServerName}.",
|
||||
"components.Settings.jellyfinSettingsSuccess": "Настройки {mediaServerName} успешно сохранены!",
|
||||
"components.Settings.jellyfinlibraries": "Библиотеки {mediaServerName}",
|
||||
"components.Settings.jellyfinlibrariesDescription": "Библиотеки {mediaServerName} проверяются на наличие заголовков. Нажмите кнопку ниже, если в списке не хватает библиотек.",
|
||||
"components.Settings.jellyfinsettings": "Настройки {mediaServerName}",
|
||||
"components.Settings.internalUrl": "Внутренний URL-адрес",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Автодобавление тега с именем и ID пользователя, отправившего запрос",
|
||||
"components.Settings.jellyfinSettings": "Настройки {mediaServerName}",
|
||||
"components.Settings.jellyfinSettingsDescription": "Необязательно настраивать внутреннюю и внешнюю конечные точки для вашего сервера {mediaServerName}. В большинстве случаев внешний URL-адрес отличается от внутреннего. Пользовательский URL-адрес для сброса пароля также может быть задан для входа в систему {mediaServerName}, на случай, если вы хотите перенаправить на другую страницу для сброса пароля.",
|
||||
"components.Settings.jellyfinsettingsDescription": "Настройте свой {mediaServerName} сервер. {mediaServerName} отсканирует ваши библиотеки, чтобы увидеть, какие библиотеки доступны.",
|
||||
"components.Settings.manualscanJellyfin": "Сканировать библиотеки вручную",
|
||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||
"components.Settings.syncJellyfin": "Синхронизировать библиотеки",
|
||||
"components.Settings.syncing": "Синхронизация",
|
||||
"components.Settings.timeout": "Время ожидания",
|
||||
"components.Setup.signin": "Войти",
|
||||
"components.Setup.signinWithPlex": "Используйте свой Plex аккаунт",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> успешно удален из списка наблюдения!",
|
||||
"components.TitleCard.watchlistError": "Что-то пошло не так, попробуйте еще раз.",
|
||||
"components.TvDetails.play": "Запустить в {mediaServerName}",
|
||||
"components.TvDetails.play4k": "Запустить 4К в {mediaServerName}",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {userCount, plural, one {# новый пользователь} other {# новых пользователя(ей)}} успешно импортированы из {mediaServerName}!",
|
||||
"components.UserList.importfromJellyfin": "Добавить пользователей из {mediaServerName}",
|
||||
"components.UserList.noJellyfinuserstoimport": "Нет пользователей {mediaServerName} для импорта.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "Пользователь {mediaServerName}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Сохранение…",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Звук уведомлений",
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Синхронизация доступности медиа",
|
||||
"components.Settings.SonarrModal.tagRequests": "Теги запросов",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Автоматически добавлять тег с ID и именем запросившего пользователя",
|
||||
"i18n.collection": "Коллекция",
|
||||
"components.Setup.configuremediaserver": "Настройте медиасервер",
|
||||
"components.UserList.importfromJellyfinerror": "Что-то пошло не так при импорте пользователей из {mediaServerName}.",
|
||||
"components.UserList.newJellyfinsigninenabled": "Параметр <strong>Включить новый вход в {mediaServerName}</strong> в настоящее время включен. Пользователей {mediaServerName} с доступом к библиотеке не нужно импортировать для входа.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "Электронная почта",
|
||||
"components.UserProfile.localWatchlist": "Список наблюдения {username}",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Обычно это выполняется только раз в 24 часа. Jellyseerr будет более настойчиво проверять недавно добавленный сервер {mediaServerName}. Если вы впервые настраиваете Jellyseerr, то рекомендуем однократное полное сканирование библиотеки!",
|
||||
"components.TitleCard.watchlistCancel": "наблюдение за <strong>{title}</strong> отменено.",
|
||||
"components.Settings.saving": "Сохранение…",
|
||||
"components.TitleCard.addToWatchList": "Добавить в список наблюдения",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> успешно добавлен в список наблюдения!",
|
||||
"components.Settings.save": "Сохранить изменения",
|
||||
"components.Setup.signinWithJellyfin": "Используйте свой {mediaServerName} аккаунт",
|
||||
"components.UserList.mediaServerUser": "Пользователь {mediaServerName}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Сохранить изменения",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Устройство по умолчанию"
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Звук уведомления",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Устройство по умолчанию",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Тип аниме-сериала",
|
||||
"components.Settings.SonarrModal.seriesType": "Тип сериала",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Звук уведомления",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Устройство по умолчанию"
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"components.Discover.CreateSlider.editsuccess": "Urejen drsnik in shranjene nastavitve prilagajanja odkrivanja.",
|
||||
"components.CollectionDetails.numberofmovies": "{count} film/ov",
|
||||
"components.Discover.CreateSlider.slidernameplaceholder": "Ime drsnika",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Premiera ↓",
|
||||
"components.AppDataWarning.dockerVolumeMissingDescription": "Pripenjanje nosilca <code>{appDataPath}</code> ni bilo pravilno konfigurirano. Vsi podatki bodo izbrisani, ko se vsebnik zaustavi ali znova zažene.",
|
||||
"components.Discover.DiscoverMovies.sortPopularityDesc": "Priljubljenost ↑",
|
||||
"components.AirDateBadge.airsrelative": "Predvajanje {relativeTime}",
|
||||
"components.CollectionDetails.overview": "Pregled",
|
||||
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# Active Filter} drugo {# Active Filters}}",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "Ocena TMDB ↓",
|
||||
"components.AirDateBadge.airedrelative": "Predvajano {relativeTime}",
|
||||
"components.Discover.CreateSlider.searchStudios": "Iskanje studiev …",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Datum izdaje ↑",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Navedite ID omrežja TMDB",
|
||||
"components.Discover.CreateSlider.addfail": "Novega drsnika ni bilo mogoče ustvariti.",
|
||||
"components.CollectionDetails.requestcollection": "Zahtevaj zbirko",
|
||||
"components.Discover.DiscoverMovieGenre.genreMovies": "Filmi: {genre}",
|
||||
"components.Discover.DiscoverMovieLanguage.languageMovies": "Filmi: {language}",
|
||||
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Active Filter} other {# Active Filters}}",
|
||||
"components.Discover.DiscoverMovies.sortPopularityAsc": "Priljubljenost ↓",
|
||||
"components.Discover.CreateSlider.needresults": "Imeti morate vsaj 1 rezultat.",
|
||||
"components.Discover.CreateSlider.addcustomslider": "Ustvari drsnik po meri",
|
||||
"components.Discover.DiscoverTv.sortPopularityAsc": "Priljubljenost ↓",
|
||||
"components.Discover.CreateSlider.editSlider": "Uredi drsnik",
|
||||
"components.Discover.DiscoverTv.sortTitleAsc": "Naslov (a-ž) ↓",
|
||||
"components.Discover.CreateSlider.validationDatarequired": "Navesti morate vrednost podatkov.",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Premiera ↑",
|
||||
"components.Discover.DiscoverTv.discovertv": "Serije",
|
||||
"components.Discover.DiscoverSliderEdit.deletefail": "Drsnika ni bilo mogoče izbrisati.",
|
||||
"components.Discover.CreateSlider.providetmdbstudio": "Navedite ID studia v TMDB",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Naslov (a-ž) ↑",
|
||||
"components.Discover.DiscoverStudio.studioMovies": "{studio} filmi",
|
||||
"components.Discover.DiscoverTv.sortPopularityDesc": "Priljubljenost ↑",
|
||||
"components.Discover.CreateSlider.searchGenres": "Išči žanre …",
|
||||
"components.Discover.CreateSlider.editfail": "Drsnika ni bilo mogoče urediti.",
|
||||
"components.Discover.CreateSlider.starttyping": "Tipkajte za iskanje.",
|
||||
"components.Discover.DiscoverSliderEdit.enable": "Preklopi vidnost",
|
||||
"components.Discover.CreateSlider.addSlider": "Dodaj drsnik",
|
||||
"components.CollectionDetails.requestcollection4k": "Zahtevaj zbirko 4K",
|
||||
"components.Discover.CreateSlider.providetmdbsearch": "Vnesite iskalno poizvedbo",
|
||||
"components.Discover.DiscoverNetwork.networkSeries": "{network} serije",
|
||||
"components.Discover.CreateSlider.providetmdbkeywordid": "Navedite ID ključne besede TMDB",
|
||||
"components.Discover.DiscoverMovieKeyword.keywordMovies": "Filmi: {keywordTitle}",
|
||||
"components.Discover.CreateSlider.validationTitlerequired": "Navesti morate naslov.",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Datum izdaje ↓",
|
||||
"components.Discover.CreateSlider.nooptions": "Ni zadetkov.",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "Ocena TMDB ↑",
|
||||
"components.Discover.CreateSlider.searchKeywords": "Iskanje po ključnih besedah …",
|
||||
"components.Discover.CreateSlider.addsuccess": "Ustvarjen nov drsnik in shranjene nastavitve prilagajanja odkrivanja.",
|
||||
"components.Discover.DiscoverSliderEdit.deletesuccess": "Drsnik je bil uspešno izbrisan.",
|
||||
"components.Discover.DiscoverMovies.discovermovies": "Filmi",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Naslov (a-ž) ↓",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Navedite ID žanra TMDB",
|
||||
"components.Discover.DiscoverTv.sortTitleDesc": "Naslov (a-ž) ↑",
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Odstrani",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "Ocena TMDB ↓",
|
||||
"components.Discover.DiscoverTvGenre.genreSeries": "{genre} Series"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,10 +10,10 @@
|
||||
"components.Discover.DiscoverMovieLanguage.languageMovies": "Фільми мовою \"{language}\"",
|
||||
"components.Discover.DiscoverNetwork.networkSeries": "Серіали {network}",
|
||||
"components.Discover.DiscoverStudio.studioMovies": "Фільми {studio}",
|
||||
"components.Discover.DiscoverTvGenre.genreSeries": "Серіали в жанрі {genre}",
|
||||
"components.Discover.DiscoverTvLanguage.languageSeries": "Серіали мовою {language}",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Ваш список перегляду Plex",
|
||||
"components.Discover.DiscoverWatchlist.watchlist": "Список перегляду Plex",
|
||||
"components.Discover.DiscoverTvGenre.genreSeries": "Серіали в жанрі \"{genre}\"",
|
||||
"components.Discover.DiscoverTvLanguage.languageSeries": "Серіали мовою \"{language}\"",
|
||||
"components.Discover.DiscoverWatchlist.discoverwatchlist": "Your Watchlist",
|
||||
"components.Discover.DiscoverWatchlist.watchlist": "Список спостереження Plex",
|
||||
"components.Discover.MovieGenreList.moviegenres": "Фільми за жанрами",
|
||||
"components.Discover.MovieGenreSlider.moviegenres": "Фільми за жанрами",
|
||||
"components.Discover.NetworkSlider.networks": "Телеканали",
|
||||
@@ -25,7 +25,7 @@
|
||||
"components.Discover.discovertv": "Популярні серіали",
|
||||
"components.Discover.emptywatchlist": "Тут з’являться медіафайли, додані до вашого <PlexWatchlistSupportLink>списку спостереження Plex</PlexWatchlistSupportLink>.",
|
||||
"components.Discover.noRequests": "Жодних запитів.",
|
||||
"components.Discover.plexwatchlist": "Ваш список перегляду Plex",
|
||||
"components.Discover.plexwatchlist": "Ваш список спостереження Plex",
|
||||
"components.Discover.popularmovies": "Популярні фільми",
|
||||
"components.Discover.populartv": "Популярні серіали",
|
||||
"components.Discover.recentlyAdded": "Нещодавно додані",
|
||||
@@ -118,7 +118,7 @@
|
||||
"components.Layout.Sidebar.settings": "Налаштування",
|
||||
"components.Layout.Sidebar.users": "Користувачі",
|
||||
"components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "Запити на фільми",
|
||||
"components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "Запити на сезони",
|
||||
"components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "Запити на серіали",
|
||||
"components.Layout.UserDropdown.myprofile": "Профіль",
|
||||
"components.Layout.UserDropdown.requests": "Запити",
|
||||
"components.Layout.UserDropdown.settings": "Налаштування",
|
||||
@@ -142,24 +142,24 @@
|
||||
"components.ManageSlideOver.downloadstatus": "Завантаження",
|
||||
"components.ManageSlideOver.manageModalAdvanced": "Просунутий",
|
||||
"components.ManageSlideOver.manageModalClearMedia": "Очистити дані",
|
||||
"components.ManageSlideOver.manageModalClearMediaWarning": "* Це призведе до незворотного видалення всіх даних для цього {mediaType}а, включаючи будь-які запити. Якщо цей елемент існує у вашій бібліотеці {mediaServerName}, мультимедійна інформація про нього буде відтворена під час наступного сканування.",
|
||||
"components.ManageSlideOver.manageModalClearMediaWarning": "* Це призведе до незворотного видалення всіх даних для цього {mediaType}а, включаючи будь-які запити. Якщо цей елемент існує у вашій бібліотеці {mediaServerName}, мультимедійна інформація про нього буде відтворена під час наступного сканування. ",
|
||||
"components.ManageSlideOver.manageModalIssues": "Відкриті проблеми",
|
||||
"components.ManageSlideOver.manageModalMedia": "Media",
|
||||
"components.ManageSlideOver.manageModalMedia4k": "4K Media",
|
||||
"components.ManageSlideOver.manageModalNoRequests": "Запитів немає.",
|
||||
"components.ManageSlideOver.manageModalRequests": "Запити",
|
||||
"components.ManageSlideOver.manageModalTitle": "Управління {mediaType}ом",
|
||||
"components.ManageSlideOver.manageModalTitle": "Управління {mediaType}",
|
||||
"components.ManageSlideOver.mark4kavailable": "Позначити як доступний у 4К",
|
||||
"components.ManageSlideOver.markallseasons4kavailable": "Позначити всі сезони як доступні в 4K",
|
||||
"components.ManageSlideOver.markallseasonsavailable": "Позначити всі сезони як доступні",
|
||||
"components.ManageSlideOver.markallseasonsavailable": "Mark All Seasons as Available",
|
||||
"components.ManageSlideOver.markavailable": "Позначити як доступний",
|
||||
"components.ManageSlideOver.movie": "фільм",
|
||||
"components.ManageSlideOver.openarr": "Відкрити в {arr}",
|
||||
"components.ManageSlideOver.openarr4k": "Відкрити в 4К {arr}",
|
||||
"components.ManageSlideOver.opentautulli": "Відкрити в Tautulli",
|
||||
"components.ManageSlideOver.pastdays": "Останні {days, number} днів",
|
||||
"components.ManageSlideOver.playedby": "Переглядає",
|
||||
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {перегляд} other {переглядів}}",
|
||||
"components.ManageSlideOver.playedby": "Грає",
|
||||
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {грає} other {грають}}",
|
||||
"components.ManageSlideOver.tvshow": "серіал",
|
||||
"components.MediaSlider.ShowMoreCard.seemore": "Подивитися більше",
|
||||
"components.MovieDetails.MovieCast.fullcast": "Повний акторський склад",
|
||||
@@ -210,7 +210,7 @@
|
||||
"components.NotificationTypeSelector.mediaapproved": "Схвалення медіа-запитів",
|
||||
"components.NotificationTypeSelector.mediaapprovedDescription": "Надсилати повідомлення, коли медіа-запити схвалюються вручну.",
|
||||
"components.NotificationTypeSelector.mediaautorequested": "Запит надіслано автоматично",
|
||||
"components.NotificationTypeSelector.mediaautorequestedDescription": "Отримуйте сповіщення, коли нові медіа-запити автоматично надсилаються для елементів у вашому списку перегляду Plex.",
|
||||
"components.NotificationTypeSelector.mediaautorequestedDescription": "Отримуйте сповіщення, коли нові медіа-запити автоматично надсилаються для елементів у вашому списку спостереження Plex.",
|
||||
"components.NotificationTypeSelector.mediaavailable": "Доступні нові медіафайли",
|
||||
"components.NotificationTypeSelector.mediaavailableDescription": "Надсилати повідомлення, коли запитані медіафайли стають доступними.",
|
||||
"components.NotificationTypeSelector.mediadeclined": "Відхилення медіа-запитів",
|
||||
@@ -245,13 +245,13 @@
|
||||
"components.PermissionEdit.autoapproveMovies": "Автоматичне схвалення фільмів",
|
||||
"components.PermissionEdit.autoapproveMoviesDescription": "Надати дозвіл на автоматичне схвалення всіх фільмів, відмінних від 4К.",
|
||||
"components.PermissionEdit.autoapproveSeries": "Автоматичне схвалення серіалів",
|
||||
"components.PermissionEdit.autoapproveSeriesDescription": "Надати дозвіл на автоматичне схвалення всіх серіалів, відмінних від 4K.",
|
||||
"components.PermissionEdit.autoapproveSeriesDescription": "Надати дозвіл на автоматичне схвалення всіх серіалів, відмінних від 4К.",
|
||||
"components.PermissionEdit.autorequest": "Автоматичний запит",
|
||||
"components.PermissionEdit.autorequestDescription": "Надайте дозвіл на автоматичне надсилання запитів на медіафайли, відмінні від 4K, через список перегляду Plex.",
|
||||
"components.PermissionEdit.autorequestDescription": "Надайте дозвіл на автоматичне надсилання запитів на медіафайли, відмінні від 4K, через Plex Watchlist.",
|
||||
"components.PermissionEdit.autorequestMovies": "Автоматичний запит фільмів",
|
||||
"components.PermissionEdit.autorequestMoviesDescription": "Надайте дозвіл на автоматичне надсилання запитів на фільми, відмінні від 4K, через список перегляду Plex.",
|
||||
"components.PermissionEdit.autorequestMoviesDescription": "Надайте дозвіл на автоматичне надсилання запитів на фільми, відмінні від 4K, через Plex Watchlist.",
|
||||
"components.PermissionEdit.autorequestSeries": "Автоматичний запит Серіалів",
|
||||
"components.PermissionEdit.autorequestSeriesDescription": "Надайте дозвіл на автоматичне надсилання запитів на серіали, відмінні від 4K, через список перегляду Plex.",
|
||||
"components.PermissionEdit.autorequestSeriesDescription": "Надайте дозвіл на автоматичне надсилання запитів на серіали, відмінні від 4K, через Plex Watchlist.",
|
||||
"components.PermissionEdit.createissues": "Повідомлення про проблеми",
|
||||
"components.PermissionEdit.createissuesDescription": "Надати дозвіл на повідомлення про проблеми з медіафайлами.",
|
||||
"components.PermissionEdit.manageissues": "Управління проблемами",
|
||||
@@ -266,7 +266,7 @@
|
||||
"components.PermissionEdit.request4kTv": "Запити серіалів у 4К",
|
||||
"components.PermissionEdit.request4kTvDescription": "Надати дозвіл на надсилання запитів серіалів у 4К.",
|
||||
"components.PermissionEdit.requestDescription": "Надати дозвіл на надсилання запитів усіх медіафайлів, відмінних від 4К.",
|
||||
"components.PermissionEdit.requestMovies": "Запити фільмів",
|
||||
"components.PermissionEdit.requestMovies": "Замовити фільми",
|
||||
"components.PermissionEdit.requestMoviesDescription": "Надати дозвіл на надсилання запитів усіх фільмів, відмінних від 4К.",
|
||||
"components.PermissionEdit.requestTv": "Запити серіалів",
|
||||
"components.PermissionEdit.requestTvDescription": "Надати дозвіл на надсилання запитів усіх серіалів, відмінних від 4К.",
|
||||
@@ -280,8 +280,8 @@
|
||||
"components.PermissionEdit.viewrecentDescription": "Надайте дозвіл на перегляд списку нещодавно доданих медіа.",
|
||||
"components.PermissionEdit.viewrequests": "Перегляд запитів",
|
||||
"components.PermissionEdit.viewrequestsDescription": "Надати дозвіл на перегляд медіа-запитів, надісланих іншими користувачами.",
|
||||
"components.PermissionEdit.viewwatchlists": "Перегляд списків переглядів Plex",
|
||||
"components.PermissionEdit.viewwatchlistsDescription": "Надайте дозвіл на перегляд списків перегляду Plex інших користувачів.",
|
||||
"components.PermissionEdit.viewwatchlists": "Перегляньте списки спостереження Plex",
|
||||
"components.PermissionEdit.viewwatchlistsDescription": "Надайте дозвіл на перегляд списків спостереження Plex інших користувачів.",
|
||||
"components.PersonDetails.alsoknownas": "Також відомий(а) як: {names}",
|
||||
"components.PersonDetails.appearsin": "Появи у фільмах та серіалах",
|
||||
"components.PersonDetails.ascharacter": "в ролі {character}",
|
||||
@@ -291,10 +291,10 @@
|
||||
"components.PlexLoginButton.signingin": "Виконується вхід...",
|
||||
"components.PlexLoginButton.signinwithplex": "Увійти",
|
||||
"components.QuotaSelector.days": "{count, plural, one {день} other {днів}}",
|
||||
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{movies} на {quotaDays} {days}</quotaUnits>",
|
||||
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{фільмів} за {quotaDays} {днів}</quotaUnits>",
|
||||
"components.QuotaSelector.movies": "{count, plural, one {фільм} other {фільми}}",
|
||||
"components.QuotaSelector.seasons": "{count, plural, one {сезон} other {сезони}}",
|
||||
"components.QuotaSelector.tvRequests": "{quotaLimit} <quotaUnits>{seasons} на {quotaDays} {days}</quotaUnits>",
|
||||
"components.QuotaSelector.tvRequests": "{quotaLimit} <quotaUnits>{сезонів} за {quotaDays} {днів}</quotaUnits>",
|
||||
"components.QuotaSelector.unlimited": "Необмежено",
|
||||
"components.RegionSelector.regionDefault": "Всі регіони",
|
||||
"components.RegionSelector.regionServerDefault": "За замовчуванням ({region})",
|
||||
@@ -329,7 +329,7 @@
|
||||
"components.RequestCard.deleterequest": "Видалити запит",
|
||||
"components.RequestCard.editrequest": "Редагувати запит",
|
||||
"components.RequestCard.failedretry": "Щось пішло не так при спробі повторити запит.",
|
||||
"components.RequestCard.mediaerror": "{mediaType} Не знайдено",
|
||||
"components.RequestCard.mediaerror": "Назва, пов'язана з цим запитом, більше недоступна.",
|
||||
"components.RequestCard.seasons": "{seasonCount, plural, one {Сезон} other {Сезони}}",
|
||||
"components.RequestCard.tmdbid": "TMDB ID",
|
||||
"components.RequestCard.tvdbid": "TheTVDB ID",
|
||||
@@ -338,7 +338,7 @@
|
||||
"components.RequestList.RequestItem.deleterequest": "Видалити запит",
|
||||
"components.RequestList.RequestItem.editrequest": "Редагувати запит",
|
||||
"components.RequestList.RequestItem.failedretry": "Щось пішло не так при спробі повторити запит.",
|
||||
"components.RequestList.RequestItem.mediaerror": "{mediaType} Не знайдено",
|
||||
"components.RequestList.RequestItem.mediaerror": "Назва, пов'язана з цим запитом, більше недоступна.",
|
||||
"components.RequestList.RequestItem.modified": "Змінено",
|
||||
"components.RequestList.RequestItem.modifieduserdate": "{date} користувачем {user}",
|
||||
"components.RequestList.RequestItem.requested": "Запрошений",
|
||||
@@ -366,17 +366,17 @@
|
||||
"components.RequestModal.QuotaDisplay.allowedRequests": "Вам дозволено запитувати <strong>{limit}</strong> {type} кожні <strong>{days}</strong> днів.",
|
||||
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "Цьому користувачеві дозволено запитувати <strong>{limit}</strong> {type} кожні <strong>{days}</strong> днів.",
|
||||
"components.RequestModal.QuotaDisplay.movie": "фільм",
|
||||
"components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {фільм} other {фільмів}}",
|
||||
"components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {фільм} other {фільми}}",
|
||||
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Залишилося недостатньо запитів на сезони",
|
||||
"components.RequestModal.QuotaDisplay.quotaLink": "Ви можете переглянути зведення ваших обмежень на кількість запитів на <ProfileLink>сторінці вашого профілю</ProfileLink>.",
|
||||
"components.RequestModal.QuotaDisplay.quotaLinkUser": "Ви можете переглянути зведення обмежень на кількість запитів цього користувача на <ProfileLink>сторінці його профілю</ProfileLink>.",
|
||||
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {Запитів на {type} не залишилося} other {Залишилось <strong>#</strong> запити(ів) на {type}}}",
|
||||
"components.RequestModal.QuotaDisplay.requiredquota": "Вам необхідно мати принаймні <strong>{seasons}</strong> {seasons, plural, one {запит на сезони} other {запиту(ів) на сезони}} для того, щоб надіслати запит на цей серіал.",
|
||||
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {запитів {type} не залишилося} other {залишилось <strong>#</strong> запиту(ів) {type}}}",
|
||||
"components.RequestModal.QuotaDisplay.requiredquota": "Вам необхідно мати принаймні <strong>{seasons}</strong> {seasons, plural, one {запит на сезони} other {запиту(ів) на сезони}} для того , щоб надіслати запит на цей серіал.",
|
||||
"components.RequestModal.QuotaDisplay.requiredquotaUser": "Цьому користувачеві необхідно мати принаймні <strong>{seasons}</strong> {seasons, plural, one {запит на сезони} other {запиту(ів) на сезони}} для того, щоб надіслати запит на цей серіал.",
|
||||
"components.RequestModal.QuotaDisplay.season": "сезон",
|
||||
"components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {сезон} other {сезонів}}",
|
||||
"components.RequestModal.SearchByNameModal.nomatches": "Нам не вдалося знайти відповідність для цього серіалу.",
|
||||
"components.RequestModal.SearchByNameModal.notvdbiddescription": "Нам не вдалося автоматично знайти цей серіал. Будь ласка, виберіть правильний збіг зі списку нижче.",
|
||||
"components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {сезон} other {сезони}}",
|
||||
"components.RequestModal.SearchByNameModal.nomatches": "Нам не вдалося знайти відповідність для цієї серії.",
|
||||
"components.RequestModal.SearchByNameModal.notvdbiddescription": "Ми не змогли автоматично виконати ваш запит. Будь ласка, виберіть правильний збіг зі списку нижче.",
|
||||
"components.RequestModal.alreadyrequested": "Вже запрошений",
|
||||
"components.RequestModal.approve": "Схвалити запит",
|
||||
"components.RequestModal.autoapproval": "Автоматичне схвалення",
|
||||
@@ -385,9 +385,9 @@
|
||||
"components.RequestModal.errorediting": "Щось пішло не так під час редагування запиту.",
|
||||
"components.RequestModal.extras": "Додатково",
|
||||
"components.RequestModal.numberofepisodes": "# епізодів",
|
||||
"components.RequestModal.pending4krequest": "Очікуючий запит в 4К",
|
||||
"components.RequestModal.pending4krequest": "",
|
||||
"components.RequestModal.pendingapproval": "Ваш запит чекає схвалення.",
|
||||
"components.RequestModal.pendingrequest": "Очікуючий запит",
|
||||
"components.RequestModal.pendingrequest": "",
|
||||
"components.RequestModal.requestApproved": "Запит на <strong>{title}</strong> схвалений!",
|
||||
"components.RequestModal.requestCancel": "Запит на <strong>{title}</strong> скасовано.",
|
||||
"components.RequestModal.requestSuccess": "<strong>{title}</strong> успішно запрошений!",
|
||||
@@ -404,7 +404,7 @@
|
||||
"components.RequestModal.requestmovietitle": "Запит на фільм",
|
||||
"components.RequestModal.requestseasons": "Запросити {seasonCount} {seasonCount, plural, one {сезон} other {сезону(ів)}}",
|
||||
"components.RequestModal.requestseasons4k": "Запит {seasonCount} {seasonCount, plural, one {сезону} other {сезонів}} у 4К",
|
||||
"components.RequestModal.requestseries4ktitle": "Запросити серіал у 4K",
|
||||
"components.RequestModal.requestseries4ktitle": "Надіслати запит на серіал у 4K",
|
||||
"components.RequestModal.requestseriestitle": "Запит на серіал",
|
||||
"components.RequestModal.season": "Сезон",
|
||||
"components.RequestModal.seasonnumber": "Сезон {number}",
|
||||
@@ -452,7 +452,7 @@
|
||||
"components.Settings.Notifications.NotificationsPushbullet.accessToken": "Токен доступу",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Створіть токен у <PushbulletSettingsLink>налаштуваннях облікового запису</PushbulletSettingsLink>",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Активувати службу",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.channelTag": "Тег каналу",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.channelTag": "Channel Tag",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Не вдалося зберегти налаштування сповіщень Pushbullet.",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Налаштування сповіщень Pushbullet успішно збережено!",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Не вдалося надіслати тестове повідомлення до Pushbullet.",
|
||||
@@ -609,7 +609,7 @@
|
||||
"components.Settings.RadarrModal.validationRootFolderRequired": "Ви повинні вибрати кореневий каталог",
|
||||
"components.Settings.SettingsAbout.Releases.currentversion": "Поточна",
|
||||
"components.Settings.SettingsAbout.Releases.latestversion": "Остання",
|
||||
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Дані про реліз наразі недоступні.",
|
||||
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Дані про дозвіл в даний час недоступні.",
|
||||
"components.Settings.SettingsAbout.Releases.releases": "Релізи",
|
||||
"components.Settings.SettingsAbout.Releases.versionChangelog": "Зміни у версії {version}",
|
||||
"components.Settings.SettingsAbout.Releases.viewchangelog": "Переглянути список змін",
|
||||
@@ -626,7 +626,7 @@
|
||||
"components.Settings.SettingsAbout.preferredmethod": "Переважний спосіб",
|
||||
"components.Settings.SettingsAbout.runningDevelop": "Ви використовуєте гілку <code>develop</code> проекту Jellyseerr, яка рекомендується тільки для тих, хто робить внесок у розробку або допомагає в тестуванні.",
|
||||
"components.Settings.SettingsAbout.supportoverseerr": "Підтримати Jellyseerr",
|
||||
"components.Settings.SettingsAbout.timezone": "Часовий пояс",
|
||||
"components.Settings.SettingsAbout.timezone": "Годинний пояс",
|
||||
"components.Settings.SettingsAbout.totalmedia": "Усього мультимедіа",
|
||||
"components.Settings.SettingsAbout.totalrequests": "Усього запитів",
|
||||
"components.Settings.SettingsAbout.uptodate": "Актуальна",
|
||||
@@ -645,7 +645,7 @@
|
||||
"components.Settings.SettingsJobsCache.download-sync": "Синхронізувати завантаження",
|
||||
"components.Settings.SettingsJobsCache.download-sync-reset": "Скинути синхронізацію завантажень",
|
||||
"components.Settings.SettingsJobsCache.editJobSchedule": "Змінити завдання",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Поточна частота",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleCurrent": "Current Frequency",
|
||||
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Частота",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Кожен {jobScheduleHours, plural, one {година} other {{jobScheduleHours} години(ів)}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Кожну {jobScheduleMinutes, plural, one {хвилину} other {{jobScheduleMinutes} хвилин(и)}}",
|
||||
@@ -837,7 +837,7 @@
|
||||
"components.Settings.sonarrsettings": "Налаштування Sonarr",
|
||||
"components.Settings.ssl": "SSL",
|
||||
"components.Settings.startscan": "Почати сканування",
|
||||
"components.Settings.tautulliApiKey": "Ключ API",
|
||||
"components.Settings.tautulliApiKey": "API Key",
|
||||
"components.Settings.tautulliSettings": "Tautulli Налаштування",
|
||||
"components.Settings.tautulliSettingsDescription": "За бажанням налаштуйте параметри для вашого сервера Tautulli. Jellyseerr отримує дані історії переглядів для медіафайлів Plex від Tautulli.",
|
||||
"components.Settings.toastApiKeyFailure": "Щось пішло не так при створенні нового ключа API.",
|
||||
@@ -966,8 +966,8 @@
|
||||
"components.UserList.user": "Користувач",
|
||||
"components.UserList.usercreatedfailed": "Щось пішло не так при створенні користувача.",
|
||||
"components.UserList.usercreatedfailedexisting": "Вказана адреса електронної пошти вже використовується іншим користувачем.",
|
||||
"components.UserList.usercreatedsuccess": "Користувача успішно створено!",
|
||||
"components.UserList.userdeleted": "Користувача успішно видалено!",
|
||||
"components.UserList.usercreatedsuccess": "Користувач успішно створено!",
|
||||
"components.UserList.userdeleted": "Користувач успішно видалено!",
|
||||
"components.UserList.userdeleteerror": "Щось пішло не так при видаленні користувача.",
|
||||
"components.UserList.userfail": "Щось пішло не так при збереженні дозволів користувача.",
|
||||
"components.UserList.userlist": "Список користувачів",
|
||||
@@ -996,9 +996,9 @@
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.owner": "Власник",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Користувач Plex",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmovies": "Автоматичний запит фільмів",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Автоматично запитувати фільми з вашого <PlexWatchlistSupportLink>списку перегляду Plex</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncmoviestip": "Автоматично надсилайте запит на перегляд фільмів у своєму <PlexWatchlistSupportLink>списку спостереження Plex</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Автоматичний запит Серіалів",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Автоматично запитувати серіали з вашого <PlexWatchlistSupportLink>списку перегляду Plex</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Автоматично надсилайте запит на серіал у своєму <PlexWatchlistSupportLink>списку спостереження Plex</PlexWatchlistSupportLink>",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.region": "Регіон для пошуку фільмів та серіалів",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Контент фільтрується за доступністю у вибраному регіоні",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.role": "Роль",
|
||||
@@ -1031,7 +1031,7 @@
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Надсилати без звуку",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Надсилати повідомлення без звуку",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "ID чату",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "<TelegramBotLink>Почніть чат</TelegramBotLink>, додайте <GetIdBotLink>@get_id_bot</GetIdBotLink> і виконайте команду <code>/my_id</code>",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "<TelegramBotLink>Почніть чат</TelegramBotLink>, додайте <GetIdBotLink>@get_id_bot</GetIdBotLink> і виконайте команду <code>/my_id</code>",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Не вдалося зберегти налаштування сповіщень Telegram.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Налаштування сповіщень Telegram успішно збережено!",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Ви повинні надати дійсний ID користувача",
|
||||
@@ -1067,15 +1067,15 @@
|
||||
"components.UserProfile.UserSettings.menuNotifications": "Сповіщення",
|
||||
"components.UserProfile.UserSettings.menuPermissions": "Дозволи",
|
||||
"components.UserProfile.UserSettings.unauthorizedDescription": "У вас немає дозволу на зміну налаштувань цього користувача.",
|
||||
"components.UserProfile.emptywatchlist": "Тут з’являться медіафайли, додані до вашого <PlexWatchlistSupportLink>списку перегляду Plex</PlexWatchlistSupportLink>.",
|
||||
"components.UserProfile.emptywatchlist": "Тут з’являться медіафайли, додані до вашого <PlexWatchlistSupportLink>списку спостереження Plex</PlexWatchlistSupportLink>.",
|
||||
"components.UserProfile.limit": "{remaining} з {limit}",
|
||||
"components.UserProfile.movierequests": "Запитів фільмів",
|
||||
"components.UserProfile.pastdays": "{type} (на {days} дні(в)",
|
||||
"components.UserProfile.plexwatchlist": "Список перегляду Plex",
|
||||
"components.UserProfile.pastdays": "{type} (за {days} день(ів))",
|
||||
"components.UserProfile.plexwatchlist": "Список спостереження Plex",
|
||||
"components.UserProfile.recentlywatched": "Нещодавно переглянуто",
|
||||
"components.UserProfile.recentrequests": "Останні запити",
|
||||
"components.UserProfile.requestsperdays": "залишилось {limit}",
|
||||
"components.UserProfile.seriesrequest": "Запитів сезонів",
|
||||
"components.UserProfile.seriesrequest": "Запитів серіалів",
|
||||
"components.UserProfile.totalrequests": "Усього запитів",
|
||||
"components.UserProfile.unlimited": "Необмежено",
|
||||
"i18n.advanced": "Для просунутих користувачів",
|
||||
@@ -1114,7 +1114,7 @@
|
||||
"i18n.requested": "Запрошений",
|
||||
"i18n.requesting": "Запит…",
|
||||
"i18n.resolved": "Вирішені",
|
||||
"i18n.restartRequired": "Потрібне перезавантаження",
|
||||
"i18n.restartRequired": "Restart Required",
|
||||
"i18n.resultsperpage": "Відобразити {pageSize} результатів на сторінці",
|
||||
"i18n.retry": "Повторити",
|
||||
"i18n.retrying": "Повтор…",
|
||||
@@ -1140,211 +1140,144 @@
|
||||
"Components.PermissionEdit.requestMovies": "Запити фільмів",
|
||||
"Components.PermissionEdit.autoapprove4kMovies": "Автоматичне схвалення 4К фільмів",
|
||||
"Components.PermissionEdit.autoapproveMovies": "Автоматичне схвалення фільмів",
|
||||
"components.Discover.CreateSlider.nooptions": "Немає результатів.",
|
||||
"components.Discover.CreateSlider.searchKeywords": "Ключові слова пошуку…",
|
||||
"components.Discover.CreateSlider.searchStudios": "Пошук студій…",
|
||||
"components.Discover.CreateSlider.starttyping": "Починайте писати для пошуку.",
|
||||
"components.Discover.CreateSlider.validationTitlerequired": "Ви повинні вказати назву.",
|
||||
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# Активний фільтр} other {# Активні фільтри}}",
|
||||
"components.Discover.DiscoverMovies.discovermovies": "Фільми",
|
||||
"components.Discover.DiscoverSliderEdit.enable": "Змінити видимість",
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Видалити",
|
||||
"components.Discover.DiscoverTv.discovertv": "Серіали",
|
||||
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# Активний фільтр} other {# Активні фільтри}}",
|
||||
"components.Discover.FilterSlideover.filters": "Фільтри",
|
||||
"components.Discover.FilterSlideover.from": "Від",
|
||||
"components.Discover.FilterSlideover.genres": "Жанри",
|
||||
"components.Discover.FilterSlideover.keywords": "Ключові слова",
|
||||
"components.Discover.FilterSlideover.originalLanguage": "Оригінальна мова",
|
||||
"components.Discover.FilterSlideover.ratingText": "Оцінки від {minValue} до {maxValue}",
|
||||
"components.Discover.FilterSlideover.releaseDate": "Дата релізу",
|
||||
"components.Discover.FilterSlideover.runtime": "Тривалість",
|
||||
"components.Discover.FilterSlideover.studio": "Студія",
|
||||
"components.Discover.FilterSlideover.tmdbuserscore": "Оцінка користувачів TMDB",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Кількість голосів від користувачів TMDB",
|
||||
"components.Discover.FilterSlideover.to": "До",
|
||||
"components.Discover.PlexWatchlistSlider.emptywatchlist": "Медіа додано до вашого <PlexWatchlistSupportLink>списку перегляду Plex</PlexWatchlistSupportLink>.",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Ваш список перегляду Plex",
|
||||
"components.Discover.RecentlyAddedSlider.recentlyAdded": "Нещодавно додані",
|
||||
"components.Discover.FilterSlideover.keywords": "Ключові слова",
|
||||
"components.Discover.FilterSlideover.ratingText": "Оцінки від {minValue} до {maxValue}",
|
||||
"components.Discover.FilterSlideover.tmdbuserscore": "Оцінка користувачів TMDB",
|
||||
"components.Discover.DiscoverTv.discovertv": "Серіали",
|
||||
"components.Discover.FilterSlideover.runtime": "Тривалість",
|
||||
"components.Discover.FilterSlideover.from": "Від",
|
||||
"components.Discover.studios": "Студії",
|
||||
"components.Discover.tmdbmoviegenre": "Жанр фільму TMDB",
|
||||
"components.Discover.tmdbmoviekeyword": "Ключове слово фільму TMDB",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Сервіси потокової передачі фільмів TMDB",
|
||||
"components.Discover.tmdbnetwork": "Телеканал TMDB",
|
||||
"components.Discover.tmdbstudio": "Студія TMDB",
|
||||
"components.Discover.tmdbtvgenre": "Жанр серіалу TMDB",
|
||||
"components.Discover.tmdbtvkeyword": "Ключове слово серіалу TMDB",
|
||||
"components.Discover.tvgenres": "Жанри серіалів",
|
||||
"components.Layout.UserWarnings.passwordRequired": "Потрібно вказати пароль.",
|
||||
"components.Login.host": "{mediaServerName} URL",
|
||||
"components.Login.initialsignin": "Підключитися",
|
||||
"components.Login.initialsigningin": "Підключення…",
|
||||
"components.Login.save": "Додати",
|
||||
"components.Login.signinwithjellyfin": "Використовуйте свій {mediaServerName} обліковий запис",
|
||||
"components.Login.title": "Додати email",
|
||||
"components.Login.username": "Ім'я користувача",
|
||||
"components.ManageSlideOver.removearr4k": "Видалити з 4K {arr}",
|
||||
"components.MovieDetails.downloadstatus": "Статус завантаження",
|
||||
"components.MovieDetails.imdbuserscore": "Оцінка користувачів IMDB",
|
||||
"components.MovieDetails.openradarr4k": "Відкрити фільм у 4К Radarr",
|
||||
"components.Selector.searchKeywords": "Ключові слова пошуку…",
|
||||
"components.Selector.searchStudios": "Пошук студій…",
|
||||
"components.Selector.starttyping": "Початок введення для пошуку.",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Звук сповіщення",
|
||||
"components.Settings.SettingsMain.general": "Загальні",
|
||||
"components.Settings.SettingsMain.generalsettings": "Загальні налаштування",
|
||||
"components.Settings.SettingsMain.applicationTitle": "Назва програми",
|
||||
"components.Settings.SettingsMain.applicationurl": "URL програми",
|
||||
"components.Settings.SettingsMain.cacheImages": "Увімкнути кешування зображень",
|
||||
"components.Settings.SettingsMain.csrfProtection": "Увімкнути CSRF захист",
|
||||
"components.Settings.SettingsMain.csrfProtectionHoverTip": "НЕ вмикайте цей параметр, якщо ви не розумієте, що робите!",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Приховати доступні медіа",
|
||||
"components.Settings.SettingsMain.locale": "Мова програми",
|
||||
"components.Settings.SettingsMain.originallanguage": "Мови для пошуку фільмів та серіалів",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Дозволити запитувати серіали частково",
|
||||
"components.Settings.SettingsMain.region": "Регіон для пошуку фільмів та серіалів",
|
||||
"components.Settings.SettingsMain.regionTip": "Фільтрувати вміст за регіональною доступністю",
|
||||
"components.Settings.SettingsMain.toastApiKeySuccess": "Новий ключ API успішно згенеровано!",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Під час збереження налаштувань сталася помилка.",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "Налаштування успішно збережено!",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Дозволити Jellyseerr правильно реєструвати IP-адреси клієнтів за проксі-сервером",
|
||||
"components.Settings.SettingsMain.validationApplicationUrl": "Ви повинні надати дійсну URL-адресу",
|
||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL-адреса не має закінчуватися косою рискою",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Тип аніме",
|
||||
"components.Settings.jellyfinSettings": "Налаштування {mediaServerName}",
|
||||
"components.Settings.jellyfinSettingsSuccess": "Налаштування {mediaServerName} успішно збережено!",
|
||||
"components.Settings.jellyfinlibraries": "Бібліотеки {mediaServerName}",
|
||||
"components.Settings.jellyfinsettings": "Налаштування {mediaServerName}",
|
||||
"components.Setup.signinWithPlex": "Використовуйте свій обліковий запис Plex",
|
||||
"components.TvDetails.play": "Відтворити в {mediaServerName}",
|
||||
"components.UserList.mediaServerUser": "Користувач {mediaServerName}",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "Електронна пошта",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "Збереження…",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Звук сповіщення",
|
||||
"i18n.collection": "Колекція",
|
||||
"components.Discover.CreateSlider.needresults": "Ви повинні мати принаймні 1 результат.",
|
||||
"components.Discover.CreateSlider.searchGenres": "Пошук жанрів…",
|
||||
"components.Discover.FilterSlideover.to": "До",
|
||||
"components.Discover.FilterSlideover.filters": "Фільтри",
|
||||
"components.Discover.CreateSlider.providetmdbsearch": "Введіть пошуковий запит",
|
||||
"components.Discover.DiscoverMovieKeyword.keywordMovies": "Фільми {keywordTitle}",
|
||||
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Активний фільтр} other {# Активні фільтри}}",
|
||||
"components.Discover.DiscoverTvKeyword.keywordSeries": "{keywordTitle} Серіали",
|
||||
"components.Discover.FilterSlideover.clearfilters": "Очистити всі активні фільтри",
|
||||
"components.Discover.PlexWatchlistSlider.plexwatchlist": "Ваш список перегляду Plex",
|
||||
"components.Discover.FilterSlideover.genres": "Жанри",
|
||||
"components.Discover.FilterSlideover.originalLanguage": "Мова оригіналу",
|
||||
"components.Discover.CreateSlider.nooptions": "Немає результатів.",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "Кількість голосів користувачів TMDB",
|
||||
"components.Discover.DiscoverMovies.discovermovies": "Фільми",
|
||||
"components.Discover.FilterSlideover.clearfilters": "Очистити активні фільтри",
|
||||
"components.Discover.CreateSlider.searchStudios": "Пошук студій…",
|
||||
"components.Discover.FilterSlideover.releaseDate": "Дата релізу",
|
||||
"components.Discover.CreateSlider.searchGenres": "Пошук жанрів…",
|
||||
"components.Discover.FilterSlideover.runtimeText": "тривалість {minValue}-{maxValue} хвилин",
|
||||
"components.Discover.FilterSlideover.voteCount": "Кількість голосів від {minValue} до {maxValue}",
|
||||
"components.Discover.networks": "Телеканали",
|
||||
"components.Discover.resettodefault": "Скинути за замовчуванням",
|
||||
"components.Discover.tmdbsearch": "Пошук TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Сервіси потокового передавання серіалів TMDB",
|
||||
"components.Discover.DiscoverSliderEdit.remove": "Видалити",
|
||||
"components.Layout.Sidebar.browsemovies": "Фільми",
|
||||
"components.MovieDetails.imdbuserscore": "Оцінка користувачів IMDB",
|
||||
"components.Layout.Sidebar.browsetv": "Серіали",
|
||||
"components.Layout.UserWarnings.emailInvalid": "Адреса електронної пошти недійсна.",
|
||||
"components.Login.saving": "Додавання…",
|
||||
"components.Login.validationhostformat": "Потрібна дійсна URL-адреса",
|
||||
"components.Login.validationusernamerequired": "Потрібно ім'я користувача",
|
||||
"components.ManageSlideOver.removearr": "Видалити з {arr}",
|
||||
"components.MovieDetails.openradarr": "Відкрити фільм у Radarr",
|
||||
"components.Selector.nooptions": "Немає результатів.",
|
||||
"components.Selector.searchGenres": "Виберіть жанри…",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Пристрій за замовчуванням",
|
||||
"components.Settings.Notifications.userEmailRequired": "Потрібен email користувача",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Автоматично додавати додатковий тег з ID та іменем користувача, який запитує",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "Підтримайте Jellyseerr",
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Встановіть доступ до зовнішнього API лише для читання (потрібний HTTPS)",
|
||||
"components.Settings.internalUrl": "Внутрішня URL-адреса",
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Синхронізація доступності медіа",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Кожну {jobScheduleSeconds, plural, one {секунду} other {{jobScheduleSeconds} секунд}}",
|
||||
"components.Settings.SettingsMain.apikey": "Ключ API",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Під час створення нового ключа API сталася помилка.",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Пристрій за замовчуванням",
|
||||
"components.UserList.noJellyfinuserstoimport": "Немає користувачів {mediaServerName} для імпорту.",
|
||||
"components.UserList.importfromJellyfinerror": "Під час імпорту користувачів з {mediaServerName} сталася помилка.",
|
||||
"components.Settings.SettingsMain.cacheImagesTip": "Кешувати зображення із зовнішніх джерел (потрібний значний об'єм дискового простору)",
|
||||
"components.Settings.SettingsMain.validationApplicationTitle": "Ви повинні вказати назву програми",
|
||||
"components.Settings.SonarrModal.seriesType": "Тип серіалу",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "Користувач {mediaServerName}",
|
||||
"components.TitleCard.watchlistError": "Щось пішло не так, повторіть спробу.",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Налаштуйте глобальні параметри та параметри за замовчуванням для Jellyseerr.",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Фільтрувати вміст за мовою оригіналу",
|
||||
"components.Settings.SettingsMain.trustProxy": "Увімкнути підтримку проксі",
|
||||
"components.Settings.jellyfinSettingsFailure": "Під час збереження налаштувань {mediaServerName} сталася помилка.",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "Зберегти зміни",
|
||||
"components.TvDetails.play4k": "Відтворити 4K в {mediaServerName}",
|
||||
"components.Discover.CreateSlider.providetmdbsearch": "Введіть пошуковий запит",
|
||||
"components.Discover.FilterSlideover.streamingservices": "Сервіси потокового передавання",
|
||||
"components.Discover.moviegenres": "Жанри фільмів",
|
||||
"components.MovieDetails.play": "Відтворити в {mediaServerName}",
|
||||
"components.MovieDetails.play4k": "Відтворити в {mediaServerName} у 4К",
|
||||
"components.Selector.showless": "Згорнути",
|
||||
"components.Discover.CreateSlider.addSlider": "Додати повзунок",
|
||||
"components.Discover.CreateSlider.addcustomslider": "Створити власний повзунок",
|
||||
"components.Discover.CreateSlider.addfail": "Не вдалося створити новий повзунок.",
|
||||
"components.Discover.DiscoverSliderEdit.deletefail": "Не вдалося видалити повзунок.",
|
||||
"components.Discover.DiscoverSliderEdit.deletesuccess": "Повзунок успішно видалено.",
|
||||
"components.Discover.CreateSlider.addsuccess": "Створено новий повзунок і збережено параметри налаштування Discover.",
|
||||
"components.Discover.CreateSlider.editSlider": "Редагувати повзунок",
|
||||
"components.Discover.CreateSlider.editfail": "Не вдалося відредагувати повзунок.",
|
||||
"components.Discover.CreateSlider.editsuccess": "Відредаговано повзунок і збережено параметри налаштування Discover.",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Введіть TMDB ID жанру",
|
||||
"components.Discover.CreateSlider.providetmdbkeywordid": "Введіть TMDB ID ключового слова",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Введіть TMDB ID мережі",
|
||||
"components.Discover.CreateSlider.providetmdbstudio": "Введіть TMDB ID студії",
|
||||
"components.Discover.CreateSlider.slidernameplaceholder": "Назва повзунка",
|
||||
"components.Discover.CreateSlider.validationDatarequired": "Ви повинні надати доступний для пошуку вміст.",
|
||||
"components.Discover.DiscoverMovies.sortPopularityAsc": "Популярність за зростанням",
|
||||
"components.Discover.DiscoverMovies.sortPopularityDesc": "Популярність за спаданням",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Дата випуску за зростанням",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Дата випуску за спаданням",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Назва (А-Я) за зростанням",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Назва (Я-А) за спаданням",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "Рейтинг TMDB за зростанням",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "Рейтинг TMDB за спаданням",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Дата виходу в ефір за зростанням",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Дата виходу в ефір за спаданням",
|
||||
"components.Discover.DiscoverTv.sortPopularityAsc": "Популярність за зростанням",
|
||||
"components.Discover.DiscoverTv.sortPopularityDesc": "Популярність за спаданням",
|
||||
"components.Discover.DiscoverTv.sortTitleAsc": "Назва (А-Я) за зростанням",
|
||||
"components.Discover.DiscoverTv.sortTitleDesc": "Назва (Я-А) за спаданням",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "Рейтинг TMDB за зростанням",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingDesc": "Рейтинг TMDB за спаданням",
|
||||
"components.Discover.FilterSlideover.firstAirDate": "Дата виходу в ефір",
|
||||
"components.Discover.createnewslider": "Створити новий повзунок",
|
||||
"components.Discover.customizediscover": "Налаштувати Discover",
|
||||
"components.Discover.resetfailed": "Щось пішло не так під час скидання налаштувань Discover.",
|
||||
"components.Discover.resetsuccess": "Успішно скинуто параметри налаштування.",
|
||||
"components.Discover.moviegenres": "Жанри фільмів",
|
||||
"components.Discover.resetwarning": "Скинути всі повзунки до стандартних. Це також видалить будь-які спеціальні повзунки!",
|
||||
"components.Discover.stopediting": "Зупинити редагування",
|
||||
"components.Discover.updatefailed": "Під час оновлення налаштувань Discover сталася помилка.",
|
||||
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# Активний фільтр} other {# Активні фільтри}}",
|
||||
"components.Discover.DiscoverTv.activefilters": "{count, plural, one {# Активний фільтр} other {# Активні фільтри}}",
|
||||
"components.Discover.FilterSlideover.streamingservices": "Сервіси потокового передавання",
|
||||
"components.Discover.FilterSlideover.activefilters": "{count, plural, one {# Активний фільтр} other {# Активні фільтри}}",
|
||||
"components.Discover.CreateSlider.addSlider": "Додати повзунок",
|
||||
"components.Discover.tvgenres": "Жанри серіалів",
|
||||
"components.Discover.tmdbmoviekeyword": "Ключове слово фільму TMDB",
|
||||
"components.Discover.tmdbtvkeyword": "Ключове слово серіала TMDB",
|
||||
"components.Discover.tmdbnetwork": "Телеканал TMDB",
|
||||
"components.Discover.networks": "Телеканали",
|
||||
"components.Discover.tmdbtvgenre": "Жанр серіала TMDB",
|
||||
"components.Discover.tmdbstudio": "Студія TMDB",
|
||||
"components.Discover.tmdbtvstreamingservices": "Сервіси потокового передавання серіалів TMDB",
|
||||
"components.Discover.tmdbmoviestreamingservices": "Сервіси потокової передачі фільмів TMDB",
|
||||
"components.Discover.resetfailed": "Щось пішло не так під час скидання налаштувань Discover.",
|
||||
"components.Discover.tmdbsearch": "Пошук TMDB",
|
||||
"components.Discover.CreateSlider.searchKeywords": "Ключові слова пошуку…",
|
||||
"components.Discover.tmdbmoviegenre": "Жанр фільму TMDB",
|
||||
"components.Discover.updatesuccess": "Оновлено параметри налаштування Discover.",
|
||||
"components.Login.credentialerror": "Ім'я користувача або пароль неправильні.",
|
||||
"components.Login.description": "Оскільки ви вперше входите в {applicationName}, вам потрібно додати дійсну адресу електронної пошти.",
|
||||
"components.Login.validationEmailFormat": "Невірний email",
|
||||
"components.Login.validationEmailRequired": "Ви повинні вказати адресу електронної пошти",
|
||||
"components.Login.validationemailformat": "Потрібен дійсний email",
|
||||
"components.Layout.UserWarnings.emailRequired": "Потрібно вказати адресу електронної пошти.",
|
||||
"components.Login.emailtooltip": "Адресу не потрібно пов’язувати з вашим {mediaServerName} сервером.",
|
||||
"components.Login.validationhostrequired": "Необхідна URL-адреса {mediaServerName}",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* Це безповоротно видалить цей {mediaType} з {arr}, включаючи всі файли.",
|
||||
"components.Discover.resetsuccess": "Успішно скинуто параметри налаштування.",
|
||||
"components.Discover.updatefailed": "Під час оновлення налаштувань Discover сталася помилка.",
|
||||
"components.Selector.showmore": "Показати більше",
|
||||
"components.Selector.searchGenres": "Виберіть жанри…",
|
||||
"components.Selector.searchStudios": "Пошук студій…",
|
||||
"components.Discover.CreateSlider.addcustomslider": "Створити власний повзунок",
|
||||
"components.Selector.showless": "Показати менше",
|
||||
"components.Selector.starttyping": "Початок введення для пошуку.",
|
||||
"components.Selector.searchKeywords": "Пошук за ключовими словами…",
|
||||
"components.Selector.nooptions": "Немає результатів.",
|
||||
"components.Discover.resettodefault": "Скинути за замовчуванням",
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "Синхронізація доступності медіа",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "Автоматично додавати додатковий тег з ID та іменем користувача, який запитує",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Кожну {jobScheduleSeconds, plural, one {секунду} other {{jobScheduleSeconds} секунд}}",
|
||||
"components.Settings.RadarrModal.tagRequests": "Теги запитів",
|
||||
"components.Settings.SettingsMain.hideAvailable": "Приховати доступні медіа",
|
||||
"components.Settings.SettingsMain.regionTip": "Фільтрувати вміст за регіональною доступністю",
|
||||
"components.Settings.SettingsMain.region": "Регіон для пошуку фільмів та серіалів",
|
||||
"components.Settings.SettingsMain.trustProxy": "Увімкнути підтримку проксі",
|
||||
"components.Settings.SettingsMain.toastSettingsSuccess": "Налаштування успішно збережено!",
|
||||
"components.Settings.SettingsMain.locale": "Мова інтерфейсу",
|
||||
"components.Settings.SettingsMain.applicationTitle": "Назва програми",
|
||||
"components.Settings.SettingsMain.originallanguage": "Мови для пошуку фільмів та серіалів",
|
||||
"components.Settings.SettingsMain.csrfProtection": "Увімкнути захист CSRF",
|
||||
"components.Settings.SettingsMain.toastApiKeyFailure": "Під час створення нового ключа API сталася помилка.",
|
||||
"components.Settings.SettingsMain.originallanguageTip": "Фільтрувати вміст за мовою оригіналу",
|
||||
"components.Settings.SettingsMain.cacheImagesTip": "Кешувати зображення із зовнішніх джерел (потрібний значний об'єм дискового простору)",
|
||||
"components.Settings.SettingsMain.trustProxyTip": "Дозволити Overseerr правильно реєструвати IP-адреси клієнтів за проксі-сервером",
|
||||
"components.Settings.SettingsMain.generalsettings": "Загальні налаштування",
|
||||
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL-адреса не має закінчуватися скісною рискою",
|
||||
"components.Settings.SettingsMain.apikey": "Ключ API",
|
||||
"components.Settings.SettingsMain.generalsettingsDescription": "Налаштуйте глобальні параметри і параметри за замовчуванням для Overseerr.",
|
||||
"components.Settings.SettingsMain.toastApiKeySuccess": "Новий ключ API успішно згенеровано!",
|
||||
"components.Settings.SettingsMain.cacheImages": "Увімкнути кешування зображень",
|
||||
"components.Settings.SettingsMain.applicationurl": "URL програми",
|
||||
"components.Settings.SettingsMain.general": "Загальні",
|
||||
"components.Settings.SettingsMain.csrfProtectionHoverTip": "НЕ вмикайте цей параметр, якщо ви не розумієте, що робите!",
|
||||
"components.Settings.SettingsMain.partialRequestsEnabled": "Дозволити запитувати серіали частково",
|
||||
"components.Settings.SettingsMain.toastSettingsFailure": "Під час збереження налаштувань сталася помилка.",
|
||||
"components.Settings.SettingsMain.validationApplicationUrl": "Ви повинні вказати дійсну URL-адресу",
|
||||
"components.Settings.SettingsMain.validationApplicationTitle": "Ви повинні вказати назву програми",
|
||||
"components.Settings.SettingsMain.csrfProtectionTip": "Встановіть доступ до зовнішнього API лише для читання (потрібний HTTPS)",
|
||||
"components.Discover.DiscoverTvKeyword.keywordSeries": "{keywordTitle} Серіали",
|
||||
"components.Discover.CreateSlider.editsuccess": "Відредаговано повзунок і збережено налаштування Discover.",
|
||||
"components.Discover.CreateSlider.slidernameplaceholder": "Назва повзунка",
|
||||
"components.Discover.CreateSlider.addfail": "Не вдалося створити новий повзунок.",
|
||||
"components.Discover.CreateSlider.needresults": "Ви повинні мати принаймні 1 результат.",
|
||||
"components.Discover.CreateSlider.editSlider": "Редагувати повзунок",
|
||||
"components.Discover.CreateSlider.editfail": "Не вдалося відредагувати повзунок.",
|
||||
"components.Discover.CreateSlider.addsuccess": "Створено новий повзунок і збережено параметри налаштування Discover.",
|
||||
"components.Discover.PlexWatchlistSlider.emptywatchlist": "Медіа додано до вашого <PlexWatchlistSupportLink>списку перегляду Plex</PlexWatchlistSupportLink>.",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "Звук сповіщення",
|
||||
"components.Discover.customizediscover": "Налаштувати Discover",
|
||||
"components.Discover.createnewslider": "Створити новий повзунок",
|
||||
"components.Discover.FilterSlideover.firstAirDate": "Дата виходу в ефір",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Пристрій за замовчуванням",
|
||||
"components.Settings.SonarrModal.tagRequests": "Теги запитів",
|
||||
"components.Setup.configuremediaserver": "Налаштуйте медіасервер",
|
||||
"components.Settings.jellyfinsettingsDescription": "Налаштуйте свій {mediaServerName} сервер. {mediaServerName} відсканує бібліотеки, щоб побачити, які бібліотеки доступні.",
|
||||
"components.Settings.manualscanJellyfin": "Сканувати бібліотеки вручну",
|
||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||
"components.Settings.jellyfinlibrariesDescription": "Бібліотеки {mediaServerName} перевіряються на наявність заголовків. Натисніть нижче, якщо в списку не вистачає бібліотек.",
|
||||
"components.Settings.saving": "Збереження…",
|
||||
"components.Settings.syncJellyfin": "Синхронізувати бібліотеки",
|
||||
"components.Settings.syncing": "Синхронізація",
|
||||
"components.Setup.signin": "Увійти",
|
||||
"components.Setup.signinWithJellyfin": "Використовуйте свій {mediaServerName} обліковий запис",
|
||||
"components.TitleCard.addToWatchList": "Додати в список перегляду",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> успішно додано до списку перегляду!",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} успішно імпортовано!",
|
||||
"components.UserList.importfromJellyfin": "Додати користувачів з {mediaServerName}",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "Зазвичай це запускається лише раз на 24 години. Jellyseerr перевірятиме нещодавно доданий сервер {mediaServerName} більш агресивно. Якщо ви вперше налаштовуєте Jellyseerr, рекомендується одноразове повне сканування бібліотеки вручну!",
|
||||
"components.Settings.save": "Зберегти зміни",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Успішно видалено зі списку перегляду!",
|
||||
"components.UserList.newJellyfinsigninenabled": "Параметр <strong>Увімкнути новий вхід на {mediaServerName}</strong> наразі ввімкнено. Користувачам {mediaServerName} із доступом до бібліотеки не потрібно імпортувати, щоб увійти.",
|
||||
"components.UserProfile.localWatchlist": "Список перегляду {username}",
|
||||
"components.Settings.jellyfinSettingsDescription": "Додатково налаштуйте внутрішні та зовнішні кінцеві точки для вашого сервера {mediaServerName}. У більшості випадків зовнішня URL-адреса відрізняється від внутрішньої URL-адреси. Для входу в систему {mediaServerName} також можна встановити спеціальну URL-адресу скидання пароля, якщо ви хочете переспрямувати на іншу сторінку скидання пароля.",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Автоматично додавати додатковий тег з ID та іменем користувача, який запитує"
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "Автоматично додавати додатковий тег з ID та іменем користувача, який запитує",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "Тип аніме-серіалу",
|
||||
"components.Settings.SonarrModal.seriesType": "Тип серіалу",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "Пристрій за замовчуванням",
|
||||
"i18n.collection": "Колекція",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "Звук сповіщення",
|
||||
"components.Discover.DiscoverMovies.sortPopularityDesc": "Популярність за спаданням",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingAsc": "Рейтинг TMDB за зростанням",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateDesc": "Дата випуску за спаданням",
|
||||
"components.Discover.CreateSlider.providetmdbnetwork": "Введіть TMDB ID мережі",
|
||||
"components.Discover.DiscoverMovies.sortPopularityAsc": "Популярність за зростанням",
|
||||
"components.Discover.CreateSlider.validationDatarequired": "Ви повинні надати доступний для пошуку вміст.",
|
||||
"components.Discover.CreateSlider.providetmdbstudio": "Введіть TMDB ID студії",
|
||||
"components.Discover.DiscoverMovies.sortTitleDesc": "Назва (Я-А) за спаданням",
|
||||
"components.Discover.CreateSlider.starttyping": "Початок введення для пошуку.",
|
||||
"components.Discover.CreateSlider.providetmdbkeywordid": "Введіть TMDB ID ключового слова",
|
||||
"components.Discover.CreateSlider.validationTitlerequired": "Ви повинні вказати назву.",
|
||||
"components.Discover.DiscoverMovies.sortReleaseDateAsc": "Дата випуску за зростанням",
|
||||
"components.Discover.DiscoverMovies.sortTmdbRatingDesc": "Рейтинг TMDB за спаданням",
|
||||
"components.Discover.DiscoverMovies.sortTitleAsc": "Назва (А-Я) за зростанням",
|
||||
"components.Discover.CreateSlider.providetmdbgenreid": "Введіть TMDB ID жанру",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingAsc": "Рейтинг TMDB за зростанням",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateAsc": "Дата виходу в ефір за зростанням",
|
||||
"components.Discover.DiscoverTv.sortTmdbRatingDesc": "Рейтинг TMDB за спаданням",
|
||||
"components.Discover.DiscoverTv.sortPopularityAsc": "Популярність за зростанням",
|
||||
"components.Discover.DiscoverTv.sortTitleAsc": "Назва (А-Я) за зростанням",
|
||||
"components.Discover.DiscoverTv.sortFirstAirDateDesc": "Дата виходу в ефір за спаданням",
|
||||
"components.Discover.DiscoverSliderEdit.deletefail": "Не вдалося видалити повзунок.",
|
||||
"components.Discover.DiscoverSliderEdit.enable": "Перемкнути видимість",
|
||||
"components.Discover.DiscoverSliderEdit.deletesuccess": "Повзунок успішно видалено.",
|
||||
"components.Discover.DiscoverTv.sortTitleDesc": "Назва (Я-А) за спаданням"
|
||||
}
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.password": "密码设置",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "你无权设置此用户的密码。",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "你的帐户目前没有设置密码。在下方配置密码,使你能够作为“本地用户”登录。",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此用户帐户目前没有设置密码。配置下面的密码以使此帐户能够作为“本地用户”登录。",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此用户帐户目前没有设置密码。在下方配置密码,使该帐户能够作为“本地用户”登录。",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.newpassword": "新密码",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "当前的密码",
|
||||
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "确认密码",
|
||||
@@ -766,14 +766,14 @@
|
||||
"components.RequestButton.viewrequest": "查看请求",
|
||||
"components.RequestButton.requestmore4k": "再提交 4K 请求",
|
||||
"components.RequestButton.requestmore": "提交更多季数的请求",
|
||||
"components.RequestButton.declinerequests": "拒绝{requestCount, plural, one {Request} other {{requestCount} Requests}}",
|
||||
"components.RequestButton.declinerequests": "拒绝{requestCount, plural, one {请求} other {{requestCount} 个请求}}",
|
||||
"components.RequestButton.declinerequest4k": "拒绝 4K 请求",
|
||||
"components.RequestButton.declinerequest": "拒绝请求",
|
||||
"components.RequestButton.decline4krequests": "拒绝 {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
|
||||
"components.RequestButton.approverequests": "批准 {requestCount, plural, one {Request} other {{requestCount} Requests}}",
|
||||
"components.RequestButton.decline4krequests": "拒绝{requestCount, plural, one { 4K 请求} other { {requestCount} 个 4K 请求}}",
|
||||
"components.RequestButton.approverequests": "批准{requestCount, plural, one {请求} other {{requestCount} 个请求}}",
|
||||
"components.RequestButton.approverequest4k": "批准 4K 请求",
|
||||
"components.RequestButton.approverequest": "批准请求",
|
||||
"components.RequestButton.approve4krequests": "批准 {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
|
||||
"components.RequestButton.approve4krequests": "批准{requestCount, plural, one { 4K 请求} other { {requestCount} 个 4K 请求}}",
|
||||
"components.RequestBlock.server": "目標服务器",
|
||||
"components.RequestBlock.seasons": "季数",
|
||||
"components.RequestBlock.rootfolder": "根目录",
|
||||
@@ -782,10 +782,10 @@
|
||||
"components.RegionSelector.regionServerDefault": "默认设置({region})",
|
||||
"components.RegionSelector.regionDefault": "所有地区",
|
||||
"components.QuotaSelector.unlimited": "无限",
|
||||
"components.QuotaSelector.tvRequests": "{quotaLimit} <quotaUnits>{seasons} 每 {quotaDays} {days}</quotaUnits>",
|
||||
"components.QuotaSelector.tvRequests": "<quotaUnits>每 {quotaDays} {days} </quotaUnits>{quotaLimit}<quotaUnits> {seasons}</quotaUnits>",
|
||||
"components.QuotaSelector.seasons": "季",
|
||||
"components.QuotaSelector.movies": "部电影",
|
||||
"components.QuotaSelector.movieRequests": "{quotaLimit} <quotaUnits>{movies} 每 {quotaDays} {days}</quotaUnits>",
|
||||
"components.QuotaSelector.movieRequests": "<quotaUnits>每 {quotaDays} {days} </quotaUnits>{quotaLimit}<quotaUnits> {movies}</quotaUnits>",
|
||||
"components.QuotaSelector.days": "天",
|
||||
"components.PlexLoginButton.signinwithplex": "登入",
|
||||
"components.PlexLoginButton.signingin": "登入中…",
|
||||
@@ -900,7 +900,7 @@
|
||||
"components.Settings.Notifications.NotificationsGotify.toastGotifyTestFailed": "Gotify 测试通知发送失败。",
|
||||
"components.Settings.Notifications.NotificationsGotify.toastGotifyTestSending": "Gotify测试通知发送中…",
|
||||
"components.Settings.Notifications.enableMentions": "允许提及",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "每 {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "每 {jobScheduleMinutes} 分钟",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "应用 API 令牌",
|
||||
"components.UserList.newplexsigninenabled": "<strong>允许新的 Plex 用户登录</strong> 设置目前已启用。还没有导入的Plex用户也能登录。",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover 通知设置保存失败。",
|
||||
@@ -909,7 +909,7 @@
|
||||
"components.Settings.RadarrModal.inCinemas": "已上映",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "从您的<PushbulletSettingsLink>账号设置</PushbulletSettingsLink>获取API令牌",
|
||||
"components.Settings.SettingsAbout.appDataPath": "数据目录",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "每 {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "每 {jobScheduleHours} 小时",
|
||||
"components.Settings.tautulliSettings": "Tautulli 设置",
|
||||
"components.Settings.tautulliSettingsDescription": "关于 Tautulli 服务器的设置。Jellyseerr 会从 Tautulli 获取 Plex 媒体的观看历史记录。",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.discordId": "Discord 用户ID",
|
||||
@@ -1063,8 +1063,8 @@
|
||||
"components.Settings.restartrequiredTooltip": "必须重新启动 Jellyseerr 才能使更改的设置生效",
|
||||
"components.TvDetails.manageseries": "管理电视节目",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "自动请求您的 <PlexWatchlistSupportLink>Plex 关注列表</PlexWatchlistSupportLink>的媒体",
|
||||
"components.AirDateBadge.airedrelative": "{relativeTime}播出",
|
||||
"components.AirDateBadge.airsrelative": "{relativeTime}播出",
|
||||
"components.AirDateBadge.airedrelative": "播出{relativeTime}",
|
||||
"components.AirDateBadge.airsrelative": "播出{relativeTime}",
|
||||
"components.Layout.UserDropdown.MiniQuotaDisplay.movierequests": "电影请求",
|
||||
"components.Layout.UserDropdown.MiniQuotaDisplay.seriesrequests": "电视节目请求",
|
||||
"components.NotificationTypeSelector.mediaautorequestedDescription": "当 Plex 关注列表中的项目自动提交新媒体请求时,会收到通知。",
|
||||
@@ -1203,7 +1203,7 @@
|
||||
"components.Discover.PlexWatchlistSlider.emptywatchlist": "您的 <PlexWatchlistSupportLink>Plex 关注列表</PlexWatchlistSupportLink>中的媒体会显示在这里。",
|
||||
"components.Selector.starttyping": "开始打字以进行搜索。",
|
||||
"components.Discover.CreateSlider.starttyping": "开始打字以进行搜索。",
|
||||
"components.Discover.CreateSlider.needresults": "你需要至少有 1 个结果。",
|
||||
"components.Discover.CreateSlider.needresults": "需要至少有 1 个结果。",
|
||||
"components.Selector.showless": "显示更少",
|
||||
"components.Discover.resetfailed": "重置探索媒体设置时出了点问题。",
|
||||
"components.Settings.SettingsMain.validationApplicationTitle": "你必须提供一个应用程序标题",
|
||||
@@ -1253,7 +1253,7 @@
|
||||
"components.Settings.SettingsJobsCache.availability-sync": "同步媒体可用性",
|
||||
"components.Discover.tmdbmoviestreamingservices": "TMDB 电影流媒体服务",
|
||||
"components.Discover.tmdbtvstreamingservices": "TMDB 电视流媒体服务",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "每 {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}",
|
||||
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "每 {jobScheduleSeconds} 秒",
|
||||
"components.Discover.FilterSlideover.voteCount": "在 {minValue} 和 {maxValue} 之间的评分数",
|
||||
"components.Settings.RadarrModal.tagRequests": "标签请求",
|
||||
"components.Settings.RadarrModal.tagRequestsInfo": "自动添加带有请求者的用户 ID 和显示名称的附加标签",
|
||||
@@ -1261,83 +1261,11 @@
|
||||
"i18n.collection": "合集",
|
||||
"components.Discover.FilterSlideover.tmdbuservotecount": "TMDB 用户评分数",
|
||||
"components.Settings.SonarrModal.tagRequestsInfo": "自动添加带有请求者的用户 ID 和显示名称的附加标签",
|
||||
"components.Layout.UserWarnings.passwordRequired": "需要输入密码。",
|
||||
"components.Login.emailtooltip": "地址不需要与{mediaServerName}实例相关联。",
|
||||
"components.Login.host": "{mediaServerName} 的 URL",
|
||||
"components.Login.initialsignin": "连接",
|
||||
"components.Login.initialsigningin": "连接中……",
|
||||
"components.Login.save": "添加",
|
||||
"components.Login.saving": "添加中……",
|
||||
"components.Login.signinwithjellyfin": "使用您的{mediaServerName}帐户",
|
||||
"components.Login.title": "添加邮件",
|
||||
"components.Login.username": "用户名",
|
||||
"components.Login.validationEmailFormat": "无效的邮件地址",
|
||||
"components.Login.validationEmailRequired": "你必须提供一个电子邮件",
|
||||
"components.Login.validationemailformat": "需要有效的电子邮件",
|
||||
"components.Login.validationhostformat": "需要有效的URL",
|
||||
"components.Login.validationusernamerequired": "需要用户名",
|
||||
"components.ManageSlideOver.manageModalRemoveMediaWarning": "* 这将不可逆地从{arr}中删除{mediaType},包括所有文件。",
|
||||
"components.ManageSlideOver.removearr4k": "移除4K {arr}",
|
||||
"components.MovieDetails.downloadstatus": "下载状态",
|
||||
"components.MovieDetails.imdbuserscore": "IMDB用户评分",
|
||||
"components.MovieDetails.openradarr": "在Radarr中打开电影",
|
||||
"components.MovieDetails.play": "播放{mediaServerName}",
|
||||
"components.MovieDetails.play4k": "播放 4K {mediaServerName}",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "通知声音",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Jellyfin全库扫描",
|
||||
"components.Settings.SonarrModal.seriesType": "系列类型",
|
||||
"components.Settings.jellyfinSettingsFailure": "保存{mediaServerName}设置时出错。",
|
||||
"components.Settings.jellyfinSettingsSuccess": "{mediaServerName}设置保存成功!",
|
||||
"components.Settings.jellyfinlibraries": "{mediaServerName}库",
|
||||
"components.Settings.jellyfinlibrariesDescription": "库{mediaServerName}用于扫描标题。如果没有列出库,请单击下面的按钮。",
|
||||
"components.Settings.jellyfinsettings": "{mediaServerName}设置",
|
||||
"components.Settings.manualscanJellyfin": "手动扫描库",
|
||||
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
|
||||
"components.Settings.saving": "保存中……",
|
||||
"components.Settings.syncJellyfin": "同步库",
|
||||
"components.Settings.syncing": "同步中",
|
||||
"components.Settings.timeout": "超时",
|
||||
"components.Setup.signin": "登录",
|
||||
"components.Setup.signinWithJellyfin": "使用您的{mediaServerName}帐户",
|
||||
"components.TitleCard.addToWatchList": "添加到监视列表",
|
||||
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong>从监视列表中删除成功!",
|
||||
"components.TitleCard.watchlistError": "出了问题,再试一次。",
|
||||
"components.TvDetails.play": "在 {mediaServerName} 播放",
|
||||
"components.TvDetails.play4k": "mediaServerName} 播放 4K",
|
||||
"components.UserList.importfrommediaserver": "导入{mediaServerName}用户",
|
||||
"components.UserList.mediaServerUser": "{mediaServerName} 用户",
|
||||
"components.UserList.noJellyfinuserstoimport": "在{mediaServerName}中没有用户要导入。",
|
||||
"components.UserList.newJellyfinsigninenabled": "<strong>启用 {mediaServerName} 登录</strong> 设置当前已启用. {mediaServerName} 具有库访问权限的用户不需要导入即可登录。",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.mediaServerUser": "{mediaServerName} 用户",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "设备默认",
|
||||
"components.Layout.UserWarnings.emailInvalid": "邮件地址无效。",
|
||||
"components.Layout.UserWarnings.emailRequired": "需要填写电子邮件地址。",
|
||||
"components.Login.credentialerror": "用户名或密码错误。",
|
||||
"components.Login.description": "由于这是您第一次登录{applicationName},您需要添加一个有效的电子邮件地址。",
|
||||
"components.Login.validationhostrequired": "{mediaServerName} URL是必需的",
|
||||
"components.ManageSlideOver.removearr": "从{arr}中删除",
|
||||
"components.MovieDetails.openradarr4k": "在 4K Radarr 中打开电影",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "设备默认",
|
||||
"components.Settings.Notifications.userEmailRequired": "获取用户邮箱",
|
||||
"components.Settings.SettingsAbout.supportjellyseerr": "支持Jellyseerr",
|
||||
"components.Settings.SettingsJobsCache.jellyfin-recently-added-scan": "Jellyfin最近新增扫描",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "动漫系列",
|
||||
"components.Settings.internalUrl": "内部URL",
|
||||
"components.Settings.jellyfinSettings": "{mediaServerName}设置",
|
||||
"components.Settings.jellyfinSettingsDescription": "可以为您的{mediaServerName}服务器配置内部和外部端点。在大多数情况下,外部URL与内部URL不同。如果你想重定向到不同的密码重置页面,也可以为{mediaServerName}登录设置自定义密码重置URL。",
|
||||
"components.Settings.jellyfinsettingsDescription": "配置{mediaServerName}服务器的设置。{mediaServerName}扫描{mediaServerName}库以查看可用的内容。",
|
||||
"components.Settings.manualscanDescriptionJellyfin": "正常情况下,每24小时只会运行一次。Jellyseerr将更积极地检查您的{mediaServerName}服务器最近添加的内容。如果这是您第一次配置Jellyseerr,建议您手动进行一次完整的库扫描!",
|
||||
"components.Settings.save": "保存更改",
|
||||
"components.Setup.configuremediaserver": "配置媒体服务器",
|
||||
"components.Setup.signinWithPlex": "使用您的 Plex 帐户",
|
||||
"components.TitleCard.watchlistCancel": "<strong>{title}</strong>的监视列表已取消。",
|
||||
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong>添加到监视列表成功!",
|
||||
"components.UserList.importfromJellyfin": "导入{mediaServerName}用户",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.email": "电子邮件",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.save": "保存更改",
|
||||
"components.UserList.importfromJellyfinerror": "导入{mediaServerName}用户时出错。",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "通知声音",
|
||||
"components.UserProfile.UserSettings.UserGeneralSettings.saving": "保存中……",
|
||||
"components.UserProfile.localWatchlist": "{username}的监视列表",
|
||||
"components.UserList.importedfromJellyfin": "<strong>{userCount}</strong> {mediaServerName} {userCount, plural, one {user} other {users}} 导入成功!"
|
||||
"components.MovieDetails.imdbuserscore": "IMDB 用户评分",
|
||||
"components.Settings.Notifications.NotificationsPushover.sound": "通知提示音",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.deviceDefault": "默认设备",
|
||||
"components.Settings.SonarrModal.animeSeriesType": "动漫剧集类型",
|
||||
"components.Settings.SonarrModal.seriesType": "剧集类型",
|
||||
"components.UserProfile.UserSettings.UserNotificationSettings.sound": "通知提示音",
|
||||
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "默认设备"
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import Blacklist from '@app/components/Blacklist';
|
||||
import useRouteGuard from '@app/hooks/useRouteGuard';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import type { NextPage } from 'next';
|
||||
|
||||
const BlacklistPage: NextPage = () => {
|
||||
useRouteGuard([Permission.MANAGE_BLACKLIST, Permission.VIEW_BLACKLIST], {
|
||||
type: 'or',
|
||||
});
|
||||
return <Blacklist />;
|
||||
};
|
||||
|
||||
export default BlacklistPage;
|
||||
@@ -8,7 +8,7 @@ const JellyfinSettingsPage: NextPage = () => {
|
||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
||||
return (
|
||||
<SettingsLayout>
|
||||
<SettingsJellyfin />
|
||||
<SettingsJellyfin showAdvancedSettings={true} />
|
||||
</SettingsLayout>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user