Compare commits

..

57 Commits

Author SHA1 Message Date
semantic-release-bot
96328ab5c6 chore(release): 1.12.0 2020-12-22 02:26:31 +00:00
sct
c31709681a Merge branch 'develop' 2020-12-22 11:21:19 +09:00
sct
4b505223b8 fix(logs): improve logging when adding to sonarr/radarr 2020-12-22 11:12:41 +09:00
sct
16e48c4919 build: remove yml from dockerignore [skip ci] 2020-12-22 09:46:22 +09:00
sct
604ba2a92f feat(frontend): add full crew page for movies/shows 2020-12-22 00:37:26 +09:00
sct
12127a7763 feat(frontend): add crew related movies/shows to person details page 2020-12-22 00:12:50 +09:00
allcontributors[bot]
9a4da25faa docs: add johnpyp as a contributor (#435) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-21 23:42:25 +09:00
johnpyp
7e8f361af7 feat: import users from plex (#428)
* feat: import users from plex

fix #281

* fix(frontend): re-enable delete user confirmation button after finished
2020-12-21 23:41:19 +09:00
allcontributors[bot]
08c22b03e0 docs: add doob187 as a contributor (#434) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-21 23:12:39 +09:00
doob187
3cee6b78a4 build: Update Docker ignore (#425)
Add some files to ignore.
Inside the Docker  ( latest dev Build )
```
CODE_OF_CONDUCT.md
CONTRIBUTING.md
Dockerfile
Dockerfile.local
LICENSE
babel.config.js
config
dist
docker-compose.yml
next-env.d.ts
next.config.js
node_modules
ormconfig.js
overseerr-api.yml
package.json
postcss.config.js
public
server
src
stylelint.config.js
tailwind.config.js
tsconfig.json
yarn.lock
```
2020-12-21 23:07:54 +09:00
sct
7083ddf181 revert(deps): revert react-use-clipboard to 1.0.2 2020-12-21 21:25:14 +09:00
sct
b092361c73 build(deps): more bundled dependency updates 2020-12-21 21:15:15 +09:00
sct
6f2a71c788 build(deps-dev): bundled dependency update 2020-12-21 21:08:46 +09:00
sct
22006e9dbd fix(api): prevent checking first admin account for plex server access 2020-12-21 20:51:54 +09:00
sct
e7ee85c29b feat: default user permissions added to settings
closes #388
2020-12-21 18:47:55 +09:00
sct
f35dae56a5 fix(api): fix cross-imported type crashing build 2020-12-21 18:14:49 +09:00
sct
5deb64a87f fix(frontend): allow more special characters in search input
fixes #430
2020-12-21 18:02:23 +09:00
sct
6753d9daaa feat(frontend): add prioritized crew under overview
closes #406
2020-12-21 17:37:38 +09:00
sct
4891298891 fix(frontend): adjust person card layout to deal with overflowing content
fixes #416
2020-12-21 15:45:55 +09:00
sct
ab9cef3624 fix: only run migrations in production 2020-12-21 14:38:32 +09:00
allcontributors[bot]
d4d79e438c docs: add danshilm as a contributor (#429) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-21 14:37:26 +09:00
Danshil Mungur
c6836e02c8 fix(frontend): add name, short_name and start_url to manifest (#424) 2020-12-21 14:36:11 +09:00
sct
0916b58594 fix(notifications): always update the media table when seasons become available 2020-12-21 01:40:23 +09:00
sct
44a305426f feat(notifications): added ability to send test notifications
closes #309
2020-12-20 21:41:25 +09:00
sct
764db94f1b fix(plex-sync): fixes processing movies using TMDB agent
fix #363
2020-12-20 11:00:48 +09:00
semantic-release-bot
0a3d7c5915 chore(release): 1.11.0 2020-12-20 01:18:39 +00:00
sct
912c63e562 Merge branch 'develop' 2020-12-20 10:12:59 +09:00
allcontributors[bot]
f6865ca0c1 docs: add sebstrgg as a contributor (#407) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-20 01:45:47 +09:00
sct
c9fe6cb0b7 feat(lang): add support for swedish language 2020-12-20 01:43:54 +09:00
Weblate (bot)
3778ad829c feat(lang): translations update from Weblate (#403)
* feat(lang): translated using Weblate (Swedish)

Currently translated at 100.0% (350 of 350 strings)

feat(lang): translated using Weblate (Swedish)

Currently translated at 75.7% (265 of 350 strings)

feat(lang): translated using Weblate (Swedish)

Currently translated at 75.7% (265 of 350 strings)

feat(lang): translated using Weblate (Swedish)

Currently translated at 72.5% (254 of 350 strings)

feat(lang): added translation using Weblate (Swedish)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: K. Herbert <herbert.ka@mailo.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Co-authored-by: sebstrgg <sebastian@wollter.nu>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Italian)

Currently translated at 100.0% (350 of 350 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (German)

Currently translated at 100.0% (350 of 350 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/
Translation: Overseerr/Overseerr Frontend

Co-authored-by: K. Herbert <herbert.ka@mailo.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Co-authored-by: sebstrgg <sebastian@wollter.nu>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
2020-12-20 01:40:56 +09:00
sct
b1786245c7 ci: change semantic-release/github to add releases to bottom [skip ci] 2020-12-20 01:34:50 +09:00
sct
b7f5739035 feat(frontend): releases added to about page
closes #303
2020-12-20 01:22:58 +09:00
sct
c128898206 feat(frontend): add support overseerr block to about page 2020-12-19 23:30:27 +09:00
sct
ff2ab29491 feat(frontend): add language picker to setup/login 2020-12-19 23:13:46 +09:00
sct
108dfc4afd feat(lang): add support for Italian, Portuguese (Brazil) and Serbian 2020-12-19 21:51:05 +09:00
allcontributors[bot]
6f2de8577e docs: add Shagon94 as a contributor (#401) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-19 21:31:51 +09:00
Weblate (bot)
1bd0e646e3 feat(lang): translations update from Weblate (#400)
* feat(lang): translated using Weblate (French)

Currently translated at 96.5% (338 of 350 strings)

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Serbian)

Currently translated at 100.0% (350 of 350 strings)

feat(lang): added translation using Weblate (Serbian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Shagon94 <Shagon94@gmail.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sr/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Portuguese (Brazil))

Currently translated at 62.8% (220 of 350 strings)

Co-authored-by: Tijuco <linuxinmotion@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Italian)

Currently translated at 100.0% (350 of 350 strings)

feat(lang): translated using Weblate (Italian)

Currently translated at 100.0% (350 of 350 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Spanish)

Currently translated at 93.7% (328 of 350 strings)

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (German)

Currently translated at 96.2% (337 of 350 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Dutch)

Currently translated at 95.7% (335 of 350 strings)

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Russian)

Currently translated at 23.4% (82 of 350 strings)

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Japanese)

Currently translated at 99.4% (348 of 350 strings)

feat(lang): translated using Weblate (Japanese)

Currently translated at 99.4% (348 of 350 strings)

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Co-authored-by: T'ai <chivalrousjosh@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ja/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (English)

Currently translated at 100.0% (350 of 350 strings)

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/en/
Translation: Overseerr/Overseerr Frontend

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Co-authored-by: Shagon94 <Shagon94@gmail.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Co-authored-by: Tijuco <linuxinmotion@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: T'ai <chivalrousjosh@gmail.com>
2020-12-19 21:31:07 +09:00
semantic-release-bot
077513151e chore(release): 1.10.0 2020-12-19 04:54:22 +00:00
sct
191f39e8bd Merge branch 'develop' 2020-12-19 13:49:05 +09:00
sct
fb883e8043 Merge branch 'develop' of github.com:sct/overseerr into develop 2020-12-19 13:44:57 +09:00
allcontributors[bot]
4435468561 docs: add Panzer1119 as a contributor (#396) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-19 13:44:48 +09:00
allcontributors[bot]
e821d0a02b docs: add mazzetta86 as a contributor (#395) [skip ci]
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-12-19 13:43:53 +09:00
Weblate (bot)
5f71fb7ee2 feat(lang): translations update from Weblate (#391)
* feat(lang): translated using Weblate (Portuguese (Brazil))

Currently translated at 17.4% (61 of 350 strings)

feat(lang): added translation using Weblate (Portuguese (Brazil))

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Tijuco <linuxinmotion@gmail.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (French)

Currently translated at 100.0% (350 of 350 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Italian)

Currently translated at 90.8% (318 of 350 strings)

feat(lang): translated using Weblate (Italian)

Currently translated at 90.8% (318 of 350 strings)

feat(lang): translated using Weblate (Italian)

Currently translated at 46.2% (162 of 350 strings)

feat(lang): translated using Weblate (Italian)

Currently translated at 27.4% (96 of 350 strings)

feat(lang): translated using Weblate (Italian)

Currently translated at 27.4% (96 of 350 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (German)

Currently translated at 100.0% (350 of 350 strings)

feat(lang): translated using Weblate (German)

Currently translated at 100.0% (350 of 350 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Paul <panzer1119@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/
Translation: Overseerr/Overseerr Frontend

Co-authored-by: Tijuco <linuxinmotion@gmail.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Co-authored-by: Paul <panzer1119@gmail.com>
2020-12-19 13:42:19 +09:00
sct
a0c5608aa0 fix(frontend): try not to render broken rottentomatoes data 2020-12-19 13:39:48 +09:00
sct
816fec1a83 fix(email): fix link to Overseerr in email templates
fixes #392
2020-12-19 13:23:15 +09:00
sct
2a4dd52275 fix(frontend): hide Request More button if all current seasons are available
fixes #343
2020-12-19 13:16:58 +09:00
sct
3601d442db fix(frontend): adjust padding of search box so placeholder text fits on mobile
this only fixes English. Other languages placeholders may or may not fit into the space

fixes #393
2020-12-19 12:57:17 +09:00
sct
53bede692d feat: add missing tzdata package to image
re #394
2020-12-19 11:32:17 +09:00
sct
fe46895289 ci: add @semantic-release/github step to publish array 2020-12-19 03:09:42 +09:00
sct
a0024a0cbe feat(frontend): add external links to movie and tv detail pages 2020-12-19 02:46:32 +09:00
Weblate (bot)
8408e19568 feat(lang): translations update from Weblate (#380)
* feat(lang): translated using Weblate (Italian)

Currently translated at 2.0% (7 of 350 strings)

feat(lang): added translation using Weblate (Italian)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (Spanish)

Currently translated at 100.0% (340 of 340 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Shutruk <cokimaya007@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (German)

Currently translated at 100.0% (340 of 340 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/
Translation: Overseerr/Overseerr Frontend

* feat(lang): translated using Weblate (French)

Currently translated at 100.0% (340 of 340 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/
Translation: Overseerr/Overseerr Frontend

Co-authored-by: Simone Chiavaccini <mazzetta86@gmail.com>
Co-authored-by: sct <sctsnipe@gmail.com>
Co-authored-by: Shutruk <cokimaya007@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
2020-12-19 02:01:47 +09:00
sct
ed49e7c28e docs: add FUNDING.yml 2020-12-19 01:55:23 +09:00
Brandon Cohen
549567a7e9 fix(frontend): changed request block for slideover on mobile UI (#387) 2020-12-19 01:55:11 +09:00
sct
51f99a2e7b ci: add missing build-args back into ci workflow 2020-12-19 01:24:50 +09:00
semantic-release-bot
048b6c1d90 chore(release): 1.9.1 2020-12-18 16:08:58 +00:00
sct
a6a065ac0b Merge branch 'develop' 2020-12-19 01:03:48 +09:00
sct
5e5ba40505 fix: change default internal port to 5055 (#389) 2020-12-19 00:12:41 +09:00
84 changed files with 4207 additions and 784 deletions

View File

@@ -153,6 +153,69 @@
"contributions": [
"code"
]
},
{
"login": "mazzetta86",
"name": "mazzetta86",
"avatar_url": "https://avatars2.githubusercontent.com/u/45591560?v=4",
"profile": "https://github.com/mazzetta86",
"contributions": [
"translation"
]
},
{
"login": "Panzer1119",
"name": "Paul Hagedorn",
"avatar_url": "https://avatars1.githubusercontent.com/u/23016343?v=4",
"profile": "https://github.com/Panzer1119",
"contributions": [
"translation"
]
},
{
"login": "Shagon94",
"name": "Shagon94",
"avatar_url": "https://avatars3.githubusercontent.com/u/9140783?v=4",
"profile": "https://github.com/Shagon94",
"contributions": [
"translation"
]
},
{
"login": "sebstrgg",
"name": "sebstrgg",
"avatar_url": "https://avatars3.githubusercontent.com/u/27026694?v=4",
"profile": "https://github.com/sebstrgg",
"contributions": [
"translation"
]
},
{
"login": "danshilm",
"name": "Danshil Mungur",
"avatar_url": "https://avatars2.githubusercontent.com/u/20923978?v=4",
"profile": "https://github.com/danshilm",
"contributions": [
"code"
]
},
{
"login": "doob187",
"name": "doob187",
"avatar_url": "https://avatars1.githubusercontent.com/u/60312740?v=4",
"profile": "https://github.com/doob187",
"contributions": [
"infra"
]
},
{
"login": "johnpyp",
"name": "johnpyp",
"avatar_url": "https://avatars2.githubusercontent.com/u/20625636?v=4",
"profile": "https://github.com/johnpyp",
"contributions": [
"code"
]
}
],
"badgeTemplate": "<a href=\"#contributors-\"><img alt=\"All Contributors\" src=\"https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg\"/></a>",

View File

@@ -4,7 +4,12 @@ node_modules
.gitconfig
.gitignore
.github
.all-contributorsrc
.editorconfig
.prettierignore
**/README.md
**/.vscode
config/db/db.sqlite3
config/db/logs/overseerr.log
Dockerfil**
**.md

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: [sct]
patreon: overseerr

View File

@@ -52,6 +52,8 @@ jobs:
context: .
file: ./Dockerfile
push: true
build-args: |
COMMIT_TAG=${{ github.sha }}
tags: |
sctx/overseerr:develop
sctx/overseerr:${{ github.sha }}

View File

@@ -1,3 +1,72 @@
# [1.12.0](https://github.com/sct/overseerr/compare/v1.11.0...v1.12.0) (2020-12-22)
### Bug Fixes
* **api:** fix cross-imported type crashing build ([f35dae5](https://github.com/sct/overseerr/commit/f35dae56a583a5545375318fa5be994ae1f2557f))
* **api:** prevent checking first admin account for plex server access ([22006e9](https://github.com/sct/overseerr/commit/22006e9dbde82609440f89bde9a40887b4742682))
* **frontend:** add name, short_name and start_url to manifest ([#424](https://github.com/sct/overseerr/issues/424)) ([c6836e0](https://github.com/sct/overseerr/commit/c6836e02c810e8adb12c3a4b110f9604cf5b7b81))
* **frontend:** adjust person card layout to deal with overflowing content ([4891298](https://github.com/sct/overseerr/commit/48912988915ae40606a900a6f1dd23fc25ed567f)), closes [#416](https://github.com/sct/overseerr/issues/416)
* **frontend:** allow more special characters in search input ([5deb64a](https://github.com/sct/overseerr/commit/5deb64a87fd70e97da27a025ad11fb8ace0e0b57)), closes [#430](https://github.com/sct/overseerr/issues/430)
* **logs:** improve logging when adding to sonarr/radarr ([4b50522](https://github.com/sct/overseerr/commit/4b505223b881a750007e3fbc7d4bcb9677d4d412))
* only run migrations in production ([ab9cef3](https://github.com/sct/overseerr/commit/ab9cef3624b5db1ec03507553a69d33b87857e29))
* **notifications:** always update the media table when seasons become available ([0916b58](https://github.com/sct/overseerr/commit/0916b58594a00db98c6701fdcaee4f3c3e08904e))
* **plex-sync:** fixes processing movies using TMDB agent ([764db94](https://github.com/sct/overseerr/commit/764db94f1bd7866309684d5bd56033b21cbc2e0c)), closes [#363](https://github.com/sct/overseerr/issues/363)
### Features
* **frontend:** add crew related movies/shows to person details page ([12127a7](https://github.com/sct/overseerr/commit/12127a77633f0e92ae88cbafd49581296f559c33))
* **frontend:** add full crew page for movies/shows ([604ba2a](https://github.com/sct/overseerr/commit/604ba2a92f1d59489e7fc6dfc011347f8595c123))
* default user permissions added to settings ([e7ee85c](https://github.com/sct/overseerr/commit/e7ee85c29b5d25c6bff58717eae5e62de4dcef0c)), closes [#388](https://github.com/sct/overseerr/issues/388)
* import users from plex ([#428](https://github.com/sct/overseerr/issues/428)) ([7e8f361](https://github.com/sct/overseerr/commit/7e8f361af711001cfc4dcc06a384b76f9846f90f)), closes [#281](https://github.com/sct/overseerr/issues/281)
* **frontend:** add prioritized crew under overview ([6753d9d](https://github.com/sct/overseerr/commit/6753d9daaafb18672f14fd86f2c1675dcec39b13)), closes [#406](https://github.com/sct/overseerr/issues/406)
* **notifications:** added ability to send test notifications ([44a3054](https://github.com/sct/overseerr/commit/44a305426f3e9829c167a4a73095d0d248641f47)), closes [#309](https://github.com/sct/overseerr/issues/309)
### Reverts
* **deps:** revert react-use-clipboard to 1.0.2 ([7083ddf](https://github.com/sct/overseerr/commit/7083ddf18121716e3442acab3506c395fdc351ac))
# [1.11.0](https://github.com/sct/overseerr/compare/v1.10.0...v1.11.0) (2020-12-20)
### Features
* **frontend:** add language picker to setup/login ([ff2ab29](https://github.com/sct/overseerr/commit/ff2ab29491a80c421525b9a394d6fbbf54914dc2))
* **frontend:** add support overseerr block to about page ([c128898](https://github.com/sct/overseerr/commit/c128898206d6cbb482de4d8dca53f70b87e4911a))
* **frontend:** releases added to about page ([b7f5739](https://github.com/sct/overseerr/commit/b7f573903500cc8a62e39afd787bc1da8c09d88b)), closes [#303](https://github.com/sct/overseerr/issues/303)
* **lang:** add support for Italian, Portuguese (Brazil) and Serbian ([108dfc4](https://github.com/sct/overseerr/commit/108dfc4afd31388cb6c9e07deccd168ade8b1574))
* **lang:** add support for swedish language ([c9fe6cb](https://github.com/sct/overseerr/commit/c9fe6cb0b7ea984d8e4e1cb3f284935c9da7cc2b))
* **lang:** translations update from Weblate ([#400](https://github.com/sct/overseerr/issues/400)) ([1bd0e64](https://github.com/sct/overseerr/commit/1bd0e646e313ddf77ef331e818e03401fbf64a72))
* **lang:** translations update from Weblate ([#403](https://github.com/sct/overseerr/issues/403)) ([3778ad8](https://github.com/sct/overseerr/commit/3778ad829c0897de178212b3bde4c0d3b5089161))
# [1.10.0](https://github.com/sct/overseerr/compare/v1.9.1...v1.10.0) (2020-12-19)
### Bug Fixes
* **email:** fix link to Overseerr in email templates ([816fec1](https://github.com/sct/overseerr/commit/816fec1a83a53edb3b65c3e5e7d0e6e1bd49726d)), closes [#392](https://github.com/sct/overseerr/issues/392)
* **frontend:** adjust padding of search box so placeholder text fits on mobile ([3601d44](https://github.com/sct/overseerr/commit/3601d442db32d3f98f7b050365c11ea8ef9bc4ae)), closes [#393](https://github.com/sct/overseerr/issues/393)
* **frontend:** changed request block for slideover on mobile UI ([#387](https://github.com/sct/overseerr/issues/387)) ([549567a](https://github.com/sct/overseerr/commit/549567a7e9db01933546d9970fc06f17218dfab1))
* **frontend:** hide Request More button if all current seasons are available ([2a4dd52](https://github.com/sct/overseerr/commit/2a4dd52275007e48f946c3b9e29f1d78da57bdaa)), closes [#343](https://github.com/sct/overseerr/issues/343)
* **frontend:** try not to render broken rottentomatoes data ([a0c5608](https://github.com/sct/overseerr/commit/a0c5608aa0b6c7a4294300589efa9a662163ce48))
### Features
* **lang:** translations update from Weblate ([#391](https://github.com/sct/overseerr/issues/391)) ([5f71fb7](https://github.com/sct/overseerr/commit/5f71fb7ee280714275d2ac045c472fcdddd5a2ea))
* add missing tzdata package to image ([53bede6](https://github.com/sct/overseerr/commit/53bede692d4f0e940dededa63015fe1908129914)), closes [#394](https://github.com/sct/overseerr/issues/394)
* **frontend:** add external links to movie and tv detail pages ([a0024a0](https://github.com/sct/overseerr/commit/a0024a0cbe717d78f53413bb78644c829f143c4d))
* **lang:** translations update from Weblate ([#380](https://github.com/sct/overseerr/issues/380)) ([8408e19](https://github.com/sct/overseerr/commit/8408e19568b2f239c57e11e2946c75f193d1c22e))
## [1.9.1](https://github.com/sct/overseerr/compare/v1.9.0...v1.9.1) (2020-12-18)
### Bug Fixes
* change default internal port to 5055 ([#389](https://github.com/sct/overseerr/issues/389)) ([5e5ba40](https://github.com/sct/overseerr/commit/5e5ba4050563f07bff367d2fb31ed7e7fca4291e))
# [1.9.0](https://github.com/sct/overseerr/compare/v1.8.0...v1.9.0) (2020-12-18)

View File

@@ -15,6 +15,8 @@ FROM node:12.18-alpine
ARG COMMIT_TAG
ENV COMMIT_TAG=${COMMIT_TAG}
RUN apk add tzdata
COPY . /app
WORKDIR /app
@@ -25,4 +27,4 @@ COPY --from=BUILD_IMAGE /app/node_modules ./node_modules
CMD yarn start
EXPOSE 3000
EXPOSE 5055

View File

@@ -16,7 +16,7 @@
<a href="https://lgtm.com/projects/g/sct/overseerr/context:javascript"><img alt="Language grade: JavaScript" src="https://img.shields.io/lgtm/grade/javascript/g/sct/overseerr.svg?logo=lgtm&logoWidth=18"/></a>
<img alt="GitHub" src="https://img.shields.io/github/license/sct/overseerr">
<!-- 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-16-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-23-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
</p>
@@ -54,7 +54,7 @@ Currently, Overseerr is only distributed through Docker images. If you have Dock
docker run -d \
-e LOG_LEVEL=info \
-e TZ=Asia/Tokyo \
-p 5055:3000 \
-p 5055:5055 \
-v /path/to/appdata/config:/app/config \
--restart unless-stopped \
sctx/overseerr
@@ -76,7 +76,7 @@ After running Overseerr for the first time, configure it by visiting the web UI
## API Documentation
Full API documentation will soon be published automatically and available outside of running the app. Currently, you can access the API docs by running Overseerr locally and visiting http://localhost:3000/api-docs
Full API documentation will soon be published automatically and available outside of running the app. Currently, you can access the API docs by running Overseerr locally and visiting http://localhost:5055/api-docs
## Community
@@ -119,6 +119,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tr>
<td align="center"><a href="https://github.com/ecelebi29"><img src="https://avatars2.githubusercontent.com/u/8337120?v=4" width="100px;" alt=""/><br /><sub><b>ecelebi29</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=ecelebi29" title="Code">💻</a> <a href="https://github.com/sct/overseerr/commits?author=ecelebi29" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/mmozeiko"><img src="https://avatars3.githubusercontent.com/u/1665010?v=4" width="100px;" alt=""/><br /><sub><b>Mārtiņš Možeiko</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=mmozeiko" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mazzetta86"><img src="https://avatars2.githubusercontent.com/u/45591560?v=4" width="100px;" alt=""/><br /><sub><b>mazzetta86</b></sub></a><br /><a href="#translation-mazzetta86" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Panzer1119"><img src="https://avatars1.githubusercontent.com/u/23016343?v=4" width="100px;" alt=""/><br /><sub><b>Paul Hagedorn</b></sub></a><br /><a href="#translation-Panzer1119" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Shagon94"><img src="https://avatars3.githubusercontent.com/u/9140783?v=4" width="100px;" alt=""/><br /><sub><b>Shagon94</b></sub></a><br /><a href="#translation-Shagon94" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/sebstrgg"><img src="https://avatars3.githubusercontent.com/u/27026694?v=4" width="100px;" alt=""/><br /><sub><b>sebstrgg</b></sub></a><br /><a href="#translation-sebstrgg" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/danshilm"><img src="https://avatars2.githubusercontent.com/u/20923978?v=4" width="100px;" alt=""/><br /><sub><b>Danshil Mungur</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=danshilm" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/doob187"><img src="https://avatars1.githubusercontent.com/u/60312740?v=4" width="100px;" alt=""/><br /><sub><b>doob187</b></sub></a><br /><a href="#infra-doob187" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/johnpyp"><img src="https://avatars2.githubusercontent.com/u/20625636?v=4" width="100px;" alt=""/><br /><sub><b>johnpyp</b></sub></a><br /><a href="https://github.com/sct/overseerr/commits?author=johnpyp" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -5,7 +5,7 @@ services:
context: .
dockerfile: Dockerfile.local
ports:
- 3000:3000
- 5055:5055
volumes:
- .:/app:rw,cached
- /app/node_modules

View File

@@ -2,6 +2,7 @@ const devConfig = {
type: 'sqlite',
database: 'config/db/db.sqlite3',
synchronize: true,
migrationsRun: false,
logging: false,
entities: ['server/entity/**/*.ts'],
migrations: ['server/migration/**/*.ts'],
@@ -19,7 +20,7 @@ const prodConfig = {
logging: false,
entities: ['dist/entity/**/*.js'],
migrations: ['dist/migration/**/*.js'],
migrationsRun: true,
migrationsRun: false,
subscribers: ['dist/subscriber/**/*.js'],
cli: {
entitiesDir: 'dist/entity',

View File

@@ -58,6 +58,9 @@ components:
applicationUrl:
type: string
example: https://os.example.com
defaultPermissions:
type: number
example: 32
PlexLibrary:
type: object
properties:
@@ -1488,6 +1491,21 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/NotificationEmailSettings'
/settings/notifications/email/test:
post:
summary: Test the provided email settings
description: Sends a test notification to the email agent
tags:
- settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationEmailSettings'
responses:
'204':
description: Test notification attempted
/settings/notifications/discord:
get:
summary: Return current discord notification settings
@@ -1519,6 +1537,21 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/DiscordSettings'
/settings/notifications/discord/test:
post:
summary: Test the provided discord settings
description: Sends a test notification to the discord agent
tags:
- settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DiscordSettings'
responses:
'204':
description: Test notification attempted
/settings/about:
get:
summary: Return current about stats
@@ -1542,6 +1575,10 @@ paths:
totalMediaItems:
type: number
example: 100
tz:
type: string
nullable: true
example: Asia/Tokyo
/auth/me:
get:
summary: Returns the currently logged in user
@@ -1636,6 +1673,24 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/User'
/user/import-from-plex:
post:
summary: Imports all users from Plex
description: |
Requests users from the Plex Server and creates a new user for each of them
Requires the `MANAGE_USERS` permission.
tags:
- users
responses:
'201':
description: A list of the newly created users
content:
application/json:
schema:
type: array
$ref: '#/components/schemas/User'
/user/{userId}:
get:
summary: Retrieve a user by ID

View File

@@ -1,6 +1,6 @@
{
"name": "overseerr",
"version": "1.9.0",
"version": "1.12.0",
"private": true,
"scripts": {
"dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts",
@@ -25,21 +25,22 @@
"cookie-parser": "^1.4.5",
"email-templates": "^8.0.2",
"express": "^4.17.1",
"express-openapi-validator": "^4.8.0",
"express-openapi-validator": "^4.9.4",
"express-session": "^1.17.1",
"formik": "^2.2.5",
"formik": "^2.2.6",
"intl": "^1.2.5",
"lodash": "^4.17.20",
"next": "^10.0.3",
"node-schedule": "^1.3.2",
"nodemailer": "^6.4.16",
"nodemailer": "^6.4.17",
"nookies": "^2.5.0",
"plex-api": "^5.3.1",
"pug": "^3.0.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-intersection-observer": "^8.31.0",
"react-intl": "^5.10.6",
"react-intl": "^5.10.9",
"react-markdown": "^5.0.3",
"react-spring": "^8.0.27",
"react-toast-notifications": "^2.4.0",
"react-transition-group": "^4.4.1",
@@ -47,7 +48,7 @@
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.0.0",
"swagger-ui-express": "^4.1.5",
"swr": "^0.3.9",
"swr": "^0.3.11",
"typeorm": "^0.2.29",
"uuid": "^8.3.2",
"winston": "^3.3.3",
@@ -56,7 +57,7 @@
"yup": "^0.32.8"
},
"devDependencies": {
"@babel/cli": "^7.12.8",
"@babel/cli": "^7.12.10",
"@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0",
"@semantic-release/changelog": "^5.0.1",
@@ -72,7 +73,7 @@
"@types/express": "^4.17.9",
"@types/express-session": "^1.17.0",
"@types/lodash": "^4.14.165",
"@types/node": "^14.14.11",
"@types/node": "^14.14.14",
"@types/node-schedule": "^1.3.1",
"@types/nodemailer": "^6.4.0",
"@types/react": "^17.0.0",
@@ -83,24 +84,24 @@
"@types/uuid": "^8.3.0",
"@types/xml2js": "^0.4.7",
"@types/yamljs": "^0.2.31",
"@types/yup": "^0.29.10",
"@typescript-eslint/eslint-plugin": "^4.9.1",
"@typescript-eslint/parser": "^4.9.1",
"@types/yup": "^0.29.11",
"@typescript-eslint/eslint-plugin": "^4.10.0",
"@typescript-eslint/parser": "^4.10.0",
"autoprefixer": "^9",
"babel-plugin-react-intl": "^8.2.21",
"babel-plugin-react-intl": "^8.2.22",
"babel-plugin-react-intl-auto": "^3.3.0",
"commitizen": "^4.2.2",
"copyfiles": "^2.4.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^7.15.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-formatjs": "^2.9.10",
"eslint": "^7.16.0",
"eslint-config-prettier": "^7.1.0",
"eslint-plugin-formatjs": "^2.9.11",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.2.0",
"eslint-plugin-prettier": "^3.3.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"extract-react-intl-messages": "^4.1.1",
"husky": "^4.3.5",
"husky": "^4.3.6",
"lint-staged": "^10.5.3",
"nodemon": "^2.0.6",
"postcss": "^7",
@@ -110,7 +111,7 @@
"semantic-release-docker": "^2.2.0",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"ts-node": "^9.1.1",
"typescript": "^4.1.2"
"typescript": "^4.1.3"
},
"config": {
"commitizen": {
@@ -172,7 +173,12 @@
}
],
"semantic-release-docker",
"@semantic-release/github"
[
"@semantic-release/github",
{
"addReleases": "bottom"
}
]
],
"branches": [
"master"
@@ -182,7 +188,8 @@
{
"path": "semantic-release-docker",
"name": "sctx/overseerr"
}
},
"@semantic-release/github"
]
}
}

View File

@@ -1 +1,20 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#1e2937","display":"standalone"}
{
"name": "Overseerr",
"short_name": "Overseerr",
"start_url": "./",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#1e2937",
"display": "standalone"
}

View File

@@ -56,6 +56,21 @@ interface FriendResponse {
};
}
interface UsersResponse {
MediaContainer: {
User: {
$: {
id: string;
title: string;
username: string;
email: string;
thumb: string;
};
Server: ServerResponse[];
}[];
};
}
class PlexTvAPI {
private authToken: string;
private axios: AxiosInstance;
@@ -129,6 +144,18 @@ class PlexTvAPI {
return false;
}
}
public async getUsers(): Promise<UsersResponse> {
const response = await this.axios.get('/api/users', {
transformResponse: [],
responseType: 'text',
});
const parsedXml = (await xml2js.parseStringPromise(
response.data
)) as UsersResponse;
return parsedXml;
}
}
export default PlexTvAPI;

View File

@@ -78,7 +78,7 @@ class RadarrAPI {
public addMovie = async (options: RadarrMovieOptions): Promise<void> => {
try {
await this.axios.post<RadarrMovie>(`/movie`, {
const response = await this.axios.post<RadarrMovie>(`/movie`, {
title: options.title,
qualityProfileId: options.qualityProfileId,
profileId: options.profileId,
@@ -92,6 +92,19 @@ class RadarrAPI {
searchForMovie: options.searchNow,
},
});
if (response.data.id) {
logger.info('Radarr accepted request', { label: 'Radarr' });
logger.debug('Radarr add details', {
label: 'Radarr',
movie: response.data,
});
} else {
logger.error('Failed to add movie to Radarr', {
label: 'Radarr',
options,
});
}
} catch (e) {
logger.error(
'Failed to add movie to Radarr. This might happen if the movie already exists, in which case you can safely ignore this error.',

View File

@@ -116,7 +116,7 @@ class RottenTomatoes {
public async getTVRatings(
name: string,
year: number
year?: number
): Promise<RTRating | null> {
try {
const response = await this.axios.get<RTMultiSearchResponse>(
@@ -126,9 +126,13 @@ class RottenTomatoes {
}
);
const tvshow = response.data.tvSeries.find(
(series) => series.startYear === year
);
let tvshow: RTTvSearchResult | undefined = response.data.tvSeries[0];
if (year) {
tvshow = response.data.tvSeries.find(
(series) => series.startYear === year
);
}
if (!tvshow) {
return null;

View File

@@ -126,7 +126,7 @@ class SonarrAPI {
series.addOptions = {
ignoreEpisodesWithFiles: true,
searchForMissingEpisodes: true,
searchForMissingEpisodes: options.searchNow,
};
const newSeriesResponse = await this.axios.put<SonarrSeries>(
@@ -134,6 +134,21 @@ class SonarrAPI {
series
);
if (newSeriesResponse.data.id) {
logger.info('Sonarr accepted request. Updated existing series', {
label: 'Sonarr',
});
logger.debug('Sonarr add details', {
label: 'Sonarr',
movie: newSeriesResponse.data,
});
} else {
logger.error('Failed to add movie to Sonarr', {
label: 'Sonarr',
options,
});
}
return newSeriesResponse.data;
}
@@ -162,6 +177,19 @@ class SonarrAPI {
} as Partial<SonarrSeries>
);
if (createdSeriesResponse.data.id) {
logger.info('Sonarr accepted request', { label: 'Sonarr' });
logger.debug('Sonarr add details', {
label: 'Sonarr',
movie: createdSeriesResponse.data,
});
} else {
logger.error('Failed to add movie to Sonarr', {
label: 'Sonarr',
options,
});
}
return createdSeriesResponse.data;
} catch (e) {
logger.error('Something went wrong adding a series to Sonarr', {

View File

@@ -374,7 +374,12 @@ class TheMovieDb {
return response.data;
} catch (e) {
throw new Error(`[TMDB] Failed to search multi: ${e.message}`);
return {
page: 1,
results: [],
total_pages: 1,
total_results: 0,
};
}
};

View File

@@ -92,6 +92,9 @@ class Media {
@UpdateDateColumn()
public updatedAt: Date;
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
public lastSeasonChange: Date;
constructor(init?: Partial<Media>) {
Object.assign(this, init);
}

View File

@@ -29,7 +29,15 @@ const handle = app.getRequestHandler();
app
.prepare()
.then(async () => {
await createConnection();
const dbConnection = await createConnection();
// Run migrations in production
if (process.env.NODE_ENV === 'production') {
await dbConnection.query('PRAGMA foreign_keys=OFF');
await dbConnection.runMigrations();
await dbConnection.query('PRAGMA foreign_keys=ON');
}
// Load Settings
const settings = getSettings().load();
@@ -100,7 +108,7 @@ app
}
);
const port = Number(process.env.PORT) || 3000;
const port = Number(process.env.PORT) || 5055;
const host = process.env.HOST;
if (host) {
server.listen(port, host, () => {

View File

@@ -2,4 +2,5 @@ export interface SettingsAboutResponse {
version: string;
totalRequests: number;
totalMediaItems: number;
tz?: string;
}

View File

@@ -1,7 +1,10 @@
import { getRepository } from 'typeorm';
import { User } from '../../entity/User';
import PlexAPI, { PlexLibraryItem } from '../../api/plexapi';
import TheMovieDb, { TmdbTvDetails } from '../../api/themoviedb';
import TheMovieDb, {
TmdbMovieDetails,
TmdbTvDetails,
} from '../../api/themoviedb';
import Media from '../../entity/Media';
import { MediaStatus, MediaType } from '../../constants/media';
import logger from '../../logger';
@@ -93,40 +96,58 @@ class JobPlexSync {
this.log(`Saved ${plexitem.title}`);
}
} else {
const matchedid = plexitem.guid.match(/imdb:\/\/(tt[0-9]+)/);
let tmdbMovieId: number | undefined;
let tmdbMovie: TmdbMovieDetails | undefined;
if (matchedid?.[1]) {
const tmdbMovie = await this.tmdb.getMovieByImdbId({
imdbId: matchedid[1],
const imdbMatch = plexitem.guid.match(imdbRegex);
const tmdbMatch = plexitem.guid.match(tmdbRegex);
if (imdbMatch) {
tmdbMovie = await this.tmdb.getMovieByImdbId({
imdbId: imdbMatch[1],
});
tmdbMovieId = tmdbMovie.id;
} else if (tmdbMatch) {
tmdbMovieId = Number(tmdbMatch[1]);
}
const existing = await this.getExisting(tmdbMovie.id);
if (existing && existing.status === MediaStatus.AVAILABLE) {
this.log(`Title exists and is already available ${plexitem.title}`);
} else if (existing && existing.status !== MediaStatus.AVAILABLE) {
existing.status = MediaStatus.AVAILABLE;
await mediaRepository.save(existing);
this.log(
`Request for ${plexitem.title} exists. Setting status AVAILABLE`,
'info'
);
} else if (tmdbMovie) {
const newMedia = new Media();
newMedia.imdbId = tmdbMovie.external_ids.imdb_id;
newMedia.tmdbId = tmdbMovie.id;
newMedia.status = MediaStatus.AVAILABLE;
newMedia.mediaType = MediaType.MOVIE;
await mediaRepository.save(newMedia);
this.log(`Saved ${tmdbMovie.title}`);
if (!tmdbMovieId) {
throw new Error('Unable to find TMDB ID');
}
const existing = await this.getExisting(tmdbMovieId);
if (existing && existing.status === MediaStatus.AVAILABLE) {
this.log(`Title exists and is already available ${plexitem.title}`);
} else if (existing && existing.status !== MediaStatus.AVAILABLE) {
existing.status = MediaStatus.AVAILABLE;
await mediaRepository.save(existing);
this.log(
`Request for ${plexitem.title} exists. Setting status AVAILABLE`,
'info'
);
} else {
// If we have a tmdb movie guid but it didn't already exist, only then
// do we request the movie from tmdb (to reduce api requests)
if (!tmdbMovie) {
tmdbMovie = await this.tmdb.getMovie({ movieId: tmdbMovieId });
}
const newMedia = new Media();
newMedia.imdbId = tmdbMovie.external_ids.imdb_id;
newMedia.tmdbId = tmdbMovie.id;
newMedia.status = MediaStatus.AVAILABLE;
newMedia.mediaType = MediaType.MOVIE;
await mediaRepository.save(newMedia);
this.log(`Saved ${tmdbMovie.title}`);
}
}
} catch (e) {
this.log(
`Failed to process plex item. ratingKey: ${
plexitem.parentRatingKey ?? plexitem.ratingKey
}`,
'error'
`Failed to process plex item. ratingKey: ${plexitem.ratingKey}`,
'error',
{
errorMessage: e.message,
plexitem,
}
);
}
}
@@ -169,6 +190,12 @@ class JobPlexSync {
const newSeasons: Season[] = [];
const currentSeasonAvailable = (
media?.seasons.filter(
(season) => season.status === MediaStatus.AVAILABLE
) ?? []
).length;
seasons.forEach((season) => {
const matchedPlexSeason = metadata.Children?.Metadata.find(
(md) => Number(md.index) === season.season_number
@@ -219,6 +246,25 @@ class JobPlexSync {
if (media) {
// Update existing
media.seasons = [...media.seasons, ...newSeasons];
const newSeasonAvailable = (
media.seasons.filter(
(season) => season.status === MediaStatus.AVAILABLE
) ?? []
).length;
// If at least one new season has become available, update
// the lastSeasonChange field so we can trigger notifications
if (newSeasonAvailable > currentSeasonAvailable) {
this.log(
`Detected ${
newSeasonAvailable - currentSeasonAvailable
} new season(s) for ${tvShow.name}`,
'debug'
);
media.lastSeasonChange = new Date();
}
media.status = isAllSeasons
? MediaStatus.AVAILABLE
: MediaStatus.PARTIALLY_AVAILABLE;

View File

@@ -1,5 +1,6 @@
import { Notification } from '..';
import { User } from '../../../entity/User';
import { NotificationAgentConfig } from '../../settings';
export interface NotificationPayload {
subject: string;
@@ -9,6 +10,15 @@ export interface NotificationPayload {
extra?: { name: string; value: string }[];
}
export abstract class BaseAgent<T extends NotificationAgentConfig> {
protected settings?: T;
public constructor(settings?: T) {
this.settings = settings;
}
protected abstract getSettings(): T;
}
export interface NotificationAgent {
shouldSend(type: Notification): boolean;
send(type: Notification, payload: NotificationPayload): Promise<boolean>;

View File

@@ -1,8 +1,8 @@
import axios from 'axios';
import { Notification } from '..';
import logger from '../../../logger';
import { getSettings } from '../../settings';
import type { NotificationAgent, NotificationPayload } from './agent';
import { getSettings, NotificationAgentDiscord } from '../../settings';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
enum EmbedColors {
DEFAULT = 0,
@@ -37,6 +37,11 @@ interface DiscordImageEmbed {
width?: number;
}
interface Field {
name: string;
value: string;
inline?: boolean;
}
interface DiscordRichEmbed {
title?: string;
type?: 'rich'; // Always rich for webhooks
@@ -61,11 +66,7 @@ interface DiscordRichEmbed {
icon_url?: string;
proxy_icon_url?: string;
};
fields?: {
name: string;
value: string;
inline?: boolean;
}[];
fields?: Field[];
}
interface DiscordWebhookPayload {
@@ -75,26 +76,72 @@ interface DiscordWebhookPayload {
tts: boolean;
}
class DiscordAgent implements NotificationAgent {
class DiscordAgent
extends BaseAgent<NotificationAgentDiscord>
implements NotificationAgent {
protected getSettings(): NotificationAgentDiscord {
if (this.settings) {
return this.settings;
}
const settings = getSettings();
return settings.notifications.agents.discord;
}
public buildEmbed(
type: Notification,
payload: NotificationPayload
): DiscordRichEmbed {
let color = EmbedColors.DEFAULT;
let status = 'Unknown';
const fields: Field[] = [];
switch (type) {
case Notification.MEDIA_PENDING:
color = EmbedColors.ORANGE;
status = 'Pending Approval';
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
inline: true,
},
{
name: 'Status',
value: 'Pending Approval',
inline: true,
}
);
break;
case Notification.MEDIA_APPROVED:
color = EmbedColors.PURPLE;
status = 'Processing Request';
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
inline: true,
},
{
name: 'Status',
value: 'Processing Request',
inline: true,
}
);
break;
case Notification.MEDIA_AVAILABLE:
color = EmbedColors.GREEN;
status = 'Available';
fields.push(
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
inline: true,
},
{
name: 'Status',
value: 'Available',
inline: true,
}
);
break;
}
@@ -105,16 +152,7 @@ class DiscordAgent implements NotificationAgent {
timestamp: new Date().toISOString(),
author: { name: 'Overseerr' },
fields: [
{
name: 'Requested By',
value: payload.notifyUser.username ?? '',
inline: true,
},
{
name: 'Status',
value: status,
inline: true,
},
...fields,
// If we have extra data, map it to fields for discord notifications
...(payload.extra ?? []).map((extra) => ({
name: extra.name,
@@ -130,12 +168,7 @@ class DiscordAgent implements NotificationAgent {
// TODO: Add checking for type here once we add notification type filters for agents
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public shouldSend(_type: Notification): boolean {
const settings = getSettings();
if (
settings.notifications.agents.discord?.enabled &&
settings.notifications.agents.discord?.options?.webhookUrl
) {
if (this.getSettings().enabled && this.getSettings().options.webhookUrl) {
return true;
}
@@ -146,11 +179,9 @@ class DiscordAgent implements NotificationAgent {
type: Notification,
payload: NotificationPayload
): Promise<boolean> {
const settings = getSettings();
logger.debug('Sending discord notification', { label: 'Notifications' });
try {
const webhookUrl =
settings.notifications.agents.discord?.options?.webhookUrl;
const webhookUrl = this.getSettings().options.webhookUrl;
if (!webhookUrl) {
return false;

View File

@@ -1,7 +1,7 @@
import type { NotificationAgent, NotificationPayload } from './agent';
import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
import { Notification } from '..';
import path from 'path';
import { getSettings } from '../../settings';
import { getSettings, NotificationAgentEmail } from '../../settings';
import nodemailer from 'nodemailer';
import Email from 'email-templates';
import logger from '../../../logger';
@@ -9,13 +9,25 @@ import { getRepository } from 'typeorm';
import { User } from '../../../entity/User';
import { Permission } from '../../permissions';
class EmailAgent implements NotificationAgent {
class EmailAgent
extends BaseAgent<NotificationAgentEmail>
implements NotificationAgent {
protected getSettings(): NotificationAgentEmail {
if (this.settings) {
return this.settings;
}
const settings = getSettings();
return settings.notifications.agents.email;
}
// TODO: Add checking for type here once we add notification type filters for agents
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public shouldSend(_type: Notification): boolean {
const settings = getSettings();
const settings = this.getSettings();
if (settings.notifications.agents.email.enabled) {
if (settings.enabled) {
return true;
}
@@ -23,7 +35,7 @@ class EmailAgent implements NotificationAgent {
}
private getSmtpTransport() {
const emailSettings = getSettings().notifications.agents.email.options;
const emailSettings = this.getSettings().options;
return nodemailer.createTransport({
host: emailSettings.smtpHost,
@@ -40,7 +52,7 @@ class EmailAgent implements NotificationAgent {
}
private getNewEmail() {
const settings = getSettings().notifications.agents.email;
const settings = this.getSettings();
return new Email({
message: {
from: settings.options.emailFrom,
@@ -51,7 +63,8 @@ class EmailAgent implements NotificationAgent {
}
private async sendMediaRequestEmail(payload: NotificationPayload) {
const settings = getSettings().main;
// This is getting main settings for the whole app
const applicationUrl = getSettings().main.applicationUrl;
try {
const userRepository = getRepository(User);
const users = await userRepository.find();
@@ -76,7 +89,7 @@ class EmailAgent implements NotificationAgent {
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
actionUrl: settings.applicationUrl,
actionUrl: applicationUrl,
requestType: 'New Request',
},
});
@@ -92,7 +105,8 @@ class EmailAgent implements NotificationAgent {
}
private async sendMediaApprovedEmail(payload: NotificationPayload) {
const settings = getSettings().main;
// This is getting main settings for the whole app
const applicationUrl = getSettings().main.applicationUrl;
try {
const email = this.getNewEmail();
@@ -110,7 +124,7 @@ class EmailAgent implements NotificationAgent {
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
actionUrl: settings.applicationUrl,
actionUrl: applicationUrl,
requestType: 'Request Approved',
},
});
@@ -125,7 +139,8 @@ class EmailAgent implements NotificationAgent {
}
private async sendMediaAvailableEmail(payload: NotificationPayload) {
const settings = getSettings().main;
// This is getting main settings for the whole app
const applicationUrl = getSettings().main.applicationUrl;
try {
const email = this.getNewEmail();
@@ -143,7 +158,7 @@ class EmailAgent implements NotificationAgent {
imageUrl: payload.image,
timestamp: new Date().toTimeString(),
requestedBy: payload.notifyUser.username,
actionUrl: settings.applicationUrl,
actionUrl: applicationUrl,
requestType: 'Now Available',
},
});
@@ -157,6 +172,32 @@ class EmailAgent implements NotificationAgent {
}
}
private async sendTestEmail(payload: NotificationPayload) {
// This is getting main settings for the whole app
const applicationUrl = getSettings().main.applicationUrl;
try {
const email = this.getNewEmail();
email.send({
template: path.join(__dirname, '../../../templates/email/test-email'),
message: {
to: payload.notifyUser.email,
},
locals: {
body: payload.message,
actionUrl: applicationUrl,
},
});
return true;
} catch (e) {
logger.error('Mail notification failed to send', {
label: 'Notifications',
message: e.message,
});
return false;
}
}
public async send(
type: Notification,
payload: NotificationPayload
@@ -173,6 +214,9 @@ class EmailAgent implements NotificationAgent {
case Notification.MEDIA_AVAILABLE:
this.sendMediaAvailableEmail(payload);
break;
case Notification.TEST_NOTIFICATION:
this.sendTestEmail(payload);
break;
}
return true;

View File

@@ -5,6 +5,7 @@ export enum Notification {
MEDIA_PENDING = 2,
MEDIA_APPROVED = 4,
MEDIA_AVAILABLE = 8,
TEST_NOTIFICATION = 16,
}
class NotificationManager {

View File

@@ -2,6 +2,7 @@ import fs from 'fs';
import path from 'path';
import { merge } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { Permission } from './permissions';
export interface Library {
id: string;
@@ -47,24 +48,25 @@ export interface SonarrSettings extends DVRSettings {
export interface MainSettings {
apiKey: string;
applicationUrl: string;
defaultPermissions: number;
}
interface PublicSettings {
initialized: boolean;
}
interface NotificationAgent {
export interface NotificationAgentConfig {
enabled: boolean;
types: number;
options: Record<string, unknown>;
}
interface NotificationAgentDiscord extends NotificationAgent {
export interface NotificationAgentDiscord extends NotificationAgentConfig {
options: {
webhookUrl: string;
};
}
interface NotificationAgentEmail extends NotificationAgent {
export interface NotificationAgentEmail extends NotificationAgentConfig {
options: {
emailFrom: string;
smtpHost: string;
@@ -105,6 +107,7 @@ class Settings {
main: {
apiKey: '',
applicationUrl: '',
defaultPermissions: Permission.REQUEST,
},
plex: {
name: '',

View File

@@ -0,0 +1,52 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddLastSeasonChangeMedia1608477467935
implements MigrationInterface {
name = 'AddLastSeasonChangeMedia1608477467935';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5"`);
await queryRunner.query(`DROP INDEX "IDX_41a289eb1fa489c1bc6f38d9c3"`);
await queryRunner.query(`DROP INDEX "IDX_7ff2d11f6a83cb52386eaebe74"`);
await queryRunner.query(
`CREATE TABLE "temporary_media" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "tmdbId" integer NOT NULL, "tvdbId" integer, "imdbId" varchar, "status" integer NOT NULL DEFAULT (1), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "lastSeasonChange" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), CONSTRAINT "UQ_7157aad07c73f6a6ae3bbd5ef5e" UNIQUE ("tmdbId"), CONSTRAINT "UQ_41a289eb1fa489c1bc6f38d9c3c" UNIQUE ("tvdbId"), CONSTRAINT "UQ_b4e05e8b45c9cc64e047db95463" UNIQUE ("imdbId"))`
);
await queryRunner.query(
`INSERT INTO "temporary_media"("id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange") SELECT "id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt", "lastSeasonChange" FROM "media"`
);
await queryRunner.query(`DROP TABLE "media"`);
await queryRunner.query(`ALTER TABLE "temporary_media" RENAME TO "media"`);
await queryRunner.query(
`CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media" ("tmdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media" ("tvdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media" ("imdbId") `
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_7ff2d11f6a83cb52386eaebe74"`);
await queryRunner.query(`DROP INDEX "IDX_41a289eb1fa489c1bc6f38d9c3"`);
await queryRunner.query(`DROP INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5"`);
await queryRunner.query(`ALTER TABLE "media" RENAME TO "temporary_media"`);
await queryRunner.query(
`CREATE TABLE "media" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "mediaType" varchar NOT NULL, "tmdbId" integer NOT NULL, "tvdbId" integer, "imdbId" varchar, "status" integer NOT NULL DEFAULT (1), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_7157aad07c73f6a6ae3bbd5ef5e" UNIQUE ("tmdbId"), CONSTRAINT "UQ_41a289eb1fa489c1bc6f38d9c3c" UNIQUE ("tvdbId"))`
);
await queryRunner.query(
`INSERT INTO "media"("id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt") SELECT "id", "mediaType", "tmdbId", "tvdbId", "imdbId", "status", "createdAt", "updatedAt" FROM "temporary_media"`
);
await queryRunner.query(`DROP TABLE "temporary_media"`);
await queryRunner.query(
`CREATE INDEX "IDX_7ff2d11f6a83cb52386eaebe74" ON "media" ("imdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_41a289eb1fa489c1bc6f38d9c3" ON "media" ("tvdbId") `
);
await queryRunner.query(
`CREATE INDEX "IDX_7157aad07c73f6a6ae3bbd5ef5" ON "media" ("tmdbId") `
);
}
}

View File

@@ -5,6 +5,7 @@ import PlexTvAPI from '../api/plextv';
import { isAuthenticated } from '../middleware/auth';
import { Permission } from '../lib/permissions';
import logger from '../logger';
import { getSettings } from '../lib/settings';
const authRoutes = Router();
@@ -25,6 +26,7 @@ authRoutes.get('/me', isAuthenticated(), async (req, res) => {
});
authRoutes.post('/login', async (req, res, next) => {
const settings = getSettings();
const userRepository = getRepository(User);
const body = req.body as { authToken?: string };
@@ -69,44 +71,48 @@ authRoutes.post('/login', async (req, res, next) => {
await userRepository.save(user);
}
// If we get to this point, the user does not already exist so we need to create the
// user _assuming_ they have access to the plex server
const mainUser = await userRepository.findOneOrFail({
select: ['id', 'plexToken'],
order: { id: 'ASC' },
});
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
if (await mainPlexTv.checkUserAccess(account)) {
user = new User({
email: account.email,
username: account.username,
plexId: account.id,
plexToken: account.authToken,
permissions: Permission.REQUEST,
avatar: account.thumb,
});
await userRepository.save(user);
} else {
logger.info(
'Failed login attempt from user without access to plex server',
{
label: 'Auth',
account: {
...account,
authentication_token: '__REDACTED__',
authToken: '__REDACTED__',
},
}
);
return next({
status: 403,
message: 'You do not have access to this Plex server',
// Double check that we didn't create the first admin user before running this
if (!user) {
// If we get to this point, the user does not already exist so we need to create the
// user _assuming_ they have access to the plex server
const mainUser = await userRepository.findOneOrFail({
select: ['id', 'plexToken'],
order: { id: 'ASC' },
});
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
if (await mainPlexTv.checkUserAccess(account)) {
user = new User({
email: account.email,
username: account.username,
plexId: account.id,
plexToken: account.authToken,
permissions: settings.main.defaultPermissions,
avatar: account.thumb,
});
await userRepository.save(user);
} else {
logger.info(
'Failed login attempt from user without access to plex server',
{
label: 'Auth',
account: {
...account,
authentication_token: '__REDACTED__',
authToken: '__REDACTED__',
},
}
);
return next({
status: 403,
message: 'You do not have access to this Plex server',
});
}
}
}
// Set logged in session
if (req.session && user) {
if (req.session) {
req.session.userId = user.id;
}

View File

@@ -16,10 +16,14 @@ import logger from '../logger';
import { scheduledJobs } from '../job/schedule';
import { Permission } from '../lib/permissions';
import { isAuthenticated } from '../middleware/auth';
import { merge } from 'lodash';
import { merge, omit } from 'lodash';
import Media from '../entity/Media';
import { MediaRequest } from '../entity/MediaRequest';
import { getAppVersion } from '../utils/appVersion';
import { SettingsAboutResponse } from '../interfaces/api/settingsInterfaces';
import { Notification } from '../lib/notifications';
import DiscordAgent from '../lib/notifications/agents/discord';
import EmailAgent from '../lib/notifications/agents/email';
const settingsRoutes = Router();
@@ -28,9 +32,7 @@ const filteredMainSettings = (
main: MainSettings
): Partial<MainSettings> => {
if (!user?.hasPermission(Permission.ADMIN)) {
return {
applicationUrl: main.applicationUrl,
};
return omit(main, 'apiKey');
}
return main;
@@ -447,6 +449,25 @@ settingsRoutes.post('/notifications/discord', (req, res) => {
res.status(200).json(settings.notifications.agents.discord);
});
settingsRoutes.post('/notifications/discord/test', (req, res, next) => {
if (!req.user) {
return next({
status: 500,
message: 'User information missing from request',
});
}
const discordAgent = new DiscordAgent(req.body);
discordAgent.send(Notification.TEST_NOTIFICATION, {
notifyUser: req.user,
subject: 'Test Notification',
message:
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
});
return res.status(204).send();
});
settingsRoutes.get('/notifications/email', (_req, res) => {
const settings = getSettings();
@@ -462,6 +483,25 @@ settingsRoutes.post('/notifications/email', (req, res) => {
res.status(200).json(settings.notifications.agents.email);
});
settingsRoutes.post('/notifications/email/test', (req, res, next) => {
if (!req.user) {
return next({
status: 500,
message: 'User information missing from request',
});
}
const emailAgent = new EmailAgent(req.body);
emailAgent.send(Notification.TEST_NOTIFICATION, {
notifyUser: req.user,
subject: 'Test Notification',
message:
'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
});
return res.status(204).send();
});
settingsRoutes.get('/about', async (req, res) => {
const mediaRepository = getRepository(Media);
const mediaRequestRepository = getRepository(MediaRequest);
@@ -473,7 +513,8 @@ settingsRoutes.get('/about', async (req, res) => {
version: getAppVersion(),
totalMediaItems,
totalRequests,
});
tz: process.env.TZ,
} as SettingsAboutResponse);
});
export default settingsRoutes;

View File

@@ -106,7 +106,7 @@ tvRoutes.get('/:id/ratings', async (req, res, next) => {
const rtratings = await rtapi.getTVRatings(
tv.name,
Number(tv.first_air_date.slice(0, 4))
tv.first_air_date ? Number(tv.first_air_date.slice(0, 4)) : undefined
);
if (!rtratings) {

View File

@@ -1,8 +1,10 @@
import { Router } from 'express';
import { getRepository } from 'typeorm';
import PlexTvAPI from '../api/plextv';
import { MediaRequest } from '../entity/MediaRequest';
import { User } from '../entity/User';
import { hasPermission, Permission } from '../lib/permissions';
import { getSettings } from '../lib/settings';
import logger from '../logger';
const router = Router();
@@ -142,4 +144,51 @@ router.delete<{ id: string }>('/:id', async (req, res, next) => {
}
});
router.post('/import-from-plex', async (req, res, next) => {
try {
const settings = getSettings();
const userRepository = getRepository(User);
// taken from auth.ts
const mainUser = await userRepository.findOneOrFail({
select: ['id', 'plexToken'],
order: { id: 'ASC' },
});
const mainPlexTv = new PlexTvAPI(mainUser.plexToken ?? '');
const plexUsersResponse = await mainPlexTv.getUsers();
const createdUsers: User[] = [];
for (const rawUser of plexUsersResponse.MediaContainer.User) {
const account = rawUser.$;
const user = await userRepository.findOne({
where: { plexId: account.id },
});
if (user) {
// Update the users avatar with their plex thumbnail (incase it changed)
user.avatar = account.thumb;
user.email = account.email;
user.username = account.username;
await userRepository.save(user);
} else {
// Check to make sure it's a real account
if (account.email && account.username) {
const newUser = new User({
username: account.username,
email: account.email,
permissions: settings.main.defaultPermissions,
plexId: parseInt(account.id),
plexToken: '',
avatar: account.thumb,
});
await userRepository.save(newUser);
createdUsers.push(newUser);
}
}
}
return res.status(201).json(User.filterMany(createdUsers));
} catch (e) {
next({ status: 500, message: e.message });
}
});
export default router;

View File

@@ -47,7 +47,7 @@ div(role='article' aria-roledescription='email' aria-label='' lang='en')
padding-bottom: 25px;\
text-align: center;\
')
a(href='https://example.com' style='\
a(href=actionUrl style='\
text-shadow: 0 1px 0 #ffffff;\
font-weight: 700;\
font-size: 16px;\

View File

@@ -0,0 +1,96 @@
doctype html
head
meta(charset='utf-8')
meta(name='x-apple-disable-message-reformatting')
meta(http-equiv='x-ua-compatible' content='ie=edge')
meta(name='viewport' content='width=device-width, initial-scale=1')
meta(name='format-detection' content='telephone=no, date=no, address=no, email=no')
link(href='https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&amp;display=swap' rel='stylesheet' media='screen')
//if mso
xml
o:officedocumentsettings
o:pixelsperinch 96
style.
td,
th,
div,
p,
a,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Segoe UI', sans-serif;
mso-line-height-rule: exactly;
}
style.
@media (max-width: 600px) {
.sm-w-full {
width: 100% !important;
}
}
div(role='article' aria-roledescription='email' aria-label='' lang='en')
table(style="\
background-color: #f2f4f6;\
font-family: 'Nunito Sans', -apple-system, 'Segoe UI', sans-serif;\
width: 100%;\
" width='100%' bgcolor='#f2f4f6' cellpadding='0' cellspacing='0' role='presentation')
tr
td(align='center')
table(style='width: 100%' width='100%' cellpadding='0' cellspacing='0' role='presentation')
tr
td(align='center' style='\
font-size: 16px;\
padding-top: 25px;\
padding-bottom: 25px;\
text-align: center;\
')
a(href=actionUrl style='\
text-shadow: 0 1px 0 #ffffff;\
font-weight: 700;\
font-size: 16px;\
color: #a8aaaf;\
text-decoration: none;\
')
| Overseerr
tr
td(style='width: 100%' width='100%')
table.sm-w-full(align='center' style='\
background-color: #ffffff;\
margin-left: auto;\
margin-right: auto;\
width: 570px;\
' width='570' bgcolor='#ffffff' cellpadding='0' cellspacing='0' role='presentation')
tr
td(style='padding: 45px')
div(style='font-size: 16px')
| #{body}
p(style='\
font-size: 13px;\
line-height: 24px;\
margin-top: 6px;\
margin-bottom: 20px;\
color: #51545e;\
')
a(href=actionUrl style='color: #3869d4') Open Overseerr
tr
td
table.sm-w-full(align='center' style='\
margin-left: auto;\
margin-right: auto;\
text-align: center;\
width: 570px;\
' width='570' cellpadding='0' cellspacing='0' role='presentation')
tr
td(align='center' style='font-size: 16px; padding: 45px')
p(style='\
font-size: 13px;\
line-height: 24px;\
margin-top: 6px;\
margin-bottom: 20px;\
text-align: center;\
color: #a8aaaf;\
')
| Overseerr.

View File

@@ -0,0 +1 @@
= `Test Notification - Overseerr`

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 575 289.83"><defs><path d="M575 24.91C573.44 12.15 563.97 1.98 551.91 0C499.05 0 76.18 0 23.32 0C10.11 2.17 0 14.16 0 28.61C0 51.84 0 237.64 0 260.86C0 276.86 12.37 289.83 27.64 289.83C79.63 289.83 495.6 289.83 547.59 289.83C561.65 289.83 573.26 278.82 575 264.57C575 216.64 575 48.87 575 24.91Z" id="d1pwhf9wy2"></path><path d="M69.35 58.24L114.98 58.24L114.98 233.89L69.35 233.89L69.35 58.24Z" id="g5jjnq26yS"></path><path d="M201.2 139.15C197.28 112.38 195.1 97.5 194.67 94.53C192.76 80.2 190.94 67.73 189.2 57.09C185.25 57.09 165.54 57.09 130.04 57.09L130.04 232.74L170.01 232.74L170.15 116.76L186.97 232.74L215.44 232.74L231.39 114.18L231.54 232.74L271.38 232.74L271.38 57.09L211.77 57.09L201.2 139.15Z" id="i3Prh1JpXt"></path><path d="M346.71 93.63C347.21 95.87 347.47 100.95 347.47 108.89C347.47 115.7 347.47 170.18 347.47 176.99C347.47 188.68 346.71 195.84 345.2 198.48C343.68 201.12 339.64 202.43 333.09 202.43C333.09 190.9 333.09 98.66 333.09 87.13C338.06 87.13 341.45 87.66 343.25 88.7C345.05 89.75 346.21 91.39 346.71 93.63ZM367.32 230.95C372.75 229.76 377.31 227.66 381.01 224.67C384.7 221.67 387.29 217.52 388.77 212.21C390.26 206.91 391.14 196.38 391.14 180.63C391.14 174.47 391.14 125.12 391.14 118.95C391.14 102.33 390.49 91.19 389.48 85.53C388.46 79.86 385.93 74.71 381.88 70.09C377.82 65.47 371.9 62.15 364.12 60.13C356.33 58.11 343.63 57.09 321.54 57.09C319.27 57.09 307.93 57.09 287.5 57.09L287.5 232.74L342.78 232.74C355.52 232.34 363.7 231.75 367.32 230.95Z" id="a4ov9rRGQm"></path><path d="M464.76 204.7C463.92 206.93 460.24 208.06 457.46 208.06C454.74 208.06 452.93 206.98 452.01 204.81C451.09 202.65 450.64 197.72 450.64 190C450.64 185.36 450.64 148.22 450.64 143.58C450.64 135.58 451.04 130.59 451.85 128.6C452.65 126.63 454.41 125.63 457.13 125.63C459.91 125.63 463.64 126.76 464.6 129.03C465.55 131.3 466.03 136.15 466.03 143.58C466.03 146.58 466.03 161.58 466.03 188.59C465.74 197.84 465.32 203.21 464.76 204.7ZM406.68 231.21L447.76 231.21C449.47 224.5 450.41 220.77 450.6 220.02C454.32 224.52 458.41 227.9 462.9 230.14C467.37 232.39 474.06 233.51 479.24 233.51C486.45 233.51 492.67 231.62 497.92 227.83C503.16 224.05 506.5 219.57 507.92 214.42C509.34 209.26 510.05 201.42 510.05 190.88C510.05 185.95 510.05 146.53 510.05 141.6C510.05 131 509.81 124.08 509.34 120.83C508.87 117.58 507.47 114.27 505.14 110.88C502.81 107.49 499.42 104.86 494.98 102.98C490.54 101.1 485.3 100.16 479.26 100.16C474.01 100.16 467.29 101.21 462.81 103.28C458.34 105.35 454.28 108.49 450.64 112.7C450.64 108.89 450.64 89.85 450.64 55.56L406.68 55.56L406.68 231.21Z" id="fk968BpsX"></path></defs><g><g><g><use xlink:href="#d1pwhf9wy2" opacity="1" fill="#f6c700" fill-opacity="1"></use><g><use xlink:href="#d1pwhf9wy2" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#g5jjnq26yS" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#g5jjnq26yS" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#i3Prh1JpXt" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#i3Prh1JpXt" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4ov9rRGQm" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#a4ov9rRGQm" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#fk968BpsX" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#fk968BpsX" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 185.04 133.4"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" y1="66.7" x2="185.04" y2="66.7" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#90cea1"/><stop offset="0.56" stop-color="#3cbec9"/><stop offset="1" stop-color="#00b3e5"/></linearGradient></defs><title>Asset 4</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M51.06,66.7h0A17.67,17.67,0,0,1,68.73,49h-.1A17.67,17.67,0,0,1,86.3,66.7h0A17.67,17.67,0,0,1,68.63,84.37h.1A17.67,17.67,0,0,1,51.06,66.7Zm82.67-31.33h32.9A17.67,17.67,0,0,0,184.3,17.7h0A17.67,17.67,0,0,0,166.63,0h-32.9A17.67,17.67,0,0,0,116.06,17.7h0A17.67,17.67,0,0,0,133.73,35.37Zm-113,98h63.9A17.67,17.67,0,0,0,102.3,115.7h0A17.67,17.67,0,0,0,84.63,98H20.73A17.67,17.67,0,0,0,3.06,115.7h0A17.67,17.67,0,0,0,20.73,133.37Zm83.92-49h6.25L125.5,49h-8.35l-8.9,23.2h-.1L99.4,49H90.5Zm32.45,0h7.8V49h-7.8Zm22.2,0h24.95V77.2H167.1V70h15.35V62.8H167.1V56.2h16.25V49h-24ZM10.1,35.4h7.8V6.9H28V0H0V6.9H10.1ZM39,35.4h7.8V20.1H61.9V35.4h7.8V0H61.9V13.2H46.75V0H39Zm41.25,0h25V28.2H88V21h15.35V13.8H88V7.2h16.25V0h-24Zm-79,49H9V57.25h.1l9,27.15H24l9.3-27.15h.1V84.4h7.8V49H29.45l-8.2,23.1h-.1L13,49H1.2Zm112.09,49H126a24.59,24.59,0,0,0,7.56-1.15,19.52,19.52,0,0,0,6.35-3.37,16.37,16.37,0,0,0,4.37-5.5A16.91,16.91,0,0,0,146,115.8a18.5,18.5,0,0,0-1.68-8.25,15.1,15.1,0,0,0-4.52-5.53A18.55,18.55,0,0,0,133.07,99,33.54,33.54,0,0,0,125,98H113.29Zm7.81-28.2h4.6a17.43,17.43,0,0,1,4.67.62,11.68,11.68,0,0,1,3.88,1.88,9,9,0,0,1,2.62,3.18,9.87,9.87,0,0,1,1,4.52,11.92,11.92,0,0,1-1,5.08,8.69,8.69,0,0,1-2.67,3.34,10.87,10.87,0,0,1-4,1.83,21.57,21.57,0,0,1-5,.55H121.1Zm36.14,28.2h14.5a23.11,23.11,0,0,0,4.73-.5,13.38,13.38,0,0,0,4.27-1.65,9.42,9.42,0,0,0,3.1-3,8.52,8.52,0,0,0,1.2-4.68,9.16,9.16,0,0,0-.55-3.2,7.79,7.79,0,0,0-1.57-2.62,8.38,8.38,0,0,0-2.45-1.85,10,10,0,0,0-3.18-1v-.1a9.28,9.28,0,0,0,4.43-2.82,7.42,7.42,0,0,0,1.67-5,8.34,8.34,0,0,0-1.15-4.65,7.88,7.88,0,0,0-3-2.73,12.9,12.9,0,0,0-4.17-1.3,34.42,34.42,0,0,0-4.63-.32h-13.2Zm7.8-28.8h5.3a10.79,10.79,0,0,1,1.85.17,5.77,5.77,0,0,1,1.7.58,3.33,3.33,0,0,1,1.23,1.13,3.22,3.22,0,0,1,.47,1.82,3.63,3.63,0,0,1-.42,1.8,3.34,3.34,0,0,1-1.13,1.2,4.78,4.78,0,0,1-1.57.65,8.16,8.16,0,0,1-1.78.2H165Zm0,14.15h5.9a15.12,15.12,0,0,1,2.05.15,7.83,7.83,0,0,1,2,.55,4,4,0,0,1,1.58,1.17,3.13,3.13,0,0,1,.62,2,3.71,3.71,0,0,1-.47,1.95,4,4,0,0,1-1.23,1.3,4.78,4.78,0,0,1-1.67.7,8.91,8.91,0,0,1-1.83.2h-7Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,36 @@
import React from 'react';
interface AlertProps {
title: string;
type?: 'warning';
}
const Alert: React.FC<AlertProps> = ({ title, children }) => {
return (
<div className="rounded-md bg-yellow-600 p-4 mb-8">
<div className="flex">
<div className="flex-shrink-0">
<svg
className="h-5 w-5 text-yellow-200"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-200">{title}</h3>
<div className="mt-2 text-sm text-yellow-300">{children}</div>
</div>
</div>
</div>
);
};
export default Alert;

View File

@@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import Transition from '../../Transition';
@@ -49,7 +48,7 @@ const SlideOver: React.FC<SlideOverProps> = ({
className={`z-50 fixed inset-0 overflow-hidden bg-opacity-50 bg-gray-800`}
>
<div className="absolute inset-0 overflow-hidden">
<section className="absolute inset-y-0 right-0 pl-10 max-w-full flex">
<section className="absolute inset-y-0 right-0 flex max-w-full pl-10">
<Transition
show={show}
appear
@@ -61,20 +60,20 @@ const SlideOver: React.FC<SlideOverProps> = ({
leaveTo="translate-x-full"
>
<div className="w-screen max-w-md" ref={slideoverRef}>
<div className="h-full flex flex-col bg-gray-700 shadow-xl overflow-y-scroll">
<header className="space-y-1 py-6 px-4 bg-indigo-600 sm:px-6">
<div className="flex flex-col h-full overflow-y-scroll bg-gray-700 shadow-xl">
<header className="px-4 py-6 space-y-1 bg-indigo-600 sm:px-6">
<div className="flex items-center justify-between space-x-3">
<h2 className="text-lg leading-7 font-medium text-white">
<h2 className="text-lg font-medium leading-7 text-white">
{title}
</h2>
<div className="h-7 flex items-center">
<div className="flex items-center h-7">
<button
aria-label="Close panel"
className="text-indigo-200 hover:text-white transition ease-in-out duration-150"
className="text-indigo-200 transition duration-150 ease-in-out hover:text-white"
onClick={() => onClose()}
>
<svg
className="h-6 w-6"
className="w-6 h-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
@@ -98,7 +97,7 @@ const SlideOver: React.FC<SlideOverProps> = ({
</div>
)}
</header>
<div className="relative flex-1 py-6 px-4 sm:px-6 text-white">
<div className="relative flex-1 px-4 py-6 text-white sm:px-6">
{children}
</div>
</div>

View File

@@ -0,0 +1,55 @@
import React from 'react';
import TmdbLogo from '../../assets/services/tmdb.svg';
import ImdbLogo from '../../assets/services/imdb.svg';
import RTLogo from '../../assets/services/rt.svg';
interface ExternalLinkBlockProps {
mediaType: 'movie' | 'tv';
imdbId?: string;
tmdbId?: number;
rtUrl?: string;
}
const ExternalLinkBlock: React.FC<ExternalLinkBlockProps> = ({
imdbId,
tmdbId,
rtUrl,
mediaType,
}) => {
return (
<div className="flex justify-end items-center">
{tmdbId && (
<a
href={`https://www.themoviedb.org/${mediaType}/${tmdbId}`}
className="w-8 mx-2 opacity-50 hover:opacity-100 transition duration-300"
target="_blank"
rel="noreferrer"
>
<TmdbLogo />
</a>
)}
{imdbId && (
<a
href={`https://www.imdb.com/title/${imdbId}`}
className="w-8 mx-2 opacity-50 hover:opacity-100 transition duration-300"
target="_blank"
rel="noreferrer"
>
<ImdbLogo />
</a>
)}
{rtUrl && (
<a
href={`${rtUrl}`}
className="w-14 mx-2 opacity-50 hover:opacity-100 transition duration-300"
target="_blank"
rel="noreferrer"
>
<RTLogo />
</a>
)}
</div>
);
};
export default ExternalLinkBlock;

View File

@@ -49,6 +49,22 @@ const availableLanguages: AvailableLanguageObject = {
code: 'es',
display: 'Spanish',
},
it: {
code: 'it',
display: 'Italian',
},
'pt-BR': {
code: 'pt-BR',
display: 'Portuguese (Brazil)',
},
sr: {
code: 'sr',
display: 'Serbian',
},
sv: {
code: 'sv',
display: 'Swedish',
},
};
const LanguagePicker: React.FC = () => {
@@ -58,7 +74,7 @@ const LanguagePicker: React.FC = () => {
useClickOutside(dropdownRef, () => setDropdownOpen(false));
return (
<div className="ml-3 relative">
<div className="relative">
<div>
<button
className="p-1 text-gray-400 rounded-full hover:bg-gray-500 hover:text-white focus:outline-none focus:ring focus:text-white"

View File

@@ -27,7 +27,7 @@ const SearchInput: React.FC = () => {
</div>
<input
id="search_field"
className="block w-full h-full pl-8 pr-3 py-2 rounded-md border-transparent focus:border-transparent bg-gray-600 text-white placeholder-gray-300 focus:outline-none focus:ring-0 focus:placeholder-gray-400 sm:text-base"
className="block w-full h-full pl-8 pr-1 py-2 rounded-md border-transparent focus:border-transparent bg-gray-600 text-white placeholder-gray-300 focus:outline-none focus:ring-0 focus:placeholder-gray-400 sm:text-base"
placeholder={intl.formatMessage(messages.searchPlaceholder)}
type="search"
value={searchValue}

View File

@@ -44,7 +44,7 @@ const Layout: React.FC = ({ children }) => {
</button>
<div className="flex-1 px-4 flex justify-between">
<SearchInput />
<div className="ml-4 flex items-center md:ml-6">
<div className="flex items-center md:ml-6">
<LanguagePicker />
<UserDropdown />
</div>

View File

@@ -6,6 +6,7 @@ import { useRouter } from 'next/dist/client/router';
import ImageFader from '../Common/ImageFader';
import { defineMessages, FormattedMessage } from 'react-intl';
import Transition from '../Transition';
import LanguagePicker from '../Layout/LanguagePicker';
const messages = defineMessages({
signinplex: 'Sign in to continue',
@@ -59,7 +60,10 @@ const Login: React.FC = () => {
'/images/rotate4.jpg',
]}
/>
<div className="px-4 sm:px-2 md:px-0 sm:mx-auto sm:w-full sm:max-w-md relative z-50">
<div className="absolute top-4 right-4 z-50">
<LanguagePicker />
</div>
<div className="px-4 sm:px-2 md:px-0 sm:mx-auto sm:w-full sm:max-w-md relative z-40">
<img
src="/logo.png"
className="mx-auto max-h-32 w-auto"

View File

@@ -42,11 +42,11 @@ const MovieCast: React.FC = () => {
{intl.formatMessage(messages.fullcast)}
</Header>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.cast.map((person) => {
{data?.credits.cast.map((person, index) => {
return (
<li
key={person.id}
className="col-span-1 flex flex-col text-center items-center"
key={`cast-${person.id}-${index}`}
className="flex flex-col items-center col-span-1 text-center"
>
<PersonCard
name={person.name}

View File

@@ -0,0 +1,66 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useContext } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import { MovieDetails } from '../../../../server/models/Movie';
import { LanguageContext } from '../../../context/LanguageContext';
import Error from '../../../pages/_error';
import Header from '../../Common/Header';
import LoadingSpinner from '../../Common/LoadingSpinner';
import PersonCard from '../../PersonCard';
const messages = defineMessages({
fullcrew: 'Full Crew',
});
const MovieCrew: React.FC = () => {
const router = useRouter();
const intl = useIntl();
const { locale } = useContext(LanguageContext);
const { data, error } = useSWR<MovieDetails>(
`/api/v1/movie/${router.query.movieId}?language=${locale}`
);
if (!data && !error) {
return <LoadingSpinner />;
}
if (!data) {
return <Error statusCode={404} />;
}
return (
<>
<Header
subtext={
<Link href={`/movie/${data.id}`}>
<a className="hover:underline">{data.title}</a>
</Link>
}
>
{intl.formatMessage(messages.fullcrew)}
</Header>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.crew.map((person, index) => {
return (
<li
key={`crew-${person.id}-${index}`}
className="flex flex-col items-center col-span-1 text-center"
>
<PersonCard
name={person.name}
personId={person.id}
subName={person.job}
profilePath={person.profilePath}
canExpand
/>
</li>
);
})}
</ul>
</>
);
};
export default MovieCrew;

View File

@@ -1,4 +1,4 @@
import React, { useState, useContext } from 'react';
import React, { useState, useContext, useMemo } from 'react';
import {
FormattedMessage,
defineMessages,
@@ -37,6 +37,8 @@ import type { RTRating } from '../../../server/api/rottentomatoes';
import Error from '../../pages/_error';
import Head from 'next/head';
import globalMessages from '../../i18n/globalMessages';
import ExternalLinkBlock from '../ExternalLinkBlock';
import { sortCrewPriority } from '../../utils/creditHelpers';
const messages = defineMessages({
releasedate: 'Release Date',
@@ -66,6 +68,7 @@ const messages = defineMessages({
approve: 'Approve',
decline: 'Decline',
studio: 'Studio',
viewfullcrew: 'View Full Crew',
});
interface MovieDetailsProps {
@@ -102,6 +105,10 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
`/api/v1/movie/${router.query.movieId}/ratings`
);
const sortedCrew = useMemo(() => sortCrewPriority(data?.credits.crew ?? []), [
data,
]);
if (!data && !error) {
return <LoadingSpinner />;
}
@@ -133,7 +140,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
return (
<div
className="bg-cover bg-center -mx-4 -mt-2 px-4 sm:px-8 pt-4 "
className="px-4 pt-4 -mx-4 -mt-2 bg-center bg-cover sm:px-8 "
style={{
height: 493,
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
@@ -158,21 +165,21 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
onClose={() => setShowManager(false)}
subText={data.title}
>
<h3 className="text-xl mb-2">
<h3 className="mb-2 text-xl">
{intl.formatMessage(messages.manageModalRequests)}
</h3>
<div className="bg-gray-600 shadow overflow-hidden rounded-md">
<div className="overflow-hidden bg-gray-600 rounded-md shadow">
<ul>
{data.mediaInfo?.requests?.map((request) => (
<li
key={`manage-request-${request.id}`}
className="border-b last:border-b-0 border-gray-700"
className="border-b border-gray-700 last:border-b-0"
>
<RequestBlock request={request} onUpdate={() => revalidate()} />
</li>
))}
{(data.mediaInfo?.requests ?? []).length === 0 && (
<li className="text-center py-4 text-gray-400">
<li className="py-4 text-center text-gray-400">
{intl.formatMessage(messages.manageModalNoRequests)}
</li>
)}
@@ -187,21 +194,21 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
>
{intl.formatMessage(messages.manageModalClearMedia)}
</Button>
<div className="text-sm text-gray-400 mt-2">
<div className="mt-2 text-sm text-gray-400">
{intl.formatMessage(messages.manageModalClearMediaWarning)}
</div>
</div>
)}
</SlideOver>
<div className="flex flex-col items-center md:flex-row md:items-end pt-4">
<div className="md:mr-4 flex-shrink-0">
<div className="flex flex-col items-center pt-4 md:flex-row md:items-end">
<div className="flex-shrink-0 md:mr-4">
<img
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
alt=""
className="rounded md:rounded-lg shadow md:shadow-2xl w-32 md:w-52"
className="w-32 rounded shadow md:rounded-lg md:shadow-2xl md:w-52"
/>
</div>
<div className="text-white flex flex-col md:mr-4 mt-4 md:mt-0 text-center md:text-left">
<div className="flex flex-col mt-4 text-center text-white md:mr-4 md:mt-0 md:text-left">
<div className="mb-2">
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
<Badge badgeType="success">
@@ -223,7 +230,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
{data.title}{' '}
<span className="text-2xl">({data.releaseDate.slice(0, 4)})</span>
</h1>
<span className="text-xs md:text-base mt-1 md:mt-0">
<span className="mt-1 text-xs md:text-base md:mt-0">
{(data.runtime ?? 0) > 0 && (
<>
<FormattedMessage
@@ -236,7 +243,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
{data.genres.map((g) => g.name).join(', ')}
</span>
</div>
<div className="flex-1 flex justify-end mt-4 md:mt-0">
<div className="flex justify-end flex-1 mt-4 md:mt-0">
{(!data.mediaInfo ||
data.mediaInfo?.status === MediaStatus.UNKNOWN) && (
<Button
@@ -381,7 +388,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
)}
</div>
</div>
<div className="flex pt-8 text-white flex-col md:flex-row pb-4">
<div className="flex flex-col pt-8 pb-4 text-white md:flex-row">
<div className="flex-1 md:mr-8">
<h2 className="text-xl md:text-2xl">
<FormattedMessage {...messages.overview} />
@@ -391,45 +398,85 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
? data.overview
: intl.formatMessage(messages.overviewunavailable)}
</p>
<ul className="grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3">
{sortedCrew.slice(0, 6).map((person) => (
<li
className="flex flex-col col-span-1"
key={`crew-${person.job}-${person.id}`}
>
<span className="font-bold">{person.job}</span>
<Link href={`/person/${person.id}`}>
<a className="text-gray-400 transition duration-300 hover:text-underline hover:text-gray-100">
{person.name}
</a>
</Link>
</li>
))}
</ul>
{sortedCrew.length > 0 && (
<div className="flex justify-end mt-4">
<Link href={`/movie/${data.id}/crew`}>
<a className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100">
<span>{intl.formatMessage(messages.viewfullcrew)}</span>
<svg
className="inline-block w-5 h-5 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</a>
</Link>
</div>
)}
</div>
<div className="w-full md:w-80 mt-8 md:mt-0">
<div className="bg-gray-900 rounded-lg shadow border border-gray-800">
<div className="w-full mt-8 md:w-80 md:mt-0">
<div className="bg-gray-900 border border-gray-800 rounded-lg shadow">
{(data.voteCount > 0 || ratingData) && (
<div className="flex px-4 py-2 border-b border-gray-800 last:border-b-0 items-center justify-center">
{ratingData?.criticsRating && (
<>
<span className="text-sm">
{ratingData.criticsRating === 'Rotten' ? (
<RTRotten className="w-6 mr-1" />
) : (
<RTFresh className="w-6 mr-1" />
)}
</span>
<span className="text-gray-400 text-sm mr-4 last:mr-0">
{ratingData.criticsScore}%
</span>
</>
)}
{ratingData?.audienceRating && (
<>
<span className="text-sm">
{ratingData.audienceRating === 'Spilled' ? (
<RTAudRotten className="w-6 mr-1" />
) : (
<RTAudFresh className="w-6 mr-1" />
)}
</span>
<span className="text-gray-400 text-sm mr-4 last:mr-0">
{ratingData.audienceScore}%
</span>
</>
)}
<div className="flex items-center justify-center px-4 py-2 border-b border-gray-800 last:border-b-0">
{ratingData?.criticsRating &&
(ratingData?.criticsScore ?? 0) > 0 && (
<>
<span className="text-sm">
{ratingData.criticsRating === 'Rotten' ? (
<RTRotten className="w-6 mr-1" />
) : (
<RTFresh className="w-6 mr-1" />
)}
</span>
<span className="mr-4 text-sm text-gray-400 last:mr-0">
{ratingData.criticsScore}%
</span>
</>
)}
{ratingData?.audienceRating &&
(ratingData?.audienceScore ?? 0) > 0 && (
<>
<span className="text-sm">
{ratingData.audienceRating === 'Spilled' ? (
<RTAudRotten className="w-6 mr-1" />
) : (
<RTAudFresh className="w-6 mr-1" />
)}
</span>
<span className="mr-4 text-sm text-gray-400 last:mr-0">
{ratingData.audienceScore}%
</span>
</>
)}
{data.voteCount > 0 && (
<>
<span className="text-sm">
<TmdbLogo className="w-6 mr-2" />
</span>
<span className="text-gray-400 text-sm">
<span className="text-sm text-gray-400">
{data.voteAverage}/10
</span>
</>
@@ -440,7 +487,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<span className="text-sm">
<FormattedMessage {...messages.releasedate} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
<FormattedDate
value={new Date(data.releaseDate)}
year="numeric"
@@ -453,7 +500,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<span className="text-sm">
<FormattedMessage {...messages.status} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{data.status}
</span>
</div>
@@ -462,7 +509,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<span className="text-sm">
<FormattedMessage {...messages.revenue} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
<FormattedNumber
currency="USD"
style="currency"
@@ -476,7 +523,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<span className="text-sm">
<FormattedMessage {...messages.budget} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
<FormattedNumber
currency="USD"
style="currency"
@@ -492,7 +539,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<span className="text-sm">
<FormattedMessage {...messages.originallanguage} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{
data.spokenLanguages.find(
(lng) => lng.iso_639_1 === data.originalLanguage
@@ -506,18 +553,26 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
<span className="text-sm">
<FormattedMessage {...messages.studio} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{data.productionCompanies[0]?.name}
</span>
</div>
)}
</div>
<div className="mt-4">
<ExternalLinkBlock
mediaType="movie"
tmdbId={data.id}
imdbId={data.externalIds.imdbId}
rtUrl={ratingData?.url}
/>
</div>
</div>
</div>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<Link href="/movie/[movieId]/cast" as={`/movie/${data.id}/cast`}>
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>
<FormattedMessage {...messages.cast} />
</span>
@@ -555,13 +610,13 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
/>
{(recommended?.results ?? []).length > 0 && (
<>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<Link
href="/movie/[movieId]/recommendations"
as={`/movie/${data.id}/recommendations`}
>
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>
<FormattedMessage {...messages.recommendations} />
</span>
@@ -605,13 +660,13 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
)}
{(similar?.results ?? []).length > 0 && (
<>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<Link
href="/movie/[movieId]/similar"
as={`/movie/${data.id}/similar`}
>
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>
<FormattedMessage {...messages.similar} />
</span>

View File

@@ -25,18 +25,19 @@ const PersonCard: React.FC<PersonCardProps> = ({
} bg-gray-600 rounded-lg text-white shadow-lg hover:bg-gray-500 transition ease-in-out duration-150 cursor-pointer`}
>
<div style={{ paddingBottom: '150%' }}>
<div className="absolute inset-0 flex flex-col items-center justify-center">
<div className="absolute inset-0 flex flex-col items-center w-full h-full p-2">
{profilePath && (
<div
style={{
backgroundImage: `url(https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath})`,
}}
className="rounded-full w-28 h-28 md:w-32 md:h-32 bg-cover bg-center mb-6"
/>
<div className="relative flex justify-center w-full mt-2 mb-4 h-1/2">
<img
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath}`}
className="object-cover w-3/4 h-full bg-center bg-cover rounded-full"
alt=""
/>
</div>
)}
{!profilePath && (
<svg
className="w-28 h-28 md:w-32 md:h-32 mb-6"
className="mb-6 w-28 h-28 md:w-32 md:h-32"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
@@ -48,12 +49,21 @@ const PersonCard: React.FC<PersonCardProps> = ({
/>
</svg>
)}
<div className="whitespace-normal text-center">{name}</div>
<div className="w-full text-center truncate">{name}</div>
{subName && (
<div className="whitespace-normal text-center text-sm text-gray-300">
<div
className="overflow-hidden text-sm text-center text-gray-300 whitespace-normal"
style={{
WebkitLineClamp: 2,
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
}}
>
{subName}
</div>
)}
<div className="absolute bottom-0 left-0 right-0 h-12 rounded-b-lg bg-gradient-to-t from-gray-600" />
</div>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { useRouter } from 'next/router';
import React, { useContext } from 'react';
import React, { useContext, useState } from 'react';
import useSWR from 'swr';
import type { PersonDetail } from '../../../server/models/Person';
import type { PersonCombinedCreditsResponse } from '../../../server/interfaces/api/personInterfaces';
@@ -11,6 +11,7 @@ import { LanguageContext } from '../../context/LanguageContext';
const messages = defineMessages({
appearsin: 'Appears in',
crewmember: 'Crew Member',
ascharacter: 'as {character}',
nobiography: 'No biography available.',
});
@@ -22,6 +23,7 @@ const PersonDetails: React.FC = () => {
const { data, error } = useSWR<PersonDetail>(
`/api/v1/person/${router.query.personId}`
);
const [showBio, setShowBio] = useState(false);
const {
data: combinedCredits,
@@ -53,77 +55,151 @@ const PersonDetails: React.FC = () => {
return 1;
});
const sortedCrew = combinedCredits?.crew.sort((a, b) => {
const aDate =
a.mediaType === 'movie'
? a.releaseDate?.slice(0, 4) ?? 0
: a.firstAirDate?.slice(0, 4) ?? 0;
const bDate =
b.mediaType === 'movie'
? b.releaseDate?.slice(0, 4) ?? 0
: b.firstAirDate?.slice(0, 4) ?? 0;
if (aDate > bDate) {
return -1;
}
return 1;
});
const isLoading = !combinedCredits && !errorCombinedCredits;
return (
<>
<div className="flex mt-8 mb-8 flex-col md:flex-row items-center md:items-start">
<div className="flex flex-col items-center mt-8 mb-8 md:flex-row md:items-start">
{data.profilePath && (
<div
style={{
backgroundImage: `url(https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.profilePath})`,
}}
className="rounded-full w-36 h-36 md:w-44 md:h-44 bg-cover bg-center mb-6 md:mb-0 mr-0 md:mr-6 flex-shrink-0"
className="flex-shrink-0 mb-6 mr-0 bg-center bg-cover rounded-full w-36 h-36 md:w-44 md:h-44 md:mb-0 md:mr-6"
/>
)}
<div className="text-gray-300 text-center md:text-left">
<h1 className="text-3xl md:text-4xl text-white mb-4">{data.name}</h1>
<div>
{data.biography
? data.biography
: intl.formatMessage(messages.nobiography)}
</div>
</div>
</div>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="flex-1 min-w-0">
<div className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<span>{intl.formatMessage(messages.appearsin)}</span>
</div>
</div>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{sortedCast?.map((media) => {
return (
<li
key={`list-cast-item-${media.id}`}
className="col-span-1 flex flex-col text-center items-center"
<div className="text-center text-gray-300 md:text-left">
<h1 className="mb-4 text-3xl text-white md:text-4xl">{data.name}</h1>
<div className="relative">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
className={`transition-max-height duration-300 ${
showBio
? 'overflow-visible extra-max-height'
: 'overflow-hidden max-h-44'
}`}
onClick={() => setShowBio((show) => !show)}
role="button"
tabIndex={-1}
>
<TitleCard
id={media.id}
title={media.mediaType === 'movie' ? media.title : media.name}
userScore={media.voteAverage}
year={
media.mediaType === 'movie'
? media.releaseDate
: media.firstAirDate
}
image={media.posterPath}
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
canExpand
/>
{media.character && (
<div className="mt-2 text-gray-300 text-xs truncate w-36 sm:w-36 md:w-44 text-center">
{intl.formatMessage(messages.ascharacter, {
character: media.character,
})}
</div>
<div className={showBio ? 'h-auto' : 'h-36'}>
{data.biography
? data.biography
: intl.formatMessage(messages.nobiography)}
</div>
{!showBio && (
<div className="absolute bottom-0 left-0 right-0 w-full h-8 bg-gradient-to-t from-gray-900" />
)}
</li>
);
})}
{isLoading &&
[...Array(20)].map((_item, i) => (
<li
key={`placeholder-${i}`}
className="col-span-1 flex flex-col text-center items-center"
>
<TitleCard.Placeholder canExpand />
</li>
))}
</ul>
</div>
</div>
</div>
</div>
{(sortedCast ?? []).length > 0 && (
<>
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>{intl.formatMessage(messages.appearsin)}</span>
</div>
</div>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{sortedCast?.map((media, index) => {
return (
<li
key={`list-cast-item-${media.id}-${index}`}
className="flex flex-col items-center col-span-1 text-center"
>
<TitleCard
id={media.id}
title={
media.mediaType === 'movie' ? media.title : media.name
}
userScore={media.voteAverage}
year={
media.mediaType === 'movie'
? media.releaseDate
: media.firstAirDate
}
image={media.posterPath}
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
canExpand
/>
{media.character && (
<div className="mt-2 text-xs text-center text-gray-300 truncate w-36 sm:w-36 md:w-44">
{intl.formatMessage(messages.ascharacter, {
character: media.character,
})}
</div>
)}
</li>
);
})}
</ul>
</>
)}
{(sortedCrew ?? []).length > 0 && (
<>
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>{intl.formatMessage(messages.crewmember)}</span>
</div>
</div>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{sortedCrew?.map((media, index) => {
return (
<li
key={`list-crew-item-${media.id}-${index}`}
className="flex flex-col items-center col-span-1 text-center"
>
<TitleCard
id={media.id}
title={
media.mediaType === 'movie' ? media.title : media.name
}
userScore={media.voteAverage}
year={
media.mediaType === 'movie'
? media.releaseDate
: media.firstAirDate
}
image={media.posterPath}
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
canExpand
/>
{media.job && (
<div className="mt-2 text-xs text-center text-gray-300 truncate w-36 sm:w-36 md:w-44">
{media.job}
</div>
)}
</li>
);
})}
</ul>
</>
)}
{isLoading && <LoadingSpinner />}
</>
);
};

View File

@@ -43,26 +43,30 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
return (
<div className="block">
<div className="px-4 py-4 sm:px-6 ">
<div className="px-4 py-4 sm:px-6">
<div className="flex items-center justify-between">
<div className="mr-6 flex items-center text-sm leading-5 text-gray-300">
<svg
className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-300"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clipRule="evenodd"
/>
</svg>
<span>{request.requestedBy.username}</span>
<div className="mr-6 flex-col items-center text-sm leading-5 text-gray-300 flex-1 min-w-0">
<div className="flex flex-nowrap mb-1 white">
<svg
className="min-w-0 flex-shrink-0 mr-1.5 h-5 w-5 text-gray-300"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clipRule="evenodd"
/>
</svg>
<span className="truncate w-40 md:w-auto">
{request.requestedBy.username}
</span>
</div>
{request.modifiedBy && (
<>
<div className="flex flex-nowrap">
<svg
className="flex-shrink-0 ml-2 mr-1.5 h-5 w-5 text-gray-300"
className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-300"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
@@ -74,8 +78,10 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
clipRule="evenodd"
/>
</svg>
<span>{request.modifiedBy?.username}</span>
</>
<span className="truncate w-40 md:w-auto">
{request.modifiedBy?.username}
</span>
</div>
)}
</div>
<div className="ml-2 flex-shrink-0 flex">
@@ -189,13 +195,18 @@ const RequestBlock: React.FC<RequestBlockProps> = ({ request, onUpdate }) => {
</div>
</div>
{(request.seasons ?? []).length > 0 && (
<div className="mt-2 text-sm flex items-center">
<span className="mr-2">{intl.formatMessage(messages.seasons)}</span>
{request.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>{season.seasonNumber}</Badge>
</span>
))}
<div className="mt-2 text-sm flex flex-col">
<div className="mb-2">{intl.formatMessage(messages.seasons)}</div>
<div>
{request.seasons.map((season) => (
<span
key={`season-${season.id}`}
className="mr-2 mb-1 inline-block"
>
<Badge>{season.seasonNumber}</Badge>
</span>
))}
</div>
</div>
)}
</div>

View File

@@ -4,7 +4,7 @@ import useSWR from 'swr';
import LoadingSpinner from '../../Common/LoadingSpinner';
import Button from '../../Common/Button';
import { defineMessages, useIntl } from 'react-intl';
import Axios from 'axios';
import axios from 'axios';
import * as Yup from 'yup';
import { useToasts } from 'react-toast-notifications';
@@ -17,6 +17,8 @@ const messages = defineMessages({
webhookUrlPlaceholder: 'Server Settings -> Integrations -> Webhooks',
discordsettingssaved: 'Discord notification settings saved!',
discordsettingsfailed: 'Discord notification settings failed to save.',
testsent: 'Test notification sent!',
test: 'Test',
});
const NotificationsDiscord: React.FC = () => {
@@ -46,7 +48,7 @@ const NotificationsDiscord: React.FC = () => {
validationSchema={NotificationsDiscordSchema}
onSubmit={async (values) => {
try {
await Axios.post('/api/v1/settings/notifications/discord', {
await axios.post('/api/v1/settings/notifications/discord', {
enabled: values.enabled,
types: values.types,
options: {
@@ -67,7 +69,22 @@ const NotificationsDiscord: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting }) => {
{({ errors, touched, isSubmitting, values, isValid }) => {
const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/discord/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
});
addToast(intl.formatMessage(messages.testsent), {
appearance: 'info',
autoDismiss: true,
});
};
return (
<Form>
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
@@ -112,11 +129,24 @@ const NotificationsDiscord: React.FC = () => {
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{intl.formatMessage(messages.test)}
</Button>
</span>
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting}
disabled={isSubmitting || !isValid}
>
{isSubmitting
? intl.formatMessage(messages.saving)

View File

@@ -4,7 +4,7 @@ import useSWR from 'swr';
import LoadingSpinner from '../../Common/LoadingSpinner';
import Button from '../../Common/Button';
import { defineMessages, useIntl } from 'react-intl';
import Axios from 'axios';
import axios from 'axios';
import * as Yup from 'yup';
import { useToasts } from 'react-toast-notifications';
@@ -23,6 +23,8 @@ const messages = defineMessages({
authPass: 'Auth Pass',
emailsettingssaved: 'Email notification settings saved!',
emailsettingsfailed: 'Email notification settings failed to save.',
test: 'Test',
testsent: 'Test notification sent!',
});
const NotificationsEmail: React.FC = () => {
@@ -63,7 +65,7 @@ const NotificationsEmail: React.FC = () => {
validationSchema={NotificationsDiscordSchema}
onSubmit={async (values) => {
try {
await Axios.post('/api/v1/settings/notifications/email', {
await axios.post('/api/v1/settings/notifications/email', {
enabled: values.enabled,
types: values.types,
options: {
@@ -89,7 +91,27 @@ const NotificationsEmail: React.FC = () => {
}
}}
>
{({ errors, touched, isSubmitting }) => {
{({ errors, touched, isSubmitting, values, isValid }) => {
const testSettings = async () => {
await axios.post('/api/v1/settings/notifications/email/test', {
enabled: true,
types: values.types,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.secure,
authUser: values.authUser,
authPass: values.authPass,
},
});
addToast(intl.formatMessage(messages.testsent), {
appearance: 'info',
autoDismiss: true,
});
};
return (
<Form>
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
@@ -228,11 +250,24 @@ const NotificationsEmail: React.FC = () => {
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="warning"
disabled={isSubmitting || !isValid}
onClick={(e) => {
e.preventDefault();
testSettings();
}}
>
{intl.formatMessage(messages.test)}
</Button>
</span>
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting}
disabled={isSubmitting || !isValid}
>
{isSubmitting
? intl.formatMessage(messages.saving)

View File

@@ -0,0 +1,186 @@
import React, { useState } from 'react';
import useSWR from 'swr';
import ReactMarkdown from 'react-markdown';
import LoadingSpinner from '../../../Common/LoadingSpinner';
import Alert from '../../../Common/Alert';
import Badge from '../../../Common/Badge';
import Button from '../../../Common/Button';
import Modal from '../../../Common/Modal';
import Transition from '../../../Transition';
import { defineMessages, useIntl } from 'react-intl';
import globalMessages from '../../../../i18n/globalMessages';
const messages = defineMessages({
releases: 'Releases',
releasedataMissing: 'Release data missing. Is GitHub down?',
versionChangelog: 'Version Changelog',
viewongithub: 'View on GitHub',
latestversion: 'Latest Version',
currentversion: 'Current Version',
viewchangelog: 'View Changelog',
runningDevelop: 'You are running a develop version of Overseerr!',
runningDevelopMessage:
'The changes in your version will not be available below. Please look at the <GithubLink>GitHub repository</GithubLink> for latest updates.',
});
const REPO_RELEASE_API =
'https://api.github.com/repos/sct/overseerr/releases?per_page=20';
interface GitHubRelease {
url: string;
assets_url: string;
upload_url: string;
html_url: string;
id: number;
node_id: string;
tag_name: string;
target_commitish: string;
name: string;
draft: boolean;
prerelease: boolean;
created_at: string;
published_at: string;
tarball_url: string;
zipball_url: string;
body: string;
}
interface ReleaseProps {
release: GitHubRelease;
isLatest: boolean;
currentVersion: string;
}
const Release: React.FC<ReleaseProps> = ({
currentVersion,
release,
isLatest,
}) => {
const intl = useIntl();
const [isModalOpen, setModalOpen] = useState(false);
return (
<div className="bg-gray-800 rounded-md flex flex-col sm:flex-row px-4 py-2">
<Transition
enter="opacity-0 transition duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
show={isModalOpen}
>
<Modal
onCancel={() => setModalOpen(false)}
iconSvg={
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
}
title={intl.formatMessage(messages.versionChangelog)}
cancelText={intl.formatMessage(globalMessages.close)}
okText={intl.formatMessage(messages.viewongithub)}
onOk={() => {
window.open(release.html_url, '_blank');
}}
>
<div className="prose">
<ReactMarkdown>{release.body}</ReactMarkdown>
</div>
</Modal>
</Transition>
<div className="flex mb-4 sm:mb-0 items-center justify-center sm:justify-start">
<span className="text-xl">{release.name}</span>
{isLatest && (
<span className="ml-2">
<Badge badgeType="primary">
{intl.formatMessage(messages.latestversion)}
</Badge>
</span>
)}
{release.name.includes(currentVersion) && (
<span className="ml-2">
<Badge badgeType="success">
{intl.formatMessage(messages.currentversion)}
</Badge>
</span>
)}
</div>
<div className="flex-1 text-center sm:text-right">
<Button buttonType="primary" onClick={() => setModalOpen(true)}>
{intl.formatMessage(messages.viewchangelog)}
</Button>
</div>
</div>
);
};
interface ReleasesProps {
currentVersion: string;
}
const Releases: React.FC<ReleasesProps> = ({ currentVersion }) => {
const intl = useIntl();
const { data, error } = useSWR<GitHubRelease[]>(REPO_RELEASE_API);
if (!data && !error) {
return <LoadingSpinner />;
}
if (!data) {
return (
<div className="text-gray-300">
{intl.formatMessage(messages.releasedataMissing)}
</div>
);
}
return (
<div>
<div className="text-xl pb-4 mb-4 border-b border-gray-800">
{intl.formatMessage(messages.releases)}
</div>
{currentVersion.startsWith('develop-') && (
<Alert title={intl.formatMessage(messages.runningDevelop)}>
{intl.formatMessage(messages.runningDevelopMessage, {
GithubLink: function GithubLink(msg) {
return (
<a
href="https://github.com/sct/overseerr"
target="_blank"
rel="noreferrer"
className="text-yellow-100 underline hover:text-white transition duration-300"
>
{msg}
</a>
);
},
})}
</Alert>
)}
{data?.map((release, index) => {
return (
<div key={`release-${release.id}`} className="mb-2">
<Release
release={release}
currentVersion={currentVersion}
isLatest={index === 0}
/>
</div>
);
})}
</div>
);
};
export default Releases;

View File

@@ -5,6 +5,7 @@ import List from '../../Common/List';
import LoadingSpinner from '../../Common/LoadingSpinner';
import { SettingsAboutResponse } from '../../../../server/interfaces/api/settingsInterfaces';
import { defineMessages, FormattedNumber, useIntl } from 'react-intl';
import Releases from './Releases';
const messages = defineMessages({
overseerrinformation: 'Overseerr Information',
@@ -14,6 +15,9 @@ const messages = defineMessages({
gettingsupport: 'Getting Support',
githubdiscussions: 'GitHub Discussions',
clickheretojoindiscord: 'Click here to join our Discord server.',
timezone: 'Timezone',
supportoverseerr: 'Support Overseerr',
helppaycoffee: 'Help pay for coffee',
});
const SettingsAbout: React.FC = () => {
@@ -22,16 +26,12 @@ const SettingsAbout: React.FC = () => {
'/api/v1/settings/about'
);
if (error) {
return <Error statusCode={500} />;
}
if (!data && !error) {
return <LoadingSpinner />;
}
if (!data) {
return <LoadingSpinner />;
return <Error statusCode={500} />;
}
return (
@@ -47,6 +47,11 @@ const SettingsAbout: React.FC = () => {
<List.Item title={intl.formatMessage(messages.totalrequests)}>
<FormattedNumber value={data.totalRequests} />
</List.Item>
{data.tz && (
<List.Item title={intl.formatMessage(messages.timezone)}>
{data.tz}
</List.Item>
)}
</List>
</div>
<div className="mb-8">
@@ -73,6 +78,25 @@ const SettingsAbout: React.FC = () => {
</List.Item>
</List>
</div>
<div className="mb-8">
<List title={intl.formatMessage(messages.supportoverseerr)}>
<List.Item
title={`☕️ ${intl.formatMessage(messages.helppaycoffee)}`}
>
<a
href="https://patreon.com/overseerr"
target="_blank"
rel="noreferrer"
className="text-indigo-500 hover:underline"
>
https://patreon.com/overseerr
</a>
</List.Item>
</List>
</div>
<div className="mb-8">
<Releases currentVersion={data.version} />
</div>
</>
);
};

View File

@@ -9,6 +9,8 @@ import Button from '../Common/Button';
import { defineMessages, useIntl } from 'react-intl';
import { useUser, Permission } from '../../hooks/useUser';
import { useToasts } from 'react-toast-notifications';
import { messages as permissionMessages } from '../UserEdit';
import { hasPermission } from '../../../server/lib/permissions';
const messages = defineMessages({
generalsettings: 'General Settings',
@@ -22,11 +24,19 @@ const messages = defineMessages({
toastApiKeyFailure: 'Something went wrong generating a new API Key.',
toastSettingsSuccess: 'Settings saved.',
toastSettingsFailure: 'Something went wrong saving settings.',
defaultPermissions: 'Default User Permissions',
});
interface PermissionOption {
id: string;
name: string;
description: string;
permission: Permission;
}
const SettingsMain: React.FC = () => {
const { addToast } = useToasts();
const { hasPermission } = useUser();
const { hasPermission: userHasPermission } = useUser();
const intl = useIntl();
const { data, error, revalidate } = useSWR<MainSettings>(
'/api/v1/settings/main'
@@ -53,13 +63,62 @@ const SettingsMain: React.FC = () => {
return <LoadingSpinner />;
}
const permissionList: PermissionOption[] = [
{
id: 'admin',
name: intl.formatMessage(permissionMessages.admin),
description: intl.formatMessage(permissionMessages.adminDescription),
permission: Permission.ADMIN,
},
{
id: 'settings',
name: intl.formatMessage(permissionMessages.settings),
description: intl.formatMessage(permissionMessages.settingsDescription),
permission: Permission.MANAGE_SETTINGS,
},
{
id: 'users',
name: intl.formatMessage(permissionMessages.users),
description: intl.formatMessage(permissionMessages.usersDescription),
permission: Permission.MANAGE_USERS,
},
{
id: 'managerequest',
name: intl.formatMessage(permissionMessages.managerequests),
description: intl.formatMessage(
permissionMessages.managerequestsDescription
),
permission: Permission.MANAGE_REQUESTS,
},
{
id: 'request',
name: intl.formatMessage(permissionMessages.request),
description: intl.formatMessage(permissionMessages.requestDescription),
permission: Permission.REQUEST,
},
{
id: 'vote',
name: intl.formatMessage(permissionMessages.vote),
description: intl.formatMessage(permissionMessages.voteDescription),
permission: Permission.VOTE,
},
{
id: 'autoapprove',
name: intl.formatMessage(permissionMessages.autoapprove),
description: intl.formatMessage(
permissionMessages.autoapproveDescription
),
permission: Permission.AUTO_APPROVE,
},
];
return (
<>
<div>
<h3 className="text-lg leading-6 font-medium text-gray-200">
<h3 className="text-lg font-medium leading-6 text-gray-200">
{intl.formatMessage(messages.generalsettings)}
</h3>
<p className="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
{intl.formatMessage(messages.generalsettingsDescription)}
</p>
</div>
@@ -67,11 +126,14 @@ const SettingsMain: React.FC = () => {
<Formik
initialValues={{
applicationUrl: data?.applicationUrl,
defaultPermissions: data?.defaultPermissions ?? 0,
}}
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/main', {
applicationUrl: values.applicationUrl,
defaultPermissions: values.defaultPermissions,
});
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
@@ -88,10 +150,10 @@ const SettingsMain: React.FC = () => {
}
}}
>
{({ isSubmitting }) => {
{({ isSubmitting, values, setFieldValue }) => {
return (
<Form>
{hasPermission(Permission.ADMIN) && (
{userHasPermission(Permission.ADMIN) && (
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800 sm:pt-5">
<label
htmlFor="username"
@@ -100,11 +162,11 @@ const SettingsMain: React.FC = () => {
{intl.formatMessage(messages.apikey)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm">
<div className="flex max-w-lg rounded-md shadow-sm">
<input
type="text"
id="apiKey"
className="flex-1 form-input block w-full min-w-0 rounded-none rounded-l-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-none form-input rounded-l-md sm:text-sm sm:leading-5"
value={data?.apiKey}
readOnly
/>
@@ -117,7 +179,7 @@ const SettingsMain: React.FC = () => {
e.preventDefault();
regenerate();
}}
className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium rounded-r-md text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-500 border border-gray-500 rounded-r-md hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
>
<svg
className="w-5 h-5"
@@ -144,20 +206,98 @@ const SettingsMain: React.FC = () => {
{intl.formatMessage(messages.applicationurl)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg flex rounded-md shadow-sm">
<div className="flex max-w-lg rounded-md shadow-sm">
<Field
id="applicationUrl"
name="applicationUrl"
type="text"
placeholder="https://os.example.com"
className="flex-1 form-input block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/>
</div>
</div>
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="mt-6">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-permissions"
>
{intl.formatMessage(messages.defaultPermissions)}
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
{permissionList.map((permissionOption) => (
<div
className={`relative flex items-start first:mt-0 mt-4 ${
permissionOption.permission !==
Permission.ADMIN &&
hasPermission(
Permission.ADMIN,
values.defaultPermissions
)
? 'opacity-50'
: ''
}`}
key={`permission-option-${permissionOption.id}`}
>
<div className="flex items-center h-5">
<input
id={permissionOption.id}
name="permissions"
type="checkbox"
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
disabled={
permissionOption.permission !==
Permission.ADMIN &&
hasPermission(
Permission.ADMIN,
values.defaultPermissions
)
}
onClick={() => {
setFieldValue(
'defaultPermissions',
hasPermission(
permissionOption.permission,
values.defaultPermissions
)
? values.defaultPermissions -
permissionOption.permission
: values.defaultPermissions +
permissionOption.permission
);
}}
checked={hasPermission(
permissionOption.permission,
values.defaultPermissions
)}
/>
</div>
<div className="ml-3 text-sm leading-5">
<label
htmlFor={permissionOption.id}
className="font-medium"
>
{permissionOption.name}
</label>
<p className="text-gray-500">
{permissionOption.description}
</p>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"

View File

@@ -13,6 +13,7 @@ import Modal from '../Common/Modal';
import Transition from '../Transition';
import axios from 'axios';
import SonarrModal from './SonarrModal';
import Alert from '../Common/Alert';
const messages = defineMessages({
radarrsettings: 'Radarr Settings',
@@ -37,42 +38,6 @@ const messages = defineMessages({
no4kimplemented: '(Default 4K servers are not currently implemented)',
});
const NoDefaultAlert: React.FC = () => {
const intl = useIntl();
return (
<div className="rounded-md bg-yellow-600 p-4 mb-8">
<div className="flex">
<div className="flex-shrink-0">
<svg
className="h-5 w-5 text-yellow-200"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-200">
{intl.formatMessage(messages.nodefault)}
</h3>
<div className="mt-2 text-sm text-yellow-300">
<p>{intl.formatMessage(messages.nodefaultdescription)}</p>
<p className="mt-2">
{intl.formatMessage(messages.no4kimplemented)}
</p>
</div>
</div>
</div>
</div>
);
};
interface ServerInstanceProps {
name: string;
isDefault?: boolean;
@@ -188,6 +153,7 @@ const ServerInstance: React.FC<ServerInstanceProps> = ({
};
const SettingsServices: React.FC = () => {
const intl = useIntl();
const {
data: radarrData,
error: radarrError,
@@ -293,7 +259,14 @@ const SettingsServices: React.FC = () => {
{radarrData.length > 0 &&
!radarrData.some(
(radarr) => radarr.isDefault && !radarr.is4k
) && <NoDefaultAlert />}
) && (
<Alert title={intl.formatMessage(messages.nodefault)}>
<p>{intl.formatMessage(messages.nodefaultdescription)}</p>
<p className="mt-2">
{intl.formatMessage(messages.no4kimplemented)}
</p>
</Alert>
)}
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{radarrData.map((radarr) => (
<ServerInstance
@@ -357,7 +330,14 @@ const SettingsServices: React.FC = () => {
{sonarrData.length > 0 &&
!sonarrData.some(
(sonarr) => sonarr.isDefault && !sonarr.is4k
) && <NoDefaultAlert />}
) && (
<Alert title={intl.formatMessage(messages.nodefault)}>
<p>{intl.formatMessage(messages.nodefaultdescription)}</p>
<p className="mt-2">
{intl.formatMessage(messages.no4kimplemented)}
</p>
</Alert>
)}
<ul className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{sonarrData.map((sonarr) => (
<ServerInstance

View File

@@ -9,6 +9,7 @@ import SetupSteps from './SetupSteps';
import axios from 'axios';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Badge from '../Common/Badge';
import LanguagePicker from '../Layout/LanguagePicker';
const messages = defineMessages({
finish: 'Finish Setup',
@@ -51,7 +52,10 @@ const Setup: React.FC = () => {
'/images/rotate4.jpg',
]}
/>
<div className="px-4 sm:px-2 md:px-0 sm:mx-auto sm:w-full sm:max-w-4xl relative z-50">
<div className="absolute top-4 right-4 z-50">
<LanguagePicker />
</div>
<div className="px-4 sm:px-2 md:px-0 sm:mx-auto sm:w-full sm:max-w-4xl relative z-40">
<img
src="/logo.png"
className="mx-auto max-h-32 w-auto mb-10"

View File

@@ -0,0 +1,66 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useContext } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import type { TvDetails } from '../../../../server/models/Tv';
import { LanguageContext } from '../../../context/LanguageContext';
import Error from '../../../pages/_error';
import Header from '../../Common/Header';
import LoadingSpinner from '../../Common/LoadingSpinner';
import PersonCard from '../../PersonCard';
const messages = defineMessages({
fullseriescrew: 'Full Series Crew',
});
const TvCrew: React.FC = () => {
const router = useRouter();
const intl = useIntl();
const { locale } = useContext(LanguageContext);
const { data, error } = useSWR<TvDetails>(
`/api/v1/tv/${router.query.tvId}?language=${locale}`
);
if (!data && !error) {
return <LoadingSpinner />;
}
if (!data) {
return <Error statusCode={404} />;
}
return (
<>
<Header
subtext={
<Link href={`/tv/${data.id}`}>
<a className="hover:underline">{data.name}</a>
</Link>
}
>
{intl.formatMessage(messages.fullseriescrew)}
</Header>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.crew.map((person, index) => {
return (
<li
key={`crew-${person.id}-${index}`}
className="flex flex-col items-center col-span-1 text-center"
>
<PersonCard
name={person.name}
personId={person.id}
subName={person.job}
profilePath={person.profilePath}
canExpand
/>
</li>
);
})}
</ul>
</>
);
};
export default TvCrew;

View File

@@ -1,4 +1,4 @@
import React, { useState, useContext } from 'react';
import React, { useState, useContext, useMemo } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import { useRouter } from 'next/router';
@@ -29,6 +29,9 @@ import type { RTRating } from '../../../server/api/rottentomatoes';
import Head from 'next/head';
import globalMessages from '../../i18n/globalMessages';
import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb';
import ExternalLinkBlock from '../ExternalLinkBlock';
import { sortCrewPriority } from '../../utils/creditHelpers';
import { Crew } from '../../../server/models/common';
const messages = defineMessages({
userrating: 'User Rating',
@@ -60,6 +63,7 @@ const messages = defineMessages({
showtype: 'Show Type',
anime: 'Anime',
network: 'Network',
viewfullcrew: 'View Full Crew',
});
interface TvDetailsProps {
@@ -104,6 +108,10 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
`/api/v1/tv/${router.query.tvId}/ratings`
);
const sortedCrew = useMemo(() => sortCrewPriority(data?.credits.crew ?? []), [
data,
]);
if (!data && !error) {
return <LoadingSpinner />;
}
@@ -137,9 +145,17 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
}
};
const isComplete =
data.seasons.filter((season) => season.seasonNumber !== 0).length <=
(
data.mediaInfo?.seasons.filter(
(season) => season.status === MediaStatus.AVAILABLE
) ?? []
).length;
return (
<div
className="bg-cover bg-center -mx-4 -mt-2 px-4 sm:px-8 pt-4 "
className="px-4 pt-4 -mx-4 -mt-2 bg-center bg-cover sm:px-8 "
style={{
height: 493,
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
@@ -164,21 +180,21 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
onClose={() => setShowManager(false)}
subText={data.name}
>
<h3 className="text-xl mb-2">
<h3 className="mb-2 text-xl">
{intl.formatMessage(messages.manageModalRequests)}
</h3>
<div className="bg-gray-600 shadow overflow-hidden rounded-md">
<div className="overflow-hidden bg-gray-600 rounded-md shadow">
<ul>
{data.mediaInfo?.requests?.map((request) => (
<li
key={`manage-request-${request.id}`}
className="border-b last:border-b-0 border-gray-700"
className="border-b border-gray-700 last:border-b-0"
>
<RequestBlock request={request} onUpdate={() => revalidate()} />
</li>
))}
{(data.mediaInfo?.requests ?? []).length === 0 && (
<li className="text-center py-4 text-gray-400">
<li className="py-4 text-center text-gray-400">
{intl.formatMessage(messages.manageModalNoRequests)}
</li>
)}
@@ -193,21 +209,21 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
>
{intl.formatMessage(messages.manageModalClearMedia)}
</Button>
<div className="text-sm text-gray-400 mt-2">
<div className="mt-2 text-sm text-gray-400">
{intl.formatMessage(messages.manageModalClearMediaWarning)}
</div>
</div>
)}
</SlideOver>
<div className="flex flex-col items-center md:flex-row md:items-end pt-4">
<div className="md:mr-4 flex-shrink-0">
<div className="flex flex-col items-center pt-4 md:flex-row md:items-end">
<div className="flex-shrink-0 md:mr-4">
<img
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
alt=""
className="rounded md:rounded-lg shadow md:shadow-2xl w-32 md:w-52"
className="w-32 rounded shadow md:rounded-lg md:shadow-2xl md:w-52"
/>
</div>
<div className="text-white flex flex-col md:mr-4 mt-4 md:mt-0 text-center md:text-left">
<div className="flex flex-col mt-4 text-center text-white md:mr-4 md:mt-0 md:text-left">
<div className="mb-2">
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
<Badge badgeType="success">
@@ -233,16 +249,16 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<h1 className="text-2xl md:text-4xl">
<span>{data.name}</span>
{data.firstAirDate && (
<span className="text-2xl ml-2">
<span className="ml-2 text-2xl">
({data.firstAirDate.slice(0, 4)})
</span>
)}
</h1>
<span className="text-xs md:text-base mt-1 md:mt-0">
<span className="mt-1 text-xs md:text-base md:mt-0">
{data.genres.map((g) => g.name).join(', ')}
</span>
</div>
<div className="flex-1 flex justify-end mt-4 md:mt-0">
<div className="flex justify-end flex-1 mt-4 md:mt-0">
{(!data.mediaInfo ||
data.mediaInfo.status === MediaStatus.UNKNOWN) && (
<Button
@@ -266,89 +282,91 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<FormattedMessage {...messages.request} />
</Button>
)}
{data.mediaInfo && data.mediaInfo.status !== MediaStatus.UNKNOWN && (
<ButtonWithDropdown
dropdownIcon={
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z"
clipRule="evenodd"
/>
</svg>
}
text={
<>
{data.mediaInfo &&
data.mediaInfo.status !== MediaStatus.UNKNOWN &&
!isComplete && (
<ButtonWithDropdown
dropdownIcon={
<svg
className="w-4 mr-1"
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z"
clipRule="evenodd"
/>
</svg>
<FormattedMessage {...messages.requestmore} />
</>
}
onClick={() => setShowRequestModal(true)}
>
{hasPermission(Permission.MANAGE_REQUESTS) &&
activeRequests &&
activeRequests.length > 0 && (
}
text={
<>
<ButtonWithDropdown.Item
onClick={() => modifyRequests('approve')}
<svg
className="w-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<svg
className="w-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
<FormattedMessage
{...messages.approverequests}
values={{ requestCount: activeRequests.length }}
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clipRule="evenodd"
/>
</ButtonWithDropdown.Item>
<ButtonWithDropdown.Item
onClick={() => modifyRequests('decline')}
>
<svg
className="w-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
<FormattedMessage
{...messages.declinerequests}
values={{ requestCount: activeRequests.length }}
/>
</ButtonWithDropdown.Item>
</svg>
<FormattedMessage {...messages.requestmore} />
</>
)}
</ButtonWithDropdown>
)}
}
onClick={() => setShowRequestModal(true)}
>
{hasPermission(Permission.MANAGE_REQUESTS) &&
activeRequests &&
activeRequests.length > 0 && (
<>
<ButtonWithDropdown.Item
onClick={() => modifyRequests('approve')}
>
<svg
className="w-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
<FormattedMessage
{...messages.approverequests}
values={{ requestCount: activeRequests.length }}
/>
</ButtonWithDropdown.Item>
<ButtonWithDropdown.Item
onClick={() => modifyRequests('decline')}
>
<svg
className="w-4 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
<FormattedMessage
{...messages.declinerequests}
values={{ requestCount: activeRequests.length }}
/>
</ButtonWithDropdown.Item>
</>
)}
</ButtonWithDropdown>
)}
{hasPermission(Permission.MANAGE_REQUESTS) && (
<Button
buttonType="default"
@@ -380,7 +398,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
)}
</div>
</div>
<div className="flex pt-8 text-white flex-col md:flex-row pb-4">
<div className="flex flex-col pt-8 pb-4 text-white md:flex-row">
<div className="flex-1 md:mr-8">
<h2 className="text-xl md:text-2xl">
<FormattedMessage {...messages.overview} />
@@ -390,45 +408,99 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
? data.overview
: intl.formatMessage(messages.overviewunavailable)}
</p>
<ul className="grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3">
{(data.createdBy.length > 0
? [
...data.createdBy.map(
(person): Partial<Crew> => ({
id: person.id,
job: 'Creator',
name: person.name,
})
),
...sortedCrew,
]
: sortedCrew
)
.slice(0, 6)
.map((person) => (
<li
className="flex flex-col col-span-1"
key={`crew-${person.job}-${person.id}`}
>
<span className="font-bold">{person.job}</span>
<Link href={`/person/${person.id}`}>
<a className="text-gray-400 transition duration-300 hover:text-underline hover:text-gray-100">
{person.name}
</a>
</Link>
</li>
))}
</ul>
{sortedCrew.length > 0 && (
<div className="flex justify-end mt-4">
<Link href={`/tv/${data.id}/crew`}>
<a className="flex items-center text-gray-400 transition duration-300 hover:text-gray-100">
<span>{intl.formatMessage(messages.viewfullcrew)}</span>
<svg
className="inline-block w-5 h-5 ml-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</a>
</Link>
</div>
)}
</div>
<div className="w-full md:w-80 mt-8 md:mt-0">
<div className="bg-gray-900 rounded-lg shadow border border-gray-800">
<div className="w-full mt-8 md:w-80 md:mt-0">
<div className="bg-gray-900 border border-gray-800 rounded-lg shadow">
{(data.voteCount > 0 || ratingData) && (
<div className="flex px-4 py-2 border-b border-gray-800 last:border-b-0 items-center justify-center">
{ratingData?.criticsRating && (
<>
<span className="text-sm">
{ratingData.criticsRating === 'Rotten' ? (
<RTRotten className="w-6 mr-1" />
) : (
<RTFresh className="w-6 mr-1" />
)}
</span>
<span className="text-gray-400 text-sm mr-4 last:mr-0">
{ratingData.criticsScore}%
</span>
</>
)}
{ratingData?.audienceRating && (
<>
<span className="text-sm">
{ratingData.audienceRating === 'Spilled' ? (
<RTAudRotten className="w-6 mr-1" />
) : (
<RTAudFresh className="w-6 mr-1" />
)}
</span>
<span className="text-gray-400 text-sm mr-4 last:mr-0">
{ratingData.audienceScore}%
</span>
</>
)}
<div className="flex items-center justify-center px-4 py-2 border-b border-gray-800 last:border-b-0">
{ratingData?.criticsRating &&
(ratingData?.criticsScore ?? 0) > 0 && (
<>
<span className="text-sm">
{ratingData.criticsRating === 'Rotten' ? (
<RTRotten className="w-6 mr-1" />
) : (
<RTFresh className="w-6 mr-1" />
)}
</span>
<span className="mr-4 text-sm text-gray-400 last:mr-0">
{ratingData.criticsScore}%
</span>
</>
)}
{ratingData?.audienceRating &&
(ratingData?.audienceScore ?? 0) > 0 && (
<>
<span className="text-sm">
{ratingData.audienceRating === 'Spilled' ? (
<RTAudRotten className="w-6 mr-1" />
) : (
<RTAudFresh className="w-6 mr-1" />
)}
</span>
<span className="mr-4 text-sm text-gray-400 last:mr-0">
{ratingData.audienceScore}%
</span>
</>
)}
{data.voteCount > 0 && (
<>
<span className="text-sm">
<TmdbLogo className="w-6 mr-2" />
</span>
<span className="text-gray-400 text-sm">
<span className="text-sm text-gray-400">
{data.voteAverage}/10
</span>
</>
@@ -442,7 +514,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<span className="text-sm">
{intl.formatMessage(messages.showtype)}
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{intl.formatMessage(messages.anime)}
</span>
</div>
@@ -451,7 +523,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<span className="text-sm">
<FormattedMessage {...messages.status} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{data.status}
</span>
</div>
@@ -462,7 +534,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<span className="text-sm">
<FormattedMessage {...messages.originallanguage} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{
data.spokenLanguages.find(
(lng) => lng.iso_639_1 === data.originalLanguage
@@ -476,18 +548,26 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
<span className="text-sm">
<FormattedMessage {...messages.network} />
</span>
<span className="flex-1 text-right text-gray-400 text-sm">
<span className="flex-1 text-sm text-right text-gray-400">
{data.networks.map((n) => n.name).join(', ')}
</span>
</div>
)}
</div>
<div className="mt-4">
<ExternalLinkBlock
mediaType="tv"
tmdbId={data.id}
imdbId={data.externalIds.imdbId}
rtUrl={ratingData?.url}
/>
</div>
</div>
</div>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<Link href="/tv/[tvId]/cast" as={`/tv/${data.id}/cast`}>
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>
<FormattedMessage {...messages.cast} />
</span>
@@ -525,13 +605,13 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
/>
{(recommended?.results ?? []).length > 0 && (
<>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<Link
href="/tv/[tvId]/recommendations"
as={`/tv/${data.id}/recommendations`}
>
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>
<FormattedMessage {...messages.recommendations} />
</span>
@@ -575,10 +655,10 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
)}
{(similar?.results ?? []).length > 0 && (
<>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<div className="flex-1 min-w-0">
<Link href="/tv/[tvId]/similar" as={`/tv/${data.id}/similar`}>
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>
<FormattedMessage {...messages.similar} />
</span>

View File

@@ -9,7 +9,7 @@ import axios from 'axios';
import { useToasts } from 'react-toast-notifications';
import Header from '../Common/Header';
const messages = defineMessages({
export const messages = defineMessages({
edituser: 'Edit User',
username: 'Username',
avatar: 'Avatar',
@@ -148,7 +148,7 @@ const UserEdit: React.FC = () => {
<FormattedMessage {...messages.edituser} />
</Header>
<div className="px-4 space-y-6 sm:p-6 lg:pb-8">
<div className="flex flex-col space-y-6 lg:flex-row lg:space-y-0 lg:space-x-6 text-white">
<div className="flex flex-col space-y-6 text-white lg:flex-row lg:space-y-0 lg:space-x-6">
<div className="flex-grow space-y-6">
<div className="space-y-1">
<label
@@ -157,11 +157,11 @@ const UserEdit: React.FC = () => {
>
<FormattedMessage {...messages.username} />
</label>
<div className="rounded-md shadow-sm flex">
<div className="flex rounded-md shadow-sm">
<input
id="username"
type="text"
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
value={user?.username}
readOnly
/>
@@ -174,11 +174,11 @@ const UserEdit: React.FC = () => {
>
<FormattedMessage {...messages.email} />
</label>
<div className="rounded-md shadow-sm flex">
<div className="flex rounded-md shadow-sm">
<input
id="email"
type="text"
className="form-input flex-grow block w-full min-w-0 rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5 bg-gray-700 border border-gray-500"
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
value={user?.email}
readOnly
/>
@@ -188,7 +188,7 @@ const UserEdit: React.FC = () => {
<div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0">
<p
className="block text-sm leading-5 font-medium text-gray-400"
className="block text-sm font-medium leading-5 text-gray-400"
aria-hidden="true"
>
<FormattedMessage {...messages.avatar} />
@@ -196,11 +196,11 @@ const UserEdit: React.FC = () => {
<div className="lg:hidden">
<div className="flex items-center">
<div
className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
className="flex-shrink-0 inline-block w-12 h-12 overflow-hidden rounded-full"
aria-hidden="true"
>
<img
className="rounded-full h-full w-full"
className="w-full h-full rounded-full"
src={user?.avatar}
alt=""
/>
@@ -208,9 +208,9 @@ const UserEdit: React.FC = () => {
</div>
</div>
<div className="hidden relative rounded-full overflow-hidden lg:block transition duration-150 ease-in-out">
<div className="relative hidden overflow-hidden transition duration-150 ease-in-out rounded-full lg:block">
<img
className="relative rounded-full w-40 h-40"
className="relative w-40 h-40 rounded-full"
src={user?.avatar}
alt=""
/>
@@ -223,7 +223,7 @@ const UserEdit: React.FC = () => {
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base leading-6 font-medium sm:text-sm sm:leading-5"
className="text-base font-medium leading-6 sm:text-sm sm:leading-5"
id="label-permissions"
>
<FormattedMessage {...messages.permissions} />
@@ -254,7 +254,7 @@ const UserEdit: React.FC = () => {
id={permissionOption.id}
name="permissions"
type="checkbox"
className="form-checkbox h-4 w-4 rounded-md text-indigo-600 transition duration-150 ease-in-out"
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
disabled={
(permissionOption.permission !==
Permission.ADMIN &&
@@ -305,9 +305,9 @@ const UserEdit: React.FC = () => {
</div>
</div>
</div>
<div className="mt-8 border-t border-gray-700 pt-5">
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"

View File

@@ -18,6 +18,10 @@ import globalMessages from '../../i18n/globalMessages';
const messages = defineMessages({
userlist: 'User List',
importfromplex: 'Import Users From Plex',
importfromplexerror: 'Something went wrong importing users from Plex',
importedfromplex:
'{userCount, plural, =0 {No new users} one {# new user} other {# new users}} imported from Plex',
username: 'Username',
totalrequests: 'Total Requests',
usertype: 'User Type',
@@ -42,6 +46,7 @@ const UserList: React.FC = () => {
const { addToast } = useToasts();
const { data, error, revalidate } = useSWR<User[]>('/api/v1/user');
const [isDeleting, setDeleting] = useState(false);
const [isImporting, setImporting] = useState(false);
const [deleteModal, setDeleteModal] = useState<{
isOpen: boolean;
user?: User;
@@ -66,10 +71,38 @@ const UserList: React.FC = () => {
appearance: 'error',
});
} finally {
setDeleting(false);
revalidate();
}
};
const importFromPlex = async () => {
setImporting(true);
try {
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-plex'
);
addToast(
intl.formatMessage(messages.importedfromplex, {
userCount: createdUsers.length,
}),
{
autoDismiss: true,
appearance: 'success',
}
);
} catch (e) {
addToast(intl.formatMessage(messages.importfromplexerror), {
autoDismiss: true,
appearance: 'error',
});
} finally {
revalidate();
setImporting(false);
}
};
if (!data && !error) {
return <LoadingSpinner />;
}
@@ -116,7 +149,17 @@ const UserList: React.FC = () => {
{intl.formatMessage(messages.deleteconfirm)}
</Modal>
</Transition>
<Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header>
<div className="flex items-center justify-between">
<Header extraMargin={4}>{intl.formatMessage(messages.userlist)}</Header>
<Button
className="mx-4 my-8"
buttonType="primary"
disabled={isImporting}
onClick={() => importFromPlex()}
>
{intl.formatMessage(messages.importfromplex)}
</Button>
</div>
<Table>
<thead>
<tr>
@@ -134,18 +177,18 @@ const UserList: React.FC = () => {
<tr key={`user-list-${user.id}`}>
<Table.TD>
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="flex-shrink-0 w-10 h-10">
<img
className="h-10 w-10 rounded-full"
className="w-10 h-10 rounded-full"
src={user.avatar}
alt=""
/>
</div>
<div className="ml-4">
<div className="text-sm leading-5 font-medium">
<div className="text-sm font-medium leading-5">
{user.username}
</div>
<div className="text-sm leading-5 text-gray-300">
<div className="text-sm text-gray-300 leading-5">
{user.email}
</div>
</div>

View File

@@ -8,7 +8,11 @@ export type AvailableLocales =
| 'de'
| 'ru'
| 'nl'
| 'es';
| 'es'
| 'it'
| 'pt-BR'
| 'sr'
| 'sv';
interface LanguageContextProps {
locale: AvailableLocales;

View File

@@ -7,8 +7,20 @@ import type { Nullable } from '../utils/typeHelpers';
type Url = string | UrlObject;
const extraEncodes: [RegExp, string][] = [
[/\(/g, '%28'],
[/\)/g, '%29'],
[/!/g, '%21'],
];
const encodeURIExtraParams = (string: string): string => {
return encodeURIComponent(string).replace(/!/g, '%21');
let finalString = encodeURIComponent(string);
extraEncodes.forEach((encode) => {
finalString = finalString.replace(encode[0], encode[1]);
});
return finalString;
};
interface SearchObject {

View File

@@ -15,6 +15,7 @@ const globalMessages = defineMessages({
decline: 'Decline',
delete: 'Delete',
deleting: 'Deleting…',
close: 'Close',
});
export default globalMessages;

View File

@@ -1,42 +1,42 @@
{
"components.Discover.discovermovies": "Beliebte Filme",
"components.Discover.discovertv": "Beliebte Serien",
"components.Discover.nopending": "Keine ausstehenden Anträge",
"components.Discover.nopending": "Keine ausstehenden Anfragen",
"components.Discover.popularmovies": "Beliebte Filme",
"components.Discover.populartv": "Beliebte Serien",
"components.Discover.recentlyAdded": "Kürzlich hinzugefügt",
"components.Discover.recentrequests": "Letzte Anträge",
"components.Discover.recentrequests": "Aktuelle Anfragen",
"components.Discover.trending": "Trends",
"components.Discover.upcoming": "Bald erscheinende Filme",
"components.Discover.upcomingmovies": "Bald erscheinende Filme",
"components.Layout.LanguagePicker.changelanguage": "Sprache ändern",
"components.Layout.SearchInput.searchPlaceholder": "Nach Filme und Fernsehsendungen suchen",
"components.Layout.SearchInput.searchPlaceholder": "Nach Filmen und Serien suchen",
"components.Layout.Sidebar.dashboard": "Entdecken",
"components.Layout.Sidebar.requests": "Anträge",
"components.Layout.Sidebar.settings": "Einstellungen",
"components.Layout.Sidebar.users": "Benutzer",
"components.Layout.UserDropdown.signout": "Abmelden",
"components.Layout.alphawarning": "Dies ist ALPHA-Software. Fast alles muss fast kaputt und/oder instabil sein. Bitte melden Sie Probleme auf der GitHub-Seite!",
"components.Layout.alphawarning": "Dies ist ALPHA-Software. Fast alles kann kaputt und/oder instabil sein. Bitte melde Probleme auf der GitHub-Seite!",
"components.Login.signinplex": "Melden Sie sich an, um fortzufahren",
"components.MovieDetails.approve": "Genehmigen",
"components.MovieDetails.available": "Verfügbar",
"components.MovieDetails.budget": "Budget",
"components.MovieDetails.cancelrequest": "Antrag abbrechen",
"components.MovieDetails.cancelrequest": "Anfrage zurücknehmen",
"components.MovieDetails.cast": "Besetzung",
"components.MovieDetails.decline": "Ablehnen",
"components.MovieDetails.manageModalClearMedia": "Alle Mediendaten löschen",
"components.MovieDetails.manageModalClearMediaWarning": "Dadurch werden alle Mediendaten einschließlich aller Anträge für dieses Element entfernt. Diese Aktion ist irreversibel. Wenn dieses Element in Ihrer Plex-Bibliothek vorhanden ist, werden die Medieninformationen bei der nächsten Synchronisierung neu erstellt.",
"components.MovieDetails.manageModalClearMediaWarning": "Dadurch werden alle Mediendaten einschließlich aller Anfragen für dieses Element entfernt. Diese Aktion ist irreversibel. Wenn dieses Element in Ihrer Plex-Bibliothek vorhanden ist, werden die Medieninformationen bei der nächsten Synchronisierung neu erstellt.",
"components.MovieDetails.manageModalNoRequests": "Keine Anträge",
"components.MovieDetails.manageModalRequests": "Anträge",
"components.MovieDetails.manageModalTitle": "Filme verwalten",
"components.MovieDetails.manageModalRequests": "Anfragen",
"components.MovieDetails.manageModalTitle": "Film verwalten",
"components.MovieDetails.originallanguage": "Originalsprache",
"components.MovieDetails.overview": "Übersicht",
"components.MovieDetails.overviewunavailable": "Übersicht nicht verfügbar",
"components.MovieDetails.pending": "Ausstehend",
"components.MovieDetails.recommendations": "Empfehlungen",
"components.MovieDetails.recommendationssubtext": "Wenn Ihnen {title} gefallen hat, könnte Ihnen auch gefallen …",
"components.MovieDetails.recommendationssubtext": "Wenn dir {title} gefallen hat, könnte dir auch gefallen …",
"components.MovieDetails.releasedate": "Erscheinungsdatum",
"components.MovieDetails.request": "Antrag",
"components.MovieDetails.request": "Anfrage",
"components.MovieDetails.revenue": "Einnahmen",
"components.MovieDetails.runtime": "{minutes} Minuten",
"components.MovieDetails.similar": "Ähnliche Titel",
@@ -44,7 +44,7 @@
"components.MovieDetails.status": "Status",
"components.MovieDetails.unavailable": "Nicht verfügbar",
"components.MovieDetails.userrating": "Benutzerbewertung",
"components.MovieDetails.viewrequest": "Antrag anzeigen",
"components.MovieDetails.viewrequest": "Anfrage anzeigen",
"components.PersonDetails.appearsin": "Erscheint in",
"components.PersonDetails.ascharacter": "als {character}",
"components.PersonDetails.nobiography": "Keine Biografie verfügbar.",
@@ -53,43 +53,43 @@
"components.PlexLoginButton.loginwithplex": "Anmeldung mit Plex",
"components.RequestBlock.seasons": "Staffeln",
"components.RequestCard.all": "Alle",
"components.RequestCard.requestedby": "Antrag von {username}",
"components.RequestCard.requestedby": "Anfrage von {username}",
"components.RequestCard.seasons": "Staffeln",
"components.RequestList.RequestItem.notavailable": "entf.",
"components.RequestList.RequestItem.requestedby": "Antrag von {username}",
"components.RequestList.RequestItem.requestedby": "Angefragt von {username}",
"components.RequestList.RequestItem.seasons": "Staffeln",
"components.RequestList.mediaInfo": "Medieninfos",
"components.RequestList.modifiedBy": "Zuletzt geändert von",
"components.RequestList.next": "Nächste",
"components.RequestList.previous": "Vorherige",
"components.RequestList.requestedAt": "Antrag erstellt am",
"components.RequestList.requests": "Anträge",
"components.RequestList.requestedAt": "Angefragt am",
"components.RequestList.requests": "Anfragen",
"components.RequestList.showingresults": "Anzeigen von <strong>{from}</strong> bis <strong> {to} </strong> von <strong> {total} </strong> Ergebnissen",
"components.RequestList.status": "Status",
"components.RequestModal.cancel": "Antrag abbrechen",
"components.RequestModal.cancel": "Anfrage abbrechen",
"components.RequestModal.cancelling": "Abbrechen …",
"components.RequestModal.cancelrequest": "Dadurch wird Ihre Antrag entfernt. Sind Sie sicher, dass Sie weitermachen wollen?",
"components.RequestModal.cancelrequest": "Dadurch wird deine Anfrage entfernt. Bist du dir sicher, dass du weitermachen willst?",
"components.RequestModal.close": "Schließen",
"components.RequestModal.extras": "Extras",
"components.RequestModal.notrequested": "Nicht angefordert",
"components.RequestModal.notrequested": "Nicht angefragt",
"components.RequestModal.numberofepisodes": "Anzahl der Folgen",
"components.RequestModal.pendingrequest": "Ausstehender Antrag für {title}",
"components.RequestModal.request": "Antrag",
"components.RequestModal.requestCancel": "Antrag für <strong>{title}</strong> storniert",
"components.RequestModal.requestSuccess": "<strong>{title}</strong> angefordert.",
"components.RequestModal.requestadmin": "Ihr Antrag wird sofort genehmigt.",
"components.RequestModal.requestfrom": "Derzeit ist ein Antrag von {username} ausstehend",
"components.RequestModal.requesting": "Antrag wird erstellt …",
"components.RequestModal.requestseasons": "{seasonCount} {seasonCount, plural, one {Season} other {Seasons}} anfordern",
"components.RequestModal.requesttitle": "{title} anfordern",
"components.RequestModal.pendingrequest": "Ausstehende Anfrage für {title}",
"components.RequestModal.request": "Anfrage",
"components.RequestModal.requestCancel": "Anfrage für <strong>{title}</strong> abgebrochen",
"components.RequestModal.requestSuccess": "<strong>{title}</strong> angefragt.",
"components.RequestModal.requestadmin": "Deine Anfrage wird direkt genehmigt.",
"components.RequestModal.requestfrom": "Derzeit steht eine Anfrage von {username} aus",
"components.RequestModal.requesting": "Wird angefragt…",
"components.RequestModal.requestseasons": "{seasonCount} {seasonCount, plural, one {Season} other {Seasons}} anfragen",
"components.RequestModal.requesttitle": "{title} anfragen",
"components.RequestModal.season": "Staffel",
"components.RequestModal.seasonnumber": "Staffel {number}",
"components.RequestModal.selectseason": "Staffel(n) auswählen",
"components.RequestModal.status": "Status",
"components.Search.searchresults": "Suchergebnisse",
"components.Settings.Notifications.agentenabled": "Agent aktiviert",
"components.Settings.Notifications.authPass": "Authentifizierungspasswort",
"components.Settings.Notifications.authUser": "Authentifizierungsbenutzername",
"components.Settings.Notifications.authPass": "Passwort",
"components.Settings.Notifications.authUser": "Benutzername",
"components.Settings.Notifications.emailsender": "E-Mail-Absenderadresse",
"components.Settings.Notifications.enableSsl": "SSL aktivieren",
"components.Settings.Notifications.save": "Änderungen speichern",
@@ -300,15 +300,15 @@
"pages.internalServerError": "{statusCode} Interner Serverfehler",
"pages.oops": "Hoppla",
"pages.pageNotFound": "404 Seite nicht gefunden",
"pages.returnHome": "Zur Starseite",
"pages.returnHome": "Zur Startseite",
"pages.serviceUnavailable": "{statusCode} Dienst nicht verfügbar",
"pages.somethingWentWrong": "{statusCode} Es ist ein Fehler aufgetreten",
"components.TvDetails.TvCast.fullseriescast": "Vollserienbesetzung",
"components.MovieDetails.MovieCast.fullcast": "Vollständige Besetzung",
"components.Settings.Notifications.emailsettingssaved": "E-Mail-Benachrichtigungseinstellungen gespeichert!",
"components.Settings.Notifications.emailsettingsfailed": "Die Einstellungen für die E-Mail-Benachrichtigungen konnten nicht gespeichert werden.",
"components.Settings.Notifications.emailsettingsfailed": "Fehler beim Speichern der E-Mail-Benachrichtigungseinstellungen.",
"components.Settings.Notifications.discordsettingssaved": "Discord-Benachrichtigungseinstellungen gespeichert!",
"components.Settings.Notifications.discordsettingsfailed": "Die Einstellungen für die Discord-Benachrichtigungen konnten nicht gespeichert werden.",
"components.Settings.Notifications.discordsettingsfailed": "Fehler beim Speichern der Discord-Benachrichtigungseinstellungen.",
"components.Settings.validationPortRequired": "Sie müssen einen Port angeben",
"components.Settings.validationHostnameRequired": "Sie müssen einen Hostnamen/IP angeben",
"components.Settings.SonarrModal.validationNameRequired": "Sie müssen einen Servernamen angeben",
@@ -321,5 +321,32 @@
"components.Settings.SettingsAbout.clickheretojoindiscord": "Klicken Sie hier, um unserem Discord-Server beizutreten.",
"components.Settings.RadarrModal.validationNameRequired": "Sie müssen einen Servernamen angeben",
"components.Setup.tip": "Tipp",
"components.Setup.syncingbackground": "Die Synchronisierung wird im Hintergrund ausgeführt. Sie können die Konfiguration in der Zwischenzeit fortsetzen."
"components.Setup.syncingbackground": "Die Synchronisierung wird im Hintergrund ausgeführt. Sie können die Konfiguration in der Zwischenzeit fortsetzen.",
"i18n.deleting": "Löschen …",
"components.UserList.userdeleteerror": "Beim Löschen des Benutzers ist ein Fehler aufgetreten",
"components.UserList.userdeleted": "Benutzer gelöscht",
"components.UserList.deleteuser": "Benutzer löschen",
"components.UserList.deleteconfirm": "Wollen Sie diesen Benutzer wirklich löschen? Alle vorhandenen Anforderungsdaten dieses Benutzers werden entfernt.",
"components.Settings.nodefaultdescription": "Mindestens ein Server muss als Standard markiert sein, bevor Anforderungen an Ihre Dienste weitergeleitet werden.",
"components.Settings.nodefault": "Kein Standardserver ausgewählt!",
"components.Settings.no4kimplemented": "(Standard-4K-Server sind derzeit nicht implementiert.)",
"components.Settings.SonarrModal.testFirstRootFolders": "Testen Sie Ihre Verbindung, um Stammordner zu laden",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Testen Sie Ihre Verbindung, um Qualitätsprofile zu laden",
"components.Settings.SonarrModal.loadingrootfolders": "Laden von Stammordnern…",
"components.Settings.SonarrModal.loadingprofiles": "Qualitätsprofile werden geladen…",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Sie müssen die Mindestverfügbarkeit auswählen",
"components.Settings.RadarrModal.testFirstRootFolders": "Testen Sie Ihre Verbindung, um Stammordner zu laden",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Testen Sie Ihre Verbindung zu Lastqualitätsprofilen",
"components.Settings.RadarrModal.loadingrootfolders": "Laden von Stammordnern…",
"components.Settings.RadarrModal.loadingprofiles": "Qualitätsprofile werden geladen…",
"components.TvDetails.anime": "Anime",
"components.Settings.toastApiKeySuccess": "Neuer API-Schlüssel generiert!",
"components.TvDetails.showtype": "Serientyp",
"components.TvDetails.network": "Netzwerk",
"components.Settings.toastSettingsSuccess": "Einstellungen gespeichert.",
"components.Settings.toastSettingsFailure": "Beim Speichern der Einstellungen ging etwas schief.",
"components.Settings.toastApiKeyFailure": "Bei der Generierung eines neuen API-Schlüssels kam es zu einem Fehler.",
"components.Settings.SonarrModal.animerootfolder": "Animestammverzeichnis",
"components.Settings.SonarrModal.animequalityprofile": "Animequalitätsprofil",
"components.MovieDetails.studio": "Studio"
}

View File

@@ -19,6 +19,7 @@
"components.Layout.alphawarning": "This is ALPHA software. Almost everything is bound to be nearly broken and/or unstable. Please report issues to the Overseerr GitHub!",
"components.Login.signinplex": "Sign in to continue",
"components.MovieDetails.MovieCast.fullcast": "Full Cast",
"components.MovieDetails.MovieCrew.fullcrew": "Full Crew",
"components.MovieDetails.approve": "Approve",
"components.MovieDetails.available": "Available",
"components.MovieDetails.budget": "Budget",
@@ -35,7 +36,7 @@
"components.MovieDetails.overviewunavailable": "Overview unavailable",
"components.MovieDetails.pending": "Pending",
"components.MovieDetails.recommendations": "Recommendations",
"components.MovieDetails.recommendationssubtext": "If you liked {title}, you might also like...",
"components.MovieDetails.recommendationssubtext": "If you liked {title}, you might also like",
"components.MovieDetails.releasedate": "Release Date",
"components.MovieDetails.request": "Request",
"components.MovieDetails.revenue": "Revenue",
@@ -46,12 +47,14 @@
"components.MovieDetails.studio": "Studio",
"components.MovieDetails.unavailable": "Unavailable",
"components.MovieDetails.userrating": "User Rating",
"components.MovieDetails.viewfullcrew": "View Full Crew",
"components.MovieDetails.viewrequest": "View Request",
"components.PersonDetails.appearsin": "Appears in",
"components.PersonDetails.ascharacter": "as {character}",
"components.PersonDetails.crewmember": "Crew Member",
"components.PersonDetails.nobiography": "No biography available.",
"components.PlexLoginButton.loading": "Loading...",
"components.PlexLoginButton.loggingin": "Logging in...",
"components.PlexLoginButton.loading": "Loading",
"components.PlexLoginButton.loggingin": "Logging in",
"components.PlexLoginButton.loginwithplex": "Login with Plex",
"components.RequestBlock.seasons": "Seasons",
"components.RequestCard.all": "All",
@@ -69,7 +72,7 @@
"components.RequestList.showingresults": "Showing <strong>{from}</strong> to <strong>{to}</strong> of <strong>{total}</strong> results",
"components.RequestList.status": "Status",
"components.RequestModal.cancel": "Cancel Request",
"components.RequestModal.cancelling": "Cancelling...",
"components.RequestModal.cancelling": "Cancelling",
"components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?",
"components.RequestModal.close": "Close",
"components.RequestModal.extras": "Extras",
@@ -99,9 +102,11 @@
"components.Settings.Notifications.emailsettingssaved": "Email notification settings saved!",
"components.Settings.Notifications.enableSsl": "Enable SSL",
"components.Settings.Notifications.save": "Save Changes",
"components.Settings.Notifications.saving": "Saving...",
"components.Settings.Notifications.saving": "Saving",
"components.Settings.Notifications.smtpHost": "SMTP Host",
"components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.test": "Test",
"components.Settings.Notifications.testsent": "Test notification sent!",
"components.Settings.Notifications.validationFromRequired": "You must provide an email sender address",
"components.Settings.Notifications.validationSmtpHostRequired": "You must provide an SMTP host",
"components.Settings.Notifications.validationSmtpPortRequired": "You must provide an SMTP port",
@@ -124,7 +129,7 @@
"components.Settings.RadarrModal.qualityprofile": "Quality Profile",
"components.Settings.RadarrModal.rootfolder": "Root Folder",
"components.Settings.RadarrModal.save": "Save Changes",
"components.Settings.RadarrModal.saving": "Saving...",
"components.Settings.RadarrModal.saving": "Saving",
"components.Settings.RadarrModal.selectMinimumAvailability": "Select minimum availability",
"components.Settings.RadarrModal.selectQualityProfile": "Select a Quality Profile",
"components.Settings.RadarrModal.selectRootFolder": "Select a Root Folder",
@@ -135,7 +140,7 @@
"components.Settings.RadarrModal.test": "Test",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Test your connection to load quality profiles",
"components.Settings.RadarrModal.testFirstRootFolders": "Test your connection to load root folders",
"components.Settings.RadarrModal.testing": "Testing...",
"components.Settings.RadarrModal.testing": "Testing",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Failed to connect to Radarr Server",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Radarr connection established!",
"components.Settings.RadarrModal.validationApiKeyRequired": "You must provide an API key",
@@ -145,10 +150,22 @@
"components.Settings.RadarrModal.validationPortRequired": "You must provide a port",
"components.Settings.RadarrModal.validationProfileRequired": "You must select a profile",
"components.Settings.RadarrModal.validationRootFolderRequired": "You must select a root folder",
"components.Settings.SettingsAbout.Releases.currentversion": "Current Version",
"components.Settings.SettingsAbout.Releases.latestversion": "Latest Version",
"components.Settings.SettingsAbout.Releases.releasedataMissing": "Release data missing. Is GitHub down?",
"components.Settings.SettingsAbout.Releases.releases": "Releases",
"components.Settings.SettingsAbout.Releases.runningDevelop": "You are running a develop version of Overseerr!",
"components.Settings.SettingsAbout.Releases.runningDevelopMessage": "The changes in your version will not be available below. Please look at the <GithubLink>GitHub repository</GithubLink> for latest updates.",
"components.Settings.SettingsAbout.Releases.versionChangelog": "Version Changelog",
"components.Settings.SettingsAbout.Releases.viewchangelog": "View Changelog",
"components.Settings.SettingsAbout.Releases.viewongithub": "View on GitHub",
"components.Settings.SettingsAbout.clickheretojoindiscord": "Click here to join our Discord server.",
"components.Settings.SettingsAbout.gettingsupport": "Getting Support",
"components.Settings.SettingsAbout.githubdiscussions": "GitHub Discussions",
"components.Settings.SettingsAbout.helppaycoffee": "Help pay for coffee",
"components.Settings.SettingsAbout.overseerrinformation": "Overseerr Information",
"components.Settings.SettingsAbout.supportoverseerr": "Support Overseerr",
"components.Settings.SettingsAbout.timezone": "Timezone",
"components.Settings.SettingsAbout.totalmedia": "Total Media",
"components.Settings.SettingsAbout.totalrequests": "Total Requests",
"components.Settings.SettingsAbout.version": "Version",
@@ -169,7 +186,7 @@
"components.Settings.SonarrModal.qualityprofile": "Quality Profile",
"components.Settings.SonarrModal.rootfolder": "Root Folder",
"components.Settings.SonarrModal.save": "Save Changes",
"components.Settings.SonarrModal.saving": "Saving...",
"components.Settings.SonarrModal.saving": "Saving",
"components.Settings.SonarrModal.seasonfolders": "Season Folders",
"components.Settings.SonarrModal.selectQualityProfile": "Select a Quality Profile",
"components.Settings.SonarrModal.selectRootFolder": "Select a Root Folder",
@@ -180,7 +197,7 @@
"components.Settings.SonarrModal.test": "Test",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Test your connection to load quality profiles",
"components.Settings.SonarrModal.testFirstRootFolders": "Test your connection to load root folders",
"components.Settings.SonarrModal.testing": "Testing...",
"components.Settings.SonarrModal.testing": "Testing",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Could not connect to Sonarr Server",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Sonarr connection established!",
"components.Settings.SonarrModal.validationApiKeyRequired": "You must provide an API key",
@@ -200,6 +217,7 @@
"components.Settings.currentlibrary": "Current Library: {name}",
"components.Settings.default": "Default",
"components.Settings.default4k": "Default 4K",
"components.Settings.defaultPermissions": "Default User Permissions",
"components.Settings.delete": "Delete",
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
"components.Settings.edit": "Edit",
@@ -233,7 +251,7 @@
"components.Settings.radarrsettings": "Radarr Settings",
"components.Settings.runnow": "Run Now",
"components.Settings.save": "Save Changes",
"components.Settings.saving": "Saving...",
"components.Settings.saving": "Saving",
"components.Settings.servername": "Server Name (Automatically set after you save)",
"components.Settings.servernamePlaceholder": "Plex Server Name",
"components.Settings.sonarrSettingsDescription": "Set up your Sonarr connection below. You can have multiple, but only two active as defaults at any time (one for standard HD and one for 4K). Administrators can override which server is used for new requests.",
@@ -252,7 +270,7 @@
"components.Setup.configureservices": "Configure Services",
"components.Setup.continue": "Continue",
"components.Setup.finish": "Finish Setup",
"components.Setup.finishing": "Finishing...",
"components.Setup.finishing": "Finishing",
"components.Setup.loginwithplex": "Login with Plex",
"components.Setup.signinMessage": "Get started by logging in with your Plex account",
"components.Setup.syncingbackground": "Syncing will run in the background. You can continue the setup process in the meantime.",
@@ -262,6 +280,7 @@
"components.TitleCard.movie": "Movie",
"components.TitleCard.tvshow": "Series",
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
"components.TvDetails.anime": "Anime",
"components.TvDetails.approve": "Approve",
"components.TvDetails.approverequests": "Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}",
@@ -281,7 +300,7 @@
"components.TvDetails.overviewunavailable": "Overview unavailable",
"components.TvDetails.pending": "Pending",
"components.TvDetails.recommendations": "Recommendations",
"components.TvDetails.recommendationssubtext": "If you liked {title}, you might also like...",
"components.TvDetails.recommendationssubtext": "If you liked {title}, you might also like",
"components.TvDetails.request": "Request",
"components.TvDetails.requestmore": "Request More",
"components.TvDetails.showtype": "Show Type",
@@ -290,6 +309,7 @@
"components.TvDetails.status": "Status",
"components.TvDetails.unavailable": "Unavailable",
"components.TvDetails.userrating": "User Rating",
"components.TvDetails.viewfullcrew": "View Full Crew",
"components.UserEdit.admin": "Admin",
"components.UserEdit.adminDescription": "Full administrator access. Bypasses all permission checks.",
"components.UserEdit.autoapprove": "Auto Approve",
@@ -303,7 +323,7 @@
"components.UserEdit.request": "Request",
"components.UserEdit.requestDescription": "Grants permission to request movies and series.",
"components.UserEdit.save": "Save",
"components.UserEdit.saving": "Saving...",
"components.UserEdit.saving": "Saving",
"components.UserEdit.settings": "Manage Settings",
"components.UserEdit.settingsDescription": "Grants permission to modify all Overseerr settings. A user must have this permission to grant it to others.",
"components.UserEdit.userfail": "Something went wrong saving the user.",
@@ -319,6 +339,9 @@
"components.UserList.deleteconfirm": "Are you sure you want to delete this user? All existing request data from this user will be removed.",
"components.UserList.deleteuser": "Delete User",
"components.UserList.edit": "Edit",
"components.UserList.importedfromplex": "{userCount, plural, =0 {No new users} one {# new user} other {# new users}} imported from Plex",
"components.UserList.importfromplex": "Import Users From Plex",
"components.UserList.importfromplexerror": "Something went wrong importing users from Plex",
"components.UserList.lastupdated": "Last Updated",
"components.UserList.plexuser": "Plex User",
"components.UserList.role": "Role",
@@ -333,6 +356,7 @@
"i18n.approved": "Approved",
"i18n.available": "Available",
"i18n.cancel": "Cancel",
"i18n.close": "Close",
"i18n.decline": "Decline",
"i18n.declined": "Declined",
"i18n.delete": "Delete",

View File

@@ -243,7 +243,7 @@
"components.TvDetails.similar": "Series similares",
"components.TvDetails.requestmore": "Solicitar más",
"components.TvDetails.request": "Solicitar",
"components.TvDetails.recommendationssubtext": "Si te gustó {title}, también te puede gustar...",
"components.TvDetails.recommendationssubtext": "Si te gustó {title}, también te puede gustar",
"components.TvDetails.recommendations": "Recomendaciones",
"components.TvDetails.pending": "Pendiente",
"components.TvDetails.overviewunavailable": "Resumen no disponible",
@@ -321,5 +321,22 @@
"components.Settings.applicationurl": "URL de la aplicación",
"components.Settings.apikey": "Clave API",
"components.Setup.tip": "Consejo",
"components.Setup.syncingbackground": "La sincronización se ejecutará en segundo plano. Mientras tanto, puede continuar con el proceso de configuración."
"components.Setup.syncingbackground": "La sincronización se ejecutará en segundo plano. Mientras tanto, puede continuar con el proceso de configuración.",
"i18n.deleting": "Eliminando…",
"components.UserList.userdeleteerror": "Algo salió mal al eliminar al usuario",
"components.UserList.userdeleted": "Usuario eliminado",
"components.UserList.deleteuser": "Eliminar usuario",
"components.UserList.deleteconfirm": "¿Está seguro de que desea eliminar este usuario? Se eliminarán todas las solicitudes existentes de este usuario.",
"components.Settings.nodefaultdescription": "Al menos un servidor debe estar marcado como predeterminado antes de que cualquier solicitud llegue a sus servicios.",
"components.Settings.nodefault": "¡Ningún servidor predeterminado seleccionado!",
"components.Settings.no4kimplemented": "(Los servidores 4K predeterminados no están implementados actualmente)",
"components.Settings.SonarrModal.testFirstRootFolders": "Prueba la conexión para cargar carpetas raíz",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Prueba la conexión para cargar perfiles de calidad",
"components.Settings.SonarrModal.loadingrootfolders": "Cargando carpetas raíz…",
"components.Settings.SonarrModal.loadingprofiles": "Cargando perfiles de calidad…",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Debes seleccionar disponibilidad mínima",
"components.Settings.RadarrModal.testFirstRootFolders": "Prueba la conexión para cargar carpetas raíz",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Prueba la conexión para cargar perfiles de calidad",
"components.Settings.RadarrModal.loadingrootfolders": "Cargando carpetas raíz…",
"components.Settings.RadarrModal.loadingprofiles": "Cargando perfiles de calidad…"
}

View File

@@ -34,7 +34,7 @@
"components.MovieDetails.overviewunavailable": "Résumé indisponible",
"components.MovieDetails.pending": "En attente",
"components.MovieDetails.recommendations": "Recommendations",
"components.MovieDetails.recommendationssubtext": "Si vous avez aimé {title}, vous aimerez peut-être...",
"components.MovieDetails.recommendationssubtext": "Si vous avez aimé {title}, vous aimerez peut-être",
"components.MovieDetails.releasedate": "Date de sortie",
"components.MovieDetails.request": "Demande d'ajout",
"components.MovieDetails.revenue": "Revenus",
@@ -321,5 +321,32 @@
"components.Settings.SettingsAbout.clickheretojoindiscord": "Cliquez ici pour rejoindre notre serveur Discord.",
"components.Settings.RadarrModal.validationNameRequired": "Vous devez fournir un nom de serveur",
"components.Setup.tip": "Astuce",
"components.Setup.syncingbackground": "La synchronisation s'exécutera en arrière-plan. Vous pouvez continuer le processus de configuration en attendant."
"components.Setup.syncingbackground": "La synchronisation s'exécutera en arrière-plan. Vous pouvez continuer le processus de configuration en attendant.",
"i18n.deleting": "Suppression…",
"components.UserList.userdeleteerror": "Une erreur s'est produite lors de la suppression de l'utilisateur",
"components.UserList.userdeleted": "Utilisateur supprimé",
"components.UserList.deleteuser": "Supprimer l'utilisateur",
"components.UserList.deleteconfirm": "Voulez-vous vraiment supprimer cet utilisateur ? Toutes les données de demande existantes de cet utilisateur seront supprimées.",
"components.Settings.nodefaultdescription": "Au moins un serveur doit être marqué par défaut avant que toute demande parvienne à vos services.",
"components.Settings.nodefault": "Aucun serveur par défaut sélectionné !",
"components.Settings.no4kimplemented": "(Les serveurs 4K par défaut ne sont actuellement pas implémentés)",
"components.Settings.SonarrModal.testFirstRootFolders": "Testez votre connexion pour charger les dossiers racine",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Testez votre connexion pour charger des profils qualité",
"components.Settings.SonarrModal.loadingrootfolders": "Chargement des dossiers racine…",
"components.Settings.SonarrModal.loadingprofiles": "Chargement des profils qualité…",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Vous devez sélectionner une disponibilité minimale",
"components.Settings.RadarrModal.testFirstRootFolders": "Testez votre connexion pour charger les dossiers racine",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Testez votre connexion pour charger des profils qualité",
"components.Settings.RadarrModal.loadingrootfolders": "Chargement des dossiers racine…",
"components.Settings.RadarrModal.loadingprofiles": "Chargement des profils qualité…",
"components.TvDetails.showtype": "Type de série",
"components.TvDetails.network": "Réseau",
"components.TvDetails.anime": "Anime",
"components.Settings.toastSettingsSuccess": "Paramètres enregistrés.",
"components.Settings.toastSettingsFailure": "Une erreur s'est produite lors de l'enregistrement des paramètres.",
"components.Settings.toastApiKeySuccess": "Nouvelle clé API générée !",
"components.Settings.toastApiKeyFailure": "Une erreur s'est produite lors de la génération de la nouvelle clé API.",
"components.Settings.SonarrModal.animerootfolder": "Dossier racine pour anime",
"components.Settings.SonarrModal.animequalityprofile": "Profil qualité pour anime",
"components.MovieDetails.studio": "Studio"
}

352
src/i18n/locale/it.json Normal file
View File

@@ -0,0 +1,352 @@
{
"components.Discover.recentrequests": "Richieste recenti",
"components.Discover.recentlyAdded": "Aggiunti di recente",
"components.Discover.populartv": "Serie popolari",
"components.Discover.popularmovies": "Film popolari",
"components.Discover.nopending": "Nessuna richiesta in sospeso",
"components.Discover.discovertv": "Serie popolari",
"components.Discover.discovermovies": "Film popolari",
"components.UserEdit.email": "E-mail",
"components.Settings.Notifications.validationFromRequired": "È necessario fornire un indirizzo mittente di posta elettronica",
"components.Settings.Notifications.emailsettingssaved": "Impostazioni di notifica tramite posta elettronica salvate!",
"components.Settings.Notifications.emailsettingsfailed": "Impossibile salvare le impostazioni di notifica tramite posta elettronica.",
"components.Settings.Notifications.emailsender": "Indirizzo mittente di posta elettronica",
"components.Settings.SonarrModal.testing": "Test in corso…",
"components.Settings.RadarrModal.testing": "Test in corso…",
"components.Settings.address": "Indirizzo",
"components.Settings.SonarrModal.test": "Test",
"components.Settings.SonarrModal.ssl": "SSL",
"components.Settings.SonarrModal.saving": "Salvataggio…",
"components.Settings.SonarrModal.port": "Porta",
"components.Settings.SonarrModal.hostname": "Hostname",
"components.Settings.SettingsAbout.version": "Versione",
"components.Settings.RadarrModal.test": "Test",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.saving": "Salvataggio…",
"components.Settings.RadarrModal.port": "Porta",
"components.Settings.RadarrModal.hostname": "Hostname",
"components.Settings.Notifications.saving": "Salvataggio…",
"components.RequestModal.requesting": "Richiesta in corso…",
"components.MovieDetails.pending": "In sospeso",
"components.Settings.Notifications.agentenabled": "Agente abilitato",
"components.Search.searchresults": "Risultati di ricerca",
"components.RequestModal.status": "Stato",
"components.RequestModal.selectseason": "Seleziona stagioni",
"components.RequestModal.seasonnumber": "Stagione {number}",
"components.RequestModal.season": "Stagione",
"components.RequestModal.requestadmin": "La tua richiesta verrà approvata immediatamente.",
"components.RequestModal.requestSuccess": "<strong>{title}</strong> richiesto.",
"components.RequestModal.requestCancel": "Richiesta per <strong>{title}</strong> cancellata",
"components.RequestModal.request": "Richiedi",
"components.RequestModal.numberofepisodes": "Nº di episodi",
"components.RequestModal.notrequested": "Non richiesto",
"components.RequestModal.extras": "Extra",
"components.RequestModal.close": "Chiudi",
"components.RequestModal.cancelling": "Cancellazione…",
"components.RequestModal.cancel": "Cancella la richiesta",
"components.RequestList.status": "Stato",
"components.RequestList.requests": "Richieste",
"components.RequestList.requestedAt": "Richiesto",
"components.RequestList.previous": "Precedente",
"components.RequestList.next": "Successivo",
"components.RequestList.modifiedBy": "Ultima modifica da",
"components.RequestList.mediaInfo": "Media",
"components.RequestList.RequestItem.seasons": "Stagioni",
"components.RequestList.RequestItem.requestedby": "Richiesto da {username}",
"components.RequestList.RequestItem.notavailable": "n.a.",
"components.RequestCard.seasons": "Stagioni",
"components.RequestCard.requestedby": "Richiesto da {username}",
"components.RequestCard.all": "Tutte",
"components.RequestBlock.seasons": "Stagioni",
"components.PlexLoginButton.loginwithplex": "Accedi con Plex",
"components.PlexLoginButton.loggingin": "Accesso in corso…",
"components.PlexLoginButton.loading": "Caricamento…",
"components.PersonDetails.nobiography": "Biografia non disponibile.",
"components.PersonDetails.ascharacter": "come {character}",
"components.PersonDetails.appearsin": "Compare in",
"components.MovieDetails.userrating": "Voto pubblico",
"components.MovieDetails.unavailable": "Non disponibile",
"components.MovieDetails.studio": "Studio",
"components.MovieDetails.status": "Stato",
"components.MovieDetails.similarsubtext": "Altri film simili a {title}",
"components.MovieDetails.similar": "Titoli simili",
"components.MovieDetails.runtime": "{minutes} minuti",
"components.MovieDetails.revenue": "Incassi",
"components.MovieDetails.request": "Richiedi",
"components.MovieDetails.releasedate": "Data di uscita",
"components.MovieDetails.recommendationssubtext": "Se ti è piaciuto {title}, potrebbe interessarti…",
"components.MovieDetails.recommendations": "Consigliati",
"components.MovieDetails.overviewunavailable": "Trama non disponibile",
"components.MovieDetails.overview": "Trama",
"components.MovieDetails.originallanguage": "Lingua originale",
"components.MovieDetails.manageModalTitle": "Gestisci film",
"components.MovieDetails.manageModalRequests": "Richieste",
"components.MovieDetails.manageModalNoRequests": "Nessuna richiesta",
"components.MovieDetails.manageModalClearMediaWarning": "Questo rimuoverà tutti i dati, incluse le richieste per questo elemento in modo irreversibile. Se questo elemento esiste nella tua libreria di Plex, i dati verranno ricreati alla prossima sincronizzazione.",
"components.MovieDetails.manageModalClearMedia": "Cancella tutti i dati",
"components.MovieDetails.cancelrequest": "Annulla la richiesta",
"components.MovieDetails.decline": "Rifiuta",
"components.MovieDetails.cast": "Cast",
"components.MovieDetails.budget": "Budget",
"components.MovieDetails.available": "Disponibile",
"components.MovieDetails.approve": "Approva",
"components.MovieDetails.MovieCast.fullcast": "Cast completo",
"components.Login.signinplex": "Accedi per continuare",
"components.Layout.alphawarning": "Questo software è in ALPHA. Tutto può smettere di funzionare o diventare instabile. Aiutaci riportando i problemi su Overseerr GitHub!",
"components.Layout.UserDropdown.signout": "Esci",
"components.Layout.Sidebar.users": "Utenti",
"components.Layout.Sidebar.settings": "Impostazioni",
"components.Layout.Sidebar.requests": "Richieste",
"components.Layout.SearchInput.searchPlaceholder": "Cerca film e serie",
"components.Layout.LanguagePicker.changelanguage": "Cambia la lingua",
"components.Discover.upcomingmovies": "Film in uscita",
"components.Discover.upcoming": "Film in uscita",
"components.Discover.trending": "Di tendenza",
"components.UserEdit.avatar": "Avatar",
"components.UserEdit.edituser": "Modifica l'utente",
"components.UserEdit.settings": "Gestisci le impostazioni",
"components.UserEdit.saving": "Salvataggio…",
"components.UserEdit.save": "Salva",
"components.UserEdit.usersaved": "Utente salvato",
"components.UserEdit.usersDescription": "Concede l'autorizzazione per gestire gli utenti di Overseerr. Gli utenti con questa autorizzazione non possono modificare gli utenti con privilegi di amministratore o concederla.",
"components.UserEdit.users": "Gestisci gli utenti",
"components.UserEdit.username": "Nome utente",
"components.UserList.deleteconfirm": "Eliminare l'utente? Tutti i dati di richiesta esistenti da questo utente verranno rimossi.",
"components.UserList.delete": "Elimina",
"components.UserList.created": "Creato",
"components.UserList.admin": "Amministratore",
"components.UserList.role": "Ruolo",
"components.UserList.plexuser": "Utente Plex",
"components.UserList.lastupdated": "Ultimo aggiornamento",
"components.UserList.edit": "Modifica",
"components.UserList.deleteuser": "Elimina l'utente",
"components.UserList.userlist": "Elenco utenti",
"components.UserList.userdeleteerror": "Qualcosa è andato storto eliminando l'utente",
"components.UserList.userdeleted": "Utente eliminato",
"components.UserList.user": "Utente",
"components.UserList.totalrequests": "Totale richieste",
"i18n.declined": "Rifiutato",
"i18n.decline": "Rifiuta",
"i18n.cancel": "Annulla",
"i18n.available": "Disponibile",
"i18n.approved": "Approvato",
"i18n.approve": "Approva",
"components.UserList.usertype": "Tipo di utente",
"components.UserList.username": "Nome utente",
"i18n.unavailable": "Non disponibile",
"i18n.tvshows": "Serie",
"i18n.processing": "Elaborazione…",
"i18n.pending": "In sospeso",
"i18n.partiallyavailable": "Parzialmente disponibile",
"i18n.movies": "Film",
"i18n.deleting": "Eliminazione…",
"i18n.delete": "Elimina",
"pages.oops": "Ops",
"components.Settings.RadarrModal.rootfolder": "Cartella radice",
"components.Settings.RadarrModal.selectRootFolder": "Seleziona una cartella radice",
"components.Settings.RadarrModal.selectQualityProfile": "Seleziona un profilo qualità",
"components.Settings.RadarrModal.selectMinimumAvailability": "Seleziona la disponibilità minima",
"components.Settings.RadarrModal.save": "Salva i cambiamenti",
"components.Settings.RadarrModal.qualityprofile": "Profilo qualità",
"components.Settings.RadarrModal.minimumAvailability": "Disponibilità minima",
"components.Settings.RadarrModal.editradarr": "Modifica server Radarr",
"components.Settings.RadarrModal.defaultserver": "Server predefinito",
"components.Settings.RadarrModal.apiKey": "Chiave API",
"components.Settings.RadarrModal.add": "Aggiungi un server",
"components.Settings.Notifications.save": "Salva i cambiamenti",
"components.Settings.Notifications.discordsettingsfailed": "Impossibile salvare le impostazioni di notifica Discord.",
"components.RequestModal.requesttitle": "Richiedi {title}",
"components.RequestModal.requestfrom": "Al momento è in sospeso una richiesta da {username}",
"components.RequestModal.cancelrequest": "Questo rimuoverà la richiesta. Continuare?",
"components.RequestModal.pendingrequest": "Richiesta in sospeso per {title}",
"components.MovieDetails.viewrequest": "Visualizza la richiesta",
"components.Layout.Sidebar.dashboard": "Esplora",
"components.TvDetails.manageModalTitle": "Gestisci serie",
"components.TvDetails.manageModalRequests": "Richieste",
"components.TvDetails.manageModalNoRequests": "Nessuna richiesta",
"components.TvDetails.manageModalClearMedia": "Cancella tutti i dati multimediali",
"components.TvDetails.declinerequests": "Rifiuta {requestCount} {requestCount, plural, one {Request} other {Requests}}",
"components.TvDetails.decline": "Rifiuta",
"components.TvDetails.cast": "Cast",
"components.TvDetails.cancelrequest": "Annulla la richiesta",
"components.TvDetails.available": "Disponibile",
"components.TvDetails.approverequests": "Approva {requestCount} {requestCount, plural, one {Request} other {Requests}}",
"components.TvDetails.approve": "Approva",
"components.TvDetails.anime": "Anime",
"components.TvDetails.TvCast.fullseriescast": "Cast completo della serie",
"components.TitleCard.tvshow": "Serie",
"components.Slider.noresults": "Nessun risultato",
"components.Setup.welcome": "Benvenuti in Overseerr",
"components.TitleCard.movie": "Film",
"components.Setup.loginwithplex": "Accedi con Plex",
"components.Setup.configureplex": "Configurare Plex",
"components.Settings.validationPortRequired": "È necessario fornire una porta",
"components.Settings.validationHostnameRequired": "È necessario fornire un nome host/IP",
"components.Settings.toastSettingsSuccess": "Impostazioni salvate.",
"components.Settings.syncing": "Sincronizzazione…",
"components.Settings.startscan": "Avvia la scansione",
"components.Settings.ssl": "SSL",
"components.Settings.saving": "Salvataggio…",
"components.Settings.save": "Salva i cambiamenti",
"components.Settings.runnow": "Esegui ora",
"components.Settings.radarrsettings": "Impostazioni Radarr",
"components.Settings.plexsettings": "Impostazioni Plex",
"components.Settings.notrunning": "Non in esecuzione",
"components.Settings.notificationsettingsDescription": "Qui puoi scegliere quali tipi di notifiche inviare e attraverso quali tipi di servizi.",
"components.Settings.notificationsettings": "Impostazioni di notifica",
"components.Settings.nodefaultdescription": "Almeno un server deve essere contrassegnato come predefinito prima che qualsiasi richiesta lo apportare ai servizi.",
"components.Settings.nodefault": "Nessun server predefinito selezionato!",
"components.Settings.no4kimplemented": "(I server 4K predefiniti non sono attualmente implementati)",
"components.Settings.nextexecution": "Esecuzione successiva",
"components.Settings.menuServices": "Servizi",
"components.Settings.menuPlexSettings": "Plex",
"components.Settings.menuNotifications": "Notifiche",
"components.Settings.menuLogs": "Registri",
"components.Settings.menuGeneralSettings": "Impostazioni generali",
"components.Settings.menuAbout": "Informazioni",
"components.Settings.manualscan": "Scansione manuale della biblioteca",
"components.Settings.librariesRemaining": "Biblioteche rimanenti: {count}",
"components.Settings.hostname": "Nome host/IP",
"components.Settings.generalsettingsDescription": "Queste sono impostazioni relative alla configurazione generale di Overseerr.",
"components.Settings.generalsettings": "Impostazioni generali",
"components.Settings.edit": "Modifica",
"components.Settings.deleteserverconfirm": "Sei sicuro/a di voler eliminare questo server?",
"components.Settings.delete": "Elimina",
"components.Settings.default4k": "4K predefinito",
"components.Settings.default": "Predefinito",
"components.Settings.currentlibrary": "Biblioteca corrente: {name}",
"components.Settings.copied": "Chiave API copiata negli appunti",
"components.Settings.cancelscan": "Annulla l'analisi",
"components.Settings.applicationurl": "URL applicazione",
"components.Settings.apikey": "Chiave API",
"components.Settings.addsonarr": "Aggiungi un server Sonarr",
"components.Settings.addradarr": "Aggiungi un server radarr",
"components.Settings.activeProfile": "Profilo attivo",
"components.Settings.SonarrModal.validationRootFolderRequired": "È necessario selezionare una cartella radice",
"components.Settings.SonarrModal.validationProfileRequired": "È necessario selezionare un profilo",
"components.Settings.SonarrModal.validationPortRequired": "È necessario fornire una porta",
"components.Settings.SonarrModal.validationNameRequired": "È necessario fornire un nome di server",
"components.Settings.SonarrModal.validationHostnameRequired": "È necessario fornire un nome host/IP",
"components.Settings.SonarrModal.validationApiKeyRequired": "È necessario fornire una chiave API",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Stabilita la connessione Sonarr!",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Impossibile connettersi a Sonarr Server",
"components.Settings.SonarrModal.testFirstRootFolders": "Testa la connessione per caricare le cartelle radice",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Testa la connessione per caricare profili di qualità",
"components.Settings.SonarrModal.servernamePlaceholder": "Un server Sonarr",
"components.Settings.SonarrModal.servername": "Nome server",
"components.Settings.SonarrModal.server4k": "Server 4K",
"components.Settings.SonarrModal.selectRootFolder": "Seleziona una cartella radice",
"components.Settings.SonarrModal.selectQualityProfile": "Seleziona un profilo qualità",
"components.Settings.SonarrModal.seasonfolders": "Cartelle stagione",
"components.Settings.SonarrModal.save": "Salva i cambiamenti",
"components.Settings.SonarrModal.rootfolder": "Cartella radice",
"components.Settings.SonarrModal.qualityprofile": "Profilo qualità",
"components.Settings.SonarrModal.loadingrootfolders": "Caricamento delle cartelle radice in…",
"components.Settings.SonarrModal.loadingprofiles": "Caricamento profili qualità…",
"components.Settings.SonarrModal.editsonarr": "Modifica server Sonarr",
"components.Settings.SonarrModal.defaultserver": "Server predefinito",
"components.Settings.SonarrModal.createsonarr": "Crea nuovo server Sonarr",
"components.Settings.SonarrModal.baseUrlPlaceholder": "Esempio: /sonarr",
"components.Settings.SonarrModal.baseUrl": "URL di base",
"components.Settings.SonarrModal.apiKeyPlaceholder": "La tua chiave API Sonarr",
"components.Settings.SonarrModal.apiKey": "Chiave API",
"components.Settings.SonarrModal.animerootfolder": "Cartella radice anime",
"components.Settings.SonarrModal.animequalityprofile": "Profilo qualità anime",
"components.Settings.SonarrModal.add": "Aggiungi un server",
"components.Settings.SettingsAbout.totalrequests": "Totale richieste",
"components.Settings.SettingsAbout.totalmedia": "Media totali",
"components.Settings.SettingsAbout.overseerrinformation": "Informazioni su Overseerr",
"components.Settings.SettingsAbout.githubdiscussions": "Discussioni su GitHub",
"components.Settings.SettingsAbout.gettingsupport": "Ottieni aiuto",
"components.Settings.SettingsAbout.clickheretojoindiscord": "Clicca qui per unirti al nostro server Discord.",
"components.Settings.RadarrModal.validationRootFolderRequired": "È necessario selezionare una cartella radice",
"components.Settings.RadarrModal.validationProfileRequired": "È necessario selezionare un profilo",
"components.Settings.RadarrModal.validationPortRequired": "È necessario fornire una porta",
"components.Settings.RadarrModal.validationNameRequired": "È necessario fornire un nome di server",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "È necessario selezionare la disponibilità minima",
"components.Settings.RadarrModal.validationHostnameRequired": "È necessario fornire un nome host/IP",
"components.Settings.RadarrModal.validationApiKeyRequired": "È necessario fornire una chiave API",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Stabilita la connessione Radarr!",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Impossibile connettersi al server Radarr",
"components.Settings.RadarrModal.testFirstRootFolders": "Testa la connessione per caricare le cartelle radice",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Testa la connessione per caricare profili qualità",
"components.Settings.RadarrModal.servernamePlaceholder": "Un server Radarr",
"components.Settings.RadarrModal.servername": "Nome server",
"components.Settings.RadarrModal.server4k": "Server 4K",
"components.Settings.RadarrModal.loadingrootfolders": "Caricamento delle cartelle radice in…",
"components.Settings.RadarrModal.loadingprofiles": "Caricamento profili di qualità…",
"components.Settings.RadarrModal.createradarr": "Crea un nuovo server Radarr",
"components.Settings.RadarrModal.baseUrlPlaceholder": "Esempio: /radarr",
"components.Settings.RadarrModal.baseUrl": "URL di base",
"components.Settings.RadarrModal.apiKeyPlaceholder": "La tua chiave API Radarr",
"components.Settings.Notifications.webhookUrlPlaceholder": "Impostazioni server -> integrazioni -> Webhook",
"components.Settings.Notifications.webhookUrl": "URL webhook",
"components.Settings.Notifications.validationWebhookUrlRequired": "È necessario fornire un URL webhook",
"components.Settings.Notifications.validationSmtpPortRequired": "È necessario fornire una porta SMTP",
"components.Settings.Notifications.validationSmtpHostRequired": "È necessario fornire un host SMTP",
"components.Settings.Notifications.smtpPort": "Porta SMTP",
"components.Settings.Notifications.smtpHost": "Host SMTP",
"components.Settings.Notifications.enableSsl": "Abilita SSL",
"components.Settings.Notifications.discordsettingssaved": "Impostazioni di notifica Discord salvate!",
"components.Settings.Notifications.authUser": "Nome utente",
"components.Settings.Notifications.authPass": "Password",
"components.RequestModal.requestseasons": "Richiedi {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestList.showingresults": "Visualizzazione dei risultati da <strong>{from}</strong> a <strong>{to}</strong> di <strong>{total}</strong>",
"components.UserEdit.userfail": "Qualcosa è andato storto salvando l'utente.",
"components.UserEdit.requestDescription": "Concede l'autorizzazione per richiedere film e serie.",
"components.UserEdit.request": "Richiesta",
"components.UserEdit.permissions": "Autorizzazioni",
"components.UserEdit.managerequestsDescription": "Concede l'autorizzazione per gestire le richieste Overseerr. Ciò include l'approvazione e la negazione delle richieste.",
"components.UserEdit.managerequests": "Gestisci le richieste",
"components.UserEdit.autoapproveDescription": "Concede l'approvazione automatica per tutte le richieste effettuate da questo utente.",
"components.UserEdit.autoapprove": "Approvazione automatica",
"components.UserEdit.adminDescription": "Accesso completo all'amministratore. Ignora tutti i controlli delle autorizzazioni.",
"components.UserEdit.admin": "Amministratore",
"components.TvDetails.userrating": "Voto pubblico",
"components.TvDetails.unavailable": "Non disponibile",
"components.TvDetails.status": "Stato",
"components.TvDetails.similarsubtext": "Altre serie simili a {title}",
"components.TvDetails.similar": "Serie simile",
"components.TvDetails.showtype": "Tipo di serie",
"components.TvDetails.requestmore": "Richiedi di più",
"components.TvDetails.request": "Richiesta",
"components.TvDetails.recommendationssubtext": "Se ti è piaciuto {title}, potrebbe anche piacerti…",
"components.TvDetails.recommendations": "Consigli",
"components.TvDetails.pending": "In sospeso",
"components.TvDetails.overviewunavailable": "Trama non disponibile",
"components.TvDetails.overview": "Trama",
"components.TvDetails.originallanguage": "Lingua originale",
"components.TvDetails.network": "Rete",
"components.Setup.finishing": "Finalizzazione…",
"components.Settings.menuJobs": "Task",
"components.Settings.jobname": "Nome Task",
"components.UserEdit.voteDescription": "Concede il permesso di votare sulle richieste (sistema di voto non acora implementato)",
"components.UserEdit.vote": "Vota",
"components.UserEdit.settingsDescription": "Concede l'autorizzazione a modificare tutte le impostazioni di Overseerr. Un utente deve disporre di questa autorizzazione per concederla ad altri.",
"components.TvDetails.manageModalClearMediaWarning": "Questo rimuoverà tutti i dati, incluse le richieste per questo elemento in modo irreversibile. Se questo elemento esiste nella tua libreria di Plex, i dati verranno ricreati alla prossima sincronizzazione.",
"components.Setup.syncingbackground": "La sincronizzazione verrà eseguita in background. Nel frattempo puoi continuare il processo di configurazione.",
"components.Setup.signinMessage": "Comincia accedendo con il tuo account Plex",
"components.Settings.sync": "Sincronizza le librerie di Plex",
"components.Settings.sonarrsettings": "Impostazioni Sonarr",
"components.Settings.sonarrSettingsDescription": "Imposta la connessione al tuo Sonarr qui sotto. È possibile avere più instanze, ma solo due attive come predefinite nello stesso momento (uno per l'HD standard e uno per 4K). Gli amministratori possono ignorare quale server è utilizzato per le nuove richieste.",
"components.Settings.radarrSettingsDescription": "Imposta la connessione al tuo Radarr qui sotto. È possibile avere più instanze, ma solo due attive come predefinite nello stesso momento (uno per l'HD standard e uno per 4K). Gli amministratori possono ignorare quale server è utilizzato per le nuove richieste.",
"components.Settings.plexsettingsDescription": "Configura le impostazioni per il tuo server Plex. Overseerr utilizza il tuo server Plex per scansionare la tua libreria a intervealli per vedere quali elementi sono disponibili.",
"components.Settings.plexlibrariesDescription": "Le librerie che verranno scansionate da Overseerr alla ricerca di titoli. Se non ci sono librerie, configura e salva le impostazioni di connessione a Plex e fai click sul pulsante qui sotto.",
"components.Settings.plexlibraries": "Librerie Plex",
"components.Settings.manualscanDescription": "Normalmente, questo verrà eseguito ogni 6 ore. Overseerr controllerà in modo più aggressivo i server Plex aggiunti di recente. Se è la prima volta che configuri Plex, si consiglia una scansione manuale completa della libreria!",
"components.Settings.port": "Porta",
"components.Settings.servername": "Nome server (impostato automaticamente dopo il salvataggio)",
"components.Settings.servernamePlaceholder": "Nome server Plex",
"components.Settings.toastSettingsFailure": "Qualcosa è andato storto durante il salvataggio delle impostazioni.",
"components.Settings.toastApiKeySuccess": "Nuova chiave API generata!",
"components.Settings.toastApiKeyFailure": "Qualcosa è andato storto generando una nuova chiave API.",
"components.Setup.configureservices": "Configura i servizi",
"components.Setup.finish": "Termina la configurazione",
"components.Setup.continue": "Continua",
"components.Setup.tip": "Consiglio",
"pages.internalServerError": "{statusCode} — Errore interno del server",
"pages.somethingWentWrong": "{statusCode} — Qualcosa è andato storto",
"pages.serviceUnavailable": "{statusCode} — Servizio non disponibile",
"pages.returnHome": "Ritorna alla pagina iniziale",
"pages.pageNotFound": "404 — Pagina non trovata"
}

View File

@@ -34,7 +34,7 @@
"components.MovieDetails.overviewunavailable": "ストーリー情報がありません",
"components.MovieDetails.pending": "リクエスト中",
"components.MovieDetails.recommendations": "オススメの作品",
"components.MovieDetails.recommendationssubtext": "{title}が好きだった人は、こんなのも好きかもしれません...",
"components.MovieDetails.recommendationssubtext": "{title}が好きだった人は、こんなのも好きかもしれません",
"components.MovieDetails.releasedate": "公開日",
"components.MovieDetails.request": "リクエストする",
"components.MovieDetails.revenue": "収益",
@@ -67,7 +67,7 @@
"components.RequestList.showingresults": "<strong>{total}</strong>中<strong>{from}</strong><strong>{to}</strong>件",
"components.RequestList.status": "状態",
"components.RequestModal.cancel": "キャンセルリクエスト",
"components.RequestModal.cancelling": "取り消し中...",
"components.RequestModal.cancelling": "取り消し中",
"components.RequestModal.cancelrequest": "このリクエストをキャンセルしてよろしいですか?",
"components.RequestModal.close": "閉じる",
"components.RequestModal.extras": "おまけ",
@@ -93,7 +93,7 @@
"components.Settings.Notifications.emailsender": "配信元メールアドレス",
"components.Settings.Notifications.enableSsl": "SSLを有効にする",
"components.Settings.Notifications.save": "変更を保存",
"components.Settings.Notifications.saving": "保存中...",
"components.Settings.Notifications.saving": "保存中",
"components.Settings.Notifications.smtpHost": "SMTPホスト",
"components.Settings.Notifications.smtpPort": "SMTPポート",
"components.Settings.Notifications.validationFromRequired": "配信元メールアドレスの入力は必要です",
@@ -116,7 +116,7 @@
"components.Settings.RadarrModal.qualityprofile": "画質プロファイル",
"components.Settings.RadarrModal.rootfolder": "ルートフォルダー",
"components.Settings.RadarrModal.save": "変更を保存",
"components.Settings.RadarrModal.saving": "保存中...",
"components.Settings.RadarrModal.saving": "保存中",
"components.Settings.RadarrModal.selectMinimumAvailability": "最低リリース状況を選ぶ",
"components.Settings.RadarrModal.selectQualityProfile": "画質プロファイルを選ぶ",
"components.Settings.RadarrModal.selectRootFolder": "ルートフォルダーを選ぶ",
@@ -125,7 +125,7 @@
"components.Settings.RadarrModal.servernamePlaceholder": "Radarrサーバー",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.test": "テストする",
"components.Settings.RadarrModal.testing": "テスト中...",
"components.Settings.RadarrModal.testing": "テスト中",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Radarrサーバーの接続は失敗しました",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Radarrサーバーの接続は成功しました!",
"components.Settings.RadarrModal.validationApiKeyRequired": "APIキーの入力が必要です",
@@ -146,7 +146,7 @@
"components.Settings.SonarrModal.qualityprofile": "画質プロファイル",
"components.Settings.SonarrModal.rootfolder": "ルートフォルダー",
"components.Settings.SonarrModal.save": "変更を保存",
"components.Settings.SonarrModal.saving": "保存中...",
"components.Settings.SonarrModal.saving": "保存中",
"components.Settings.SonarrModal.seasonfolders": "シーズンフォルダー",
"components.Settings.SonarrModal.selectQualityProfile": "画質プロファイルを選ぶ",
"components.Settings.SonarrModal.selectRootFolder": "ルートフォルダーを選ぶ",
@@ -155,7 +155,7 @@
"components.Settings.SonarrModal.servernamePlaceholder": "Sonarrサーバー",
"components.Settings.SonarrModal.ssl": "SSL",
"components.Settings.SonarrModal.test": "テストする",
"components.Settings.SonarrModal.testing": "テスト中...",
"components.Settings.SonarrModal.testing": "テスト中",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Sonarrサーバーに接続できませんでした",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Sonarrサーバーの接続が成功しました!",
"components.Settings.SonarrModal.validationApiKeyRequired": "APIキーの入力が必要です",
@@ -204,7 +204,7 @@
"components.Settings.radarrsettings": "Radarr設定",
"components.Settings.runnow": "今すぐ実行",
"components.Settings.save": "変更を保存",
"components.Settings.saving": "保存中...",
"components.Settings.saving": "保存中",
"components.Settings.servername": "サーバー名 (保存時に自動で設定されます)",
"components.Settings.servernamePlaceholder": "Plexサーバー名",
"components.Settings.sonarrSettingsDescription": "Sonarr接続設定。複数のサーバーを繋ぐことができますが、デフォルトで常にアクティブなのは2つのみ1つは標準HD用、もう1つは4K用。管理者は、新しいリクエストに使用するサーバーを選択することができます。",
@@ -217,7 +217,7 @@
"components.Setup.configureservices": "連携サービスの設定",
"components.Setup.continue": "続行",
"components.Setup.finish": "セットアップを完了",
"components.Setup.finishing": "保存中...",
"components.Setup.finishing": "保存中",
"components.Setup.loginwithplex": "Plexでログイン",
"components.Setup.signinMessage": "Plexアカウントでログインして始める",
"components.Setup.welcome": "Overseerrへようこそ",
@@ -241,7 +241,7 @@
"components.TvDetails.overviewunavailable": "ストーリー情報がありません",
"components.TvDetails.pending": "リクエスト中",
"components.TvDetails.recommendations": "オススメの作品",
"components.TvDetails.recommendationssubtext": "{title}が好きだった人は、こんなのも好きかもしれません...",
"components.TvDetails.recommendationssubtext": "{title}が好きだった人は、こんなのも好きかもしれません",
"components.TvDetails.request": "リクエストする",
"components.TvDetails.requestmore": "もっとリクエストする",
"components.TvDetails.similar": "類似シリーズ",
@@ -262,7 +262,7 @@
"components.UserEdit.request": "リクエストする",
"components.UserEdit.requestDescription": "映画やテレビ番組のリクエスト権限を付与します。",
"components.UserEdit.save": "保存",
"components.UserEdit.saving": "保存中...",
"components.UserEdit.saving": "保存中",
"components.UserEdit.settings": "設定の管理",
"components.UserEdit.settingsDescription": "すべてのOverseerrの設定を変更する権限を付与します。他の人にこの権限を付与するには、ユーザーがこの権限を持っていなければなりません。",
"components.UserEdit.userfail": "ユーザーの保存する時に問題が発生しました。",
@@ -319,5 +319,34 @@
"components.Settings.Notifications.emailsettingsfailed": "メール通知設定の保存に失敗しました。",
"components.Settings.Notifications.discordsettingsfailed": "ディスコード通知設定の保存に失敗しました。",
"components.Settings.Notifications.discordsettingssaved": "ディスコードの通知設定が保存されました!",
"components.MovieDetails.MovieCast.fullcast": "フルキャスト"
"components.MovieDetails.MovieCast.fullcast": "フルキャスト",
"i18n.deleting": "削除中…",
"components.UserList.userdeleteerror": "ユーザーの削除する時に問題が発生しました",
"components.UserList.userdeleted": "ユーザーが削除されました",
"components.UserList.deleteuser": "ユーザーの削除",
"components.UserList.deleteconfirm": "このユーザーを削除しますか?このユーザーの既存のリクエストデータはすべて削除されます。",
"components.TvDetails.showtype": "番組タイプ",
"components.TvDetails.network": "ネットワーク",
"components.TvDetails.anime": "アニメ",
"components.Setup.tip": "ヒント",
"components.Setup.syncingbackground": "同期はバックグラウンドで実行されます。その間、セットアッププロセスを続行できます。",
"components.Settings.toastSettingsSuccess": "設定の変更は保存しました。",
"components.Settings.toastSettingsFailure": "設定保存中に問題が発生しました。",
"components.Settings.toastApiKeySuccess": "新しい API キーが生成されました。",
"components.Settings.toastApiKeyFailure": "新しい API キーの生成中に問題が発生しました。",
"components.Settings.nodefaultdescription": "サービスへのリクエストを行う前に、一つのサーバーをデフォルトとして設定する必要があります。",
"components.Settings.nodefault": "デフォルトサーバーが選択されていません。",
"components.Settings.no4kimplemented": "( 4K サーバーのデフォルト設定は未実装)",
"components.Settings.SonarrModal.testFirstRootFolders": "ルートフォルダーをロードするには先に接続をテストしてください",
"components.Settings.SonarrModal.testFirstQualityProfiles": "画質プロファイルをロードするには先に接続をテストしてください",
"components.Settings.SonarrModal.loadingrootfolders": "ルートフォルダー読込中…",
"components.Settings.SonarrModal.loadingprofiles": "画質プロファイル読込中…",
"components.Settings.SonarrModal.animerootfolder": "アニメルートフォルダー",
"components.Settings.SonarrModal.animequalityprofile": "アニメ画質プロファイル",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "最低リリース状況を選択してください",
"components.Settings.RadarrModal.testFirstRootFolders": "ルートフォルダーをロードするには先に接続をテストしてください",
"components.Settings.RadarrModal.testFirstQualityProfiles": "画質プロファイルをロードするには先に接続をテストしてください",
"components.Settings.RadarrModal.loadingprofiles": "画質プロファイルの読込中…",
"components.Settings.RadarrModal.loadingrootfolders": "ルートフォルダー読込中…",
"components.MovieDetails.studio": "スタジオ"
}

View File

@@ -34,7 +34,7 @@
"components.MovieDetails.overviewunavailable": "Overzicht onbeschikbaar",
"components.MovieDetails.pending": "Wachtende",
"components.MovieDetails.recommendations": "Aanbevelingen",
"components.MovieDetails.recommendationssubtext": "Als je {title} leuk vond, vind je dit misschien ook leuk...",
"components.MovieDetails.recommendationssubtext": "Als je {title} leuk vond, vind je dit misschien ook leuk",
"components.MovieDetails.releasedate": "Releasedatum",
"components.MovieDetails.request": "Verzoek",
"components.MovieDetails.revenue": "Omzet",
@@ -48,8 +48,8 @@
"components.PersonDetails.appearsin": "Verschijnt in",
"components.PersonDetails.ascharacter": "als {character}",
"components.PersonDetails.nobiography": "Geen biografie beschikbaar.",
"components.PlexLoginButton.loading": "Bezig met laden...",
"components.PlexLoginButton.loggingin": "Bezig met inloggen...",
"components.PlexLoginButton.loading": "Bezig met laden",
"components.PlexLoginButton.loggingin": "Bezig met inloggen",
"components.PlexLoginButton.loginwithplex": "Login met Plex",
"components.RequestBlock.seasons": "Seizoenen",
"components.RequestCard.all": "Alle",
@@ -67,7 +67,7 @@
"components.RequestList.showingresults": "<strong>{from}</strong> tot <strong>{to}</strong> van de <strong>{total}</strong> resultaten worden weergegeven",
"components.RequestList.status": "Status",
"components.RequestModal.cancel": "Verzoek Annuleren",
"components.RequestModal.cancelling": "Bezig met annuleren...",
"components.RequestModal.cancelling": "Bezig met annuleren",
"components.RequestModal.cancelrequest": "Hiermee wordt je verzoek verwijderd. Weet je zeker dat je door wilt gaan?",
"components.RequestModal.close": "Sluiten",
"components.RequestModal.extras": "Extra",
@@ -93,7 +93,7 @@
"components.Settings.Notifications.emailsender": "E-mailadres Van Afzender",
"components.Settings.Notifications.enableSsl": "Schakel SSL in",
"components.Settings.Notifications.save": "Wijzigingen Opslaan",
"components.Settings.Notifications.saving": "Bezig met opslaan...",
"components.Settings.Notifications.saving": "Bezig met opslaan",
"components.Settings.Notifications.smtpHost": "SMTP Host",
"components.Settings.Notifications.smtpPort": "SMTP Poort",
"components.Settings.Notifications.validationFromRequired": "Je moet een afzenderadres opgeven",
@@ -116,7 +116,7 @@
"components.Settings.RadarrModal.qualityprofile": "Kwaliteitsprofiel",
"components.Settings.RadarrModal.rootfolder": "Hoofdmap",
"components.Settings.RadarrModal.save": "Wijzigingen Opslaan",
"components.Settings.RadarrModal.saving": "Bezig met opslaan...",
"components.Settings.RadarrModal.saving": "Bezig met opslaan",
"components.Settings.RadarrModal.selectMinimumAvailability": "Selecteer minimale beschikbaarheid",
"components.Settings.RadarrModal.selectQualityProfile": "Selecteer een kwaliteitsprofiel",
"components.Settings.RadarrModal.selectRootFolder": "Selecteer een Hoofdmap",
@@ -125,7 +125,7 @@
"components.Settings.RadarrModal.servernamePlaceholder": "Een Radarr Server",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.test": "Test",
"components.Settings.RadarrModal.testing": "Bezig met testen...",
"components.Settings.RadarrModal.testing": "Bezig met testen",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Kon niet verbinden met de Radarr Server",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Verbonden met de Radarr server!",
"components.Settings.RadarrModal.validationApiKeyRequired": "Je moet een API key opgeven",
@@ -146,7 +146,7 @@
"components.Settings.SonarrModal.qualityprofile": "Kwaliteitsprofiel",
"components.Settings.SonarrModal.rootfolder": "Basisfolder",
"components.Settings.SonarrModal.save": "Wijzigingen Opslaan",
"components.Settings.SonarrModal.saving": "Bezig met opslaan...",
"components.Settings.SonarrModal.saving": "Bezig met opslaan",
"components.Settings.SonarrModal.seasonfolders": "Seizoenmappen",
"components.Settings.SonarrModal.selectQualityProfile": "Selecteer een Kwaliteitsprofiel",
"components.Settings.SonarrModal.selectRootFolder": "Selecteer een hoofdfolder",
@@ -155,7 +155,7 @@
"components.Settings.SonarrModal.servernamePlaceholder": "Een Sonarr Server",
"components.Settings.SonarrModal.ssl": "SSL",
"components.Settings.SonarrModal.test": "Test",
"components.Settings.SonarrModal.testing": "Bezig met testen...",
"components.Settings.SonarrModal.testing": "Bezig met testen",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Kon niet verbinden met de Sonarr Server",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Verbonden met de Sonarr server!",
"components.Settings.SonarrModal.validationApiKeyRequired": "Je moet een API key opgeven",
@@ -204,7 +204,7 @@
"components.Settings.radarrsettings": "Radarr Instellingen",
"components.Settings.runnow": "Nu Starten",
"components.Settings.save": "Wijzigingen Opslaan",
"components.Settings.saving": "Bezig met opslaan...",
"components.Settings.saving": "Bezig met opslaan",
"components.Settings.servername": "Servernaam (Automatisch ingesteld na opslaan)",
"components.Settings.servernamePlaceholder": "Plex Servernaam",
"components.Settings.sonarrSettingsDescription": "Stel hier onder je Sonarr connectie in. Je kan er meerdere hebben, maar slechts twee actief hebben als standaard (één voor standaard HD, en één voor 4K). Beheerders kunnen bepalen welke server gebruikt wordt voor nieuwe verzoeken.",
@@ -217,7 +217,7 @@
"components.Setup.configureservices": "Configureer Diensten",
"components.Setup.continue": "Doorgaan",
"components.Setup.finish": "Installatie Voltooien",
"components.Setup.finishing": "Bezig met voltooien...",
"components.Setup.finishing": "Bezig met voltooien",
"components.Setup.loginwithplex": "Login met Plex",
"components.Setup.signinMessage": "Ga aan de slag door in te loggen met je Plex account",
"components.Setup.welcome": "Welcome bij Overseerr",
@@ -241,7 +241,7 @@
"components.TvDetails.overviewunavailable": "Overzicht onbeschikbaar",
"components.TvDetails.pending": "Wachtende",
"components.TvDetails.recommendations": "Aanbevelingen",
"components.TvDetails.recommendationssubtext": "Als je {title} leuk vond, vind je dit misschien ook leuk...",
"components.TvDetails.recommendationssubtext": "Als je {title} leuk vond, vind je dit misschien ook leuk",
"components.TvDetails.request": "Verzoek",
"components.TvDetails.requestmore": "Verzoek Meer",
"components.TvDetails.similar": "Soortgelijke Series",
@@ -262,7 +262,7 @@
"components.UserEdit.request": "Verzoek",
"components.UserEdit.requestDescription": "Geeft toestemming om films en series aan te vragen.",
"components.UserEdit.save": "Sla op",
"components.UserEdit.saving": "Bezig met opslaan...",
"components.UserEdit.saving": "Bezig met opslaan",
"components.UserEdit.settings": "Instellingen Beheren",
"components.UserEdit.settingsDescription": "Geeft toestemming om alle Overseerr-instellingen te wijzigen. Een gebruiker moet deze toestemming hebben om deze aan anderen te verlenen.",
"components.UserEdit.userfail": "Er ging iets verkeerd bij het opslaan van de gebruiker.",

232
src/i18n/locale/pt_BR.json Normal file
View File

@@ -0,0 +1,232 @@
{
"components.RequestList.requestedAt": "Solicitado Em",
"components.RequestList.next": "Próxima",
"components.RequestList.modifiedBy": "Última Mudança Feita Por",
"components.RequestList.mediaInfo": "Títulos",
"components.RequestList.RequestItem.requestedby": "Solicitado por {username}",
"components.RequestCard.seasons": "Temporadas",
"components.RequestCard.requestedby": "Solicitado por {username}",
"components.RequestBlock.seasons": "Temporadas",
"components.PlexLoginButton.loginwithplex": "Entrar com",
"components.PlexLoginButton.loggingin": "Fazendo login...",
"components.PlexLoginButton.loading": "Carregando...",
"components.PersonDetails.nobiography": "Biografia não disponível.",
"components.PersonDetails.ascharacter": "como {character}",
"components.PersonDetails.appearsin": "Aparece em",
"components.MovieDetails.viewrequest": "Ver solicitação",
"components.MovieDetails.userrating": "Avaliação do usuário",
"components.MovieDetails.unavailable": "Indisponível",
"components.MovieDetails.studio": "Estúdio",
"components.MovieDetails.status": "Detalhes",
"components.MovieDetails.similarsubtext": "Outros filmes semelhantes a {title}",
"components.MovieDetails.similar": "Títulos Semelhantes",
"components.MovieDetails.runtime": "{minutes} minutos",
"components.MovieDetails.revenue": "Receita",
"components.MovieDetails.request": "Solicitar",
"components.MovieDetails.releasedate": "Data de Lançamento",
"components.MovieDetails.recommendationssubtext": "Se você gostou {title}, você provavelmente irá gostar...",
"components.MovieDetails.recommendations": "Recomendações",
"components.MovieDetails.pending": "Pendente",
"components.MovieDetails.overviewunavailable": "Visão Geral Indisponível",
"components.MovieDetails.overview": "Visão Geral",
"components.MovieDetails.originallanguage": "Língua original",
"components.MovieDetails.manageModalTitle": "Gerenciar Filme",
"components.MovieDetails.manageModalRequests": "Solicitações",
"components.MovieDetails.manageModalNoRequests": "Nenhuma Solicitação",
"components.MovieDetails.manageModalClearMediaWarning": "Isso irá remover em definitivo todos dados de mídia, incluindo todas solicitações para esse item. Se este item existir in sua biblioteca do Plex, os dados de mídia serão recriados na próxima sincronia.",
"components.MovieDetails.manageModalClearMedia": "Limpar Todos Dados de Mídia",
"components.MovieDetails.decline": "Rejeitar",
"components.MovieDetails.cast": "Elenco",
"components.MovieDetails.cancelrequest": "Cancelar Solicitação",
"components.MovieDetails.budget": "Orçamento",
"components.MovieDetails.available": "Disponível",
"components.MovieDetails.approve": "Aprovar",
"components.MovieDetails.MovieCast.fullcast": "Elenco Completo",
"components.Login.signinplex": "Faça login para continuar",
"components.Layout.alphawarning": "Esta é uma versão Alpha. Quase tudo é instável ou não funciona. Por favor reporte os problemas no GitHub do Overseerr!",
"components.Layout.UserDropdown.signout": "Sair",
"components.Layout.Sidebar.users": "Usuários",
"components.Layout.Sidebar.settings": "Configurações",
"components.Layout.Sidebar.requests": "Solicitações",
"components.Layout.Sidebar.dashboard": "Descobrir",
"components.Layout.SearchInput.searchPlaceholder": "Pesquisar Filmes & Séries",
"components.Layout.LanguagePicker.changelanguage": "Alterar Idioma",
"components.Discover.upcomingmovies": "Em Breve",
"components.Discover.upcoming": "Em Breve",
"components.Discover.trending": "Em Alta",
"components.Discover.recentrequests": "Solicitações Recentes",
"components.Discover.recentlyAdded": "Adicionado recentemente",
"components.Discover.populartv": "Séries Populares",
"components.Discover.popularmovies": "Filmes Populares",
"components.Discover.nopending": "Nenhuma Solicitação Pendente",
"components.Discover.discovertv": "Séries Populares",
"components.Discover.discovermovies": "Filmes Populares",
"components.Settings.SonarrModal.testing": "Testando...",
"components.Settings.SonarrModal.saving": "Salvando...",
"components.Settings.RadarrModal.testing": "Testando...",
"components.Settings.RadarrModal.saving": "Salvando...",
"components.Settings.Notifications.saving": "Salvando...",
"components.RequestModal.cancelling": "Cancelando...",
"components.Settings.plexlibraries": "Bibliotecas do Plex",
"components.Settings.notrunning": "Parado",
"components.Settings.notificationsettingsDescription": "Aqui você pode escolher e selecionar os tipos de notificações e através de quais serviços deseja enviar.",
"pages.pageNotFound": "404 - Página Não Encontrada",
"components.RequestModal.season": "Temporada",
"components.Settings.notificationsettings": "Configurações de Notificação",
"components.Settings.nodefaultdescription": "Pelo menos um servidor deve ser selecionado como padrão antes que qualquer solicitação chegue aos seus serviços.",
"components.Settings.nodefault": "Nenhum servidor padrão selecionado!",
"components.Settings.no4kimplemented": "(Servidores 4K padrões não configurados atualmente)",
"components.Settings.nextexecution": "Próxima Execução",
"components.Settings.menuServices": "Serviços",
"components.Settings.menuPlexSettings": "Plex",
"components.Settings.menuNotifications": "Notificações",
"components.Settings.menuLogs": "Logs",
"components.Settings.menuJobs": "Tarefas",
"components.Settings.menuGeneralSettings": "Configurações Gerais",
"components.Settings.menuAbout": "Sobre",
"components.Settings.manualscanDescription": "Normalmente, isso só será executado uma vez a cada 6 horas. Overseerr irá checar em detalhes items recentemente adicionados ao seu servidor Plex. Se essa é a primeira vez que você configura um servidor Plex, é recomendado a varredura completa de sua biblioteca!",
"components.Settings.manualscan": "Varredura Manual da Biblioteca",
"components.Settings.librariesRemaining": "Bibliotecas Restantes: {count}",
"components.Settings.jobname": "Nome da Tarefa",
"components.Settings.hostname": "Nome de Servidor/IP",
"components.Settings.generalsettingsDescription": "Essas são configurações gerais do Overseerr.",
"components.Settings.generalsettings": "Configurações Gerais",
"components.Settings.edit": "Editar",
"components.Settings.deleteserverconfirm": "Tem certeza que deseja apagar esse servidor?",
"components.Settings.delete": "Apagar",
"components.UserList.delete": "Apagar",
"i18n.deleting": "Apagando…",
"i18n.delete": "Apagar",
"components.Settings.default4k": "Padrão 4K",
"components.Settings.default": "Padrão",
"components.Settings.currentlibrary": "Biblioteca Atual: {name}",
"components.Settings.copied": "Chave de API copiada",
"components.Settings.cancelscan": "Cancelar Escaneamento",
"components.Settings.applicationurl": "URL da Aplicação",
"components.Settings.apikey": "Chave de API",
"components.Settings.addsonarr": "Adicionar Servidor Sonarr",
"components.Settings.address": "Endereço",
"components.Settings.addradarr": "Adicionar Servidor Radarr",
"components.Settings.activeProfile": "Perfil Ativo",
"components.Settings.SonarrModal.validationRootFolderRequired": "Você deve selecionar uma pasta raíz",
"components.Settings.SonarrModal.validationProfileRequired": "Você deve selecionar um perfil",
"components.Settings.SonarrModal.validationPortRequired": "Você deve prover uma porta",
"components.Settings.SonarrModal.validationNameRequired": "Você deve prover o nome do servidor",
"components.Settings.SonarrModal.validationHostnameRequired": "Você deve prover o Nome do Servidor/IP",
"components.Settings.SonarrModal.validationApiKeyRequired": "Você deve prover uma chave de API",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Conexão estabelecida com servidor Sonarr!",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Falha ao conectar ao Servidor Sonarr",
"components.Settings.SonarrModal.testFirstRootFolders": "Teste sua conexão para carregar as pastas raízes",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Teste sua conexão para carregar perfis de qualidade",
"components.Settings.SonarrModal.test": "Testar",
"components.Settings.SonarrModal.ssl": "SSL",
"components.Settings.SonarrModal.servernamePlaceholder": "Meu Servidor Sonarr",
"components.Settings.SonarrModal.servername": "Nome do Servidor",
"components.Settings.SonarrModal.server4k": "Servidor 4K",
"components.Settings.SonarrModal.selectRootFolder": "Selecione a Pasta Raíz",
"components.Settings.SonarrModal.selectQualityProfile": "Selecione o Perfil de Qualidade",
"components.Settings.SonarrModal.seasonfolders": "Temporadas Em Pastas",
"components.Settings.SonarrModal.save": "Salvar Mudanças",
"components.Settings.SonarrModal.rootfolder": "Pasta Raíz",
"components.Settings.SonarrModal.qualityprofile": "Perfil de Qualidade",
"components.Settings.SonarrModal.port": "Porta",
"components.Settings.SonarrModal.loadingrootfolders": "Carregando Pastas Raízes…",
"components.Settings.SonarrModal.loadingprofiles": "Carregando Perfis de Qualidade…",
"components.Settings.SonarrModal.hostname": "Nome do Servidor / IP",
"components.Settings.SonarrModal.editsonarr": "Editar Servidor Sonarr",
"components.Settings.SonarrModal.defaultserver": "Servidor Padrão",
"components.Settings.SonarrModal.createsonarr": "Criar Um Novo Servidor Sonarr",
"components.Settings.SonarrModal.baseUrlPlaceholder": "Exemplo: /sonarr",
"components.Settings.SonarrModal.baseUrl": "URL Base",
"components.Settings.SonarrModal.apiKeyPlaceholder": "Sua Chave de API do Sonarr",
"components.Settings.SonarrModal.apiKey": "Chave de API",
"components.Settings.SonarrModal.animerootfolder": "Pasta Raíz de Animes",
"components.Settings.SonarrModal.animequalityprofile": "Perfil de Qualidade Para Animes",
"components.Settings.SonarrModal.add": "Adicionar Servidor",
"components.Settings.SettingsAbout.version": "Versão",
"components.Settings.SettingsAbout.totalrequests": "Total de Solicitações",
"components.Settings.SettingsAbout.totalmedia": "Total de Títulos",
"components.Settings.SettingsAbout.overseerrinformation": "Sobre Overseerr",
"components.Settings.SettingsAbout.githubdiscussions": "Discussões no GitHub",
"components.Settings.SettingsAbout.gettingsupport": "Obtenha Suporte",
"components.Settings.SettingsAbout.clickheretojoindiscord": "Clique aqui para participar de nosso servidor Discord.",
"components.Settings.RadarrModal.validationRootFolderRequired": "Você deve selecionar uma pasta raíz",
"components.Settings.RadarrModal.validationProfileRequired": "Você deve selecionar um perfil",
"components.Settings.RadarrModal.validationPortRequired": "Você deve prover uma porta",
"components.Settings.RadarrModal.validationNameRequired": "Você deve prover o nome do servidor",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Você deve selecionar a disponibilidade mínima",
"components.Settings.RadarrModal.validationHostnameRequired": "Você deve prover o Nome do Servidor/IP",
"components.Settings.RadarrModal.validationApiKeyRequired": "Você deve prover uma chave de API",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Conexão estabelecida com Radarr!",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Falha ao conectar ao Servidor Radarr",
"components.Settings.RadarrModal.testFirstRootFolders": "Teste sua conexão para carregar as pastas raízes",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Teste sua conexão para carregar perfis de qualidade",
"components.Settings.RadarrModal.test": "Testar",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.servernamePlaceholder": "Meu Servidor Radarr",
"components.Settings.RadarrModal.servername": "Nome do Servidor",
"components.Settings.RadarrModal.server4k": "Servidor 4K",
"components.Settings.RadarrModal.selectRootFolder": "Selecione a Pasta Raíz",
"components.Settings.RadarrModal.selectQualityProfile": "Selecione o Perfil de Qualidade",
"components.Settings.RadarrModal.selectMinimumAvailability": "Selecione disponibilidade mínima",
"components.Settings.RadarrModal.save": "Salvar Mudanças",
"components.Settings.RadarrModal.rootfolder": "Pasta Raíz",
"components.Settings.RadarrModal.qualityprofile": "Perfil de Qualidade",
"components.Settings.RadarrModal.port": "Porta",
"components.Settings.RadarrModal.minimumAvailability": "Disponibilidade Mínima",
"components.Settings.RadarrModal.loadingrootfolders": "Carregando Pastas Raízes…",
"components.Settings.RadarrModal.loadingprofiles": "Carregando Perfis de Qualidade…",
"components.Settings.RadarrModal.hostname": "Nome do Servidor / IP",
"components.Settings.RadarrModal.editradarr": "Editar Servidor Radarr",
"components.Settings.RadarrModal.defaultserver": "Servidor Padrão",
"components.Settings.RadarrModal.createradarr": "Criar Um Novo Servidor Radarr",
"components.Settings.RadarrModal.baseUrlPlaceholder": "Exemplo: /radarr",
"components.Settings.RadarrModal.baseUrl": "URL Base",
"components.Settings.RadarrModal.apiKeyPlaceholder": "Sua Chave de API do Radarr",
"components.Settings.RadarrModal.apiKey": "Chave de API",
"components.Settings.RadarrModal.add": "Adicionar Servidor",
"components.Settings.Notifications.webhookUrlPlaceholder": "Configurações do Servidor -> Integrações -> Webhooks",
"components.Settings.Notifications.webhookUrl": "URL de Webhook",
"components.Settings.Notifications.validationWebhookUrlRequired": "Você deve prover uma URL de webhook",
"components.Settings.Notifications.validationSmtpPortRequired": "Você deve prover a porta SMTP",
"components.Settings.Notifications.validationFromRequired": "Você deve prover um e-mail de remetente",
"components.Settings.Notifications.validationSmtpHostRequired": "Você deve prover um servidor SMTP",
"components.Settings.Notifications.smtpPort": "Porta SMTP",
"components.Settings.Notifications.smtpHost": "Servidor SMTP",
"components.Settings.Notifications.save": "Salvar Mudanças",
"components.Settings.Notifications.enableSsl": "Habilitar SSL",
"components.Settings.Notifications.emailsettingssaved": "Configurações de notificação via e-mail salvas!",
"components.Settings.Notifications.emailsettingsfailed": "Falha ao salvar configurações de notificação via e-mail.",
"components.Settings.Notifications.emailsender": "Email do Remetente",
"components.Settings.Notifications.discordsettingssaved": "Configurações de notificação do Discord salvas!",
"components.Settings.Notifications.discordsettingsfailed": "Falha ao salvar configurações de notificação do Discord.",
"components.Settings.Notifications.authUser": "Usuário",
"components.Settings.Notifications.authPass": "Senha",
"components.Settings.Notifications.agentenabled": "Agente Habilitado",
"components.Search.searchresults": "Resultados da Pesquisa",
"components.TvDetails.status": "Estado",
"components.RequestModal.status": "Estado",
"components.RequestModal.selectseason": "Selecione temporada(s)",
"components.RequestModal.seasonnumber": "Temporada {number}",
"components.RequestModal.requesttitle": "Solicitar {title}",
"components.RequestModal.requesting": "Solicitando…",
"components.RequestModal.requestfrom": "Existe uma solicitação pendende de {username}",
"components.RequestModal.requestadmin": "Sua solicitação será aprovada imediatamente.",
"components.RequestModal.requestSuccess": "<strong>{title}</strong> solicitado.",
"components.RequestModal.requestCancel": "Solicitação para <strong>{title}</strong> cancelada",
"components.RequestModal.request": "Solicitar",
"components.RequestModal.pendingrequest": "Solicitação pendente para {title}",
"components.RequestModal.numberofepisodes": "# de Episódeos",
"components.RequestModal.notrequested": "Não Solicitado",
"components.RequestModal.extras": "Extras",
"components.RequestModal.close": "Fechar",
"components.RequestModal.cancelrequest": "Isso irá cancelar sua solicitação. Você tem certeza que deseja continuar?",
"components.RequestModal.cancel": "Cancelar Solicitação",
"components.RequestList.status": "Estado",
"components.RequestList.showingresults": "Exibindo <strong>{from}</strong> a <strong>{to}</strong> de <strong>{total}</strong> resultados",
"components.RequestList.requests": "Solicitações",
"components.RequestList.previous": "Anterior",
"components.RequestList.RequestItem.seasons": "Temporadas",
"components.RequestList.RequestItem.notavailable": "N/A",
"components.RequestCard.all": "Todas"
}

View File

@@ -48,7 +48,7 @@
"components.PersonDetails.appearsin": "",
"components.PersonDetails.ascharacter": "",
"components.PersonDetails.nobiography": "",
"components.PlexLoginButton.loading": "Загрузка...",
"components.PlexLoginButton.loading": "Загрузка",
"components.PlexLoginButton.loggingin": "",
"components.PlexLoginButton.loginwithplex": "Войти через Plex",
"components.RequestBlock.seasons": "Сезонов",
@@ -93,7 +93,7 @@
"components.Settings.Notifications.emailsender": "",
"components.Settings.Notifications.enableSsl": "Включить SSL",
"components.Settings.Notifications.save": "Сохранить изменения",
"components.Settings.Notifications.saving": "Сохранение...",
"components.Settings.Notifications.saving": "Сохранение",
"components.Settings.Notifications.smtpHost": "",
"components.Settings.Notifications.smtpPort": "",
"components.Settings.Notifications.validationFromRequired": "",
@@ -116,7 +116,7 @@
"components.Settings.RadarrModal.qualityprofile": "",
"components.Settings.RadarrModal.rootfolder": "Корневая папка",
"components.Settings.RadarrModal.save": "Сохранить изменения",
"components.Settings.RadarrModal.saving": "Сохранение...",
"components.Settings.RadarrModal.saving": "Сохранение",
"components.Settings.RadarrModal.selectMinimumAvailability": "",
"components.Settings.RadarrModal.selectQualityProfile": "",
"components.Settings.RadarrModal.selectRootFolder": "Выберите корневую папку",
@@ -125,7 +125,7 @@
"components.Settings.RadarrModal.servernamePlaceholder": "",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.test": "Тест",
"components.Settings.RadarrModal.testing": "Тестирование...",
"components.Settings.RadarrModal.testing": "Тестирование",
"components.Settings.RadarrModal.toastRadarrTestFailure": "",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "",
"components.Settings.RadarrModal.validationApiKeyRequired": "",

352
src/i18n/locale/sr.json Normal file
View File

@@ -0,0 +1,352 @@
{
"components.RequestList.mediaInfo": "Informacija o fajlu",
"components.TvDetails.manageModalClearMedia": "Obriši sve infomacije o fajlu",
"components.TvDetails.approverequests": "Odobri {requestCount} {requestCount, plural, {Request} drugih {Requests}}",
"components.RequestModal.requestseasons": "Zatraži {seasonCount} {seasonCount, plural, jednu {Season} više {Seasons}}",
"components.RequestList.showingresults": "Prikazivanje <strong>{from}</strong> do <strong>{to}</strong> od <strong>{total}</strong> rezultata",
"components.MovieDetails.decline": "Poništi",
"components.Layout.Sidebar.dashboard": "Pronadji Novo",
"pages.somethingWentWrong": "{statusCode} - Nesto se pokvarilo",
"pages.serviceUnavailable": "{statusCode} - Servis nije dostupan",
"pages.returnHome": "Povratak na glavnu stranicu",
"pages.pageNotFound": "404 - Stranica Nije Pronadjena",
"pages.oops": "Ups",
"pages.internalServerError": "{statusCode} - Greska na serveru",
"i18n.unavailable": "Nije dostupno",
"i18n.tvshows": "Serije",
"i18n.processing": "Obradjuje se…",
"i18n.pending": "Na čekanju",
"i18n.partiallyavailable": "Polovično Dostupno",
"i18n.movies": "Filmovi",
"i18n.deleting": "Brisanje u toku…",
"i18n.delete": "Izbriši",
"i18n.declined": "Odbijeno",
"i18n.decline": "Poništi",
"i18n.cancel": "Poništi",
"i18n.available": "Dostupno",
"i18n.approved": "Prihvaćeno",
"i18n.approve": "Prihvati",
"components.UserList.usertype": "Tip Korisnika",
"components.UserList.username": "Korisničko Ime",
"components.UserList.userlist": "Lista Korisnika",
"components.UserList.userdeleteerror": "Neuspešno brisanje korisnika",
"components.UserList.userdeleted": "Korisnik izbrisan",
"components.UserList.user": "Korisnik",
"components.UserList.totalrequests": "Ukupno Zahteva",
"components.UserList.role": "Uloga",
"components.UserList.plexuser": "Plex Korisnik",
"components.UserList.lastupdated": "Zadnja Promena",
"components.UserList.edit": "Edituj",
"components.UserList.deleteuser": "Izbriši Korisnika",
"components.UserList.deleteconfirm": "Da li ste sigurni da želite da izbrišete ovog korisnika? Svi njegovi zahtevi će biti izbrisani.",
"components.UserList.delete": "Izbriši",
"components.UserList.created": "Napravljeno",
"components.UserList.admin": "Administrator",
"components.UserEdit.voteDescription": "Dodaje permisiju da glasa na zahteve (glasanje jos nije implementirano)",
"components.UserEdit.vote": "Glasaj",
"components.UserEdit.usersaved": "Korisnik sačuvan",
"components.UserEdit.usersDescription": "Dodaje permisiju da upravlja Overseerr korisnicima. Korisnici sa ovom permisiju ne mogu da modifikuju korisnike sa Administratorskim statusom, niti da ga dodaju.",
"components.UserEdit.users": "Upravljaj Korisnicima",
"components.UserEdit.username": "Korisničko ime",
"components.UserEdit.userfail": "Neuspešno sačuvavanje korisnika.",
"components.UserEdit.settingsDescription": "Dodaje permisiju da menja sva Overseerr podešavanja. Korisnik mora da ima ovu permisiju da bi je dodao drugima.",
"components.UserEdit.settings": "Upravljaj Podešavanjima",
"components.UserEdit.saving": "Sačuvavanje…",
"components.UserEdit.save": "Sačuvaj",
"components.UserEdit.requestDescription": "Dodaje permisiju za zahtevanje filmova i serija.",
"components.UserEdit.request": "Zahtev",
"components.UserEdit.permissions": "Permisije",
"components.UserEdit.managerequestsDescription": "Dodaje permisiju za upravljanje Overseerr zahtevima. Ovo uključuje prihvatanje i odbijanje zahteva.",
"components.UserEdit.managerequests": "Upravljaj Zahtevima",
"components.UserEdit.email": "Email",
"components.UserEdit.edituser": "Edituj Korisnika",
"components.UserEdit.avatar": "Slika",
"components.UserEdit.autoapproveDescription": "Dozvoljava automatsko prihvatanje zahteva za ovog korisnika.",
"components.UserEdit.autoapprove": "Automatski Odobri",
"components.UserEdit.adminDescription": "Potpuni Administrativni Pristup. Zaobilazi sve provere permisija.",
"components.UserEdit.admin": "Admin",
"components.TvDetails.userrating": "Korisnički Rejting",
"components.TvDetails.unavailable": "Nije dostupno",
"components.TvDetails.status": "Status",
"components.TvDetails.similarsubtext": "Slične serije kao {title}",
"components.TvDetails.similar": "Slične Serije",
"components.TvDetails.showtype": "Tip Serije",
"components.TvDetails.requestmore": "Zatraži još",
"components.TvDetails.request": "Zatraži",
"components.TvDetails.recommendationssubtext": "Ako vam se svidja {title}, možda vam se svidi…",
"components.TvDetails.recommendations": "Preporuke",
"components.TvDetails.pending": "Na čekanju",
"components.TvDetails.overviewunavailable": "Pregled nije dostupan",
"components.TvDetails.overview": "Pregled",
"components.TvDetails.originallanguage": "Originalni Jezik",
"components.TvDetails.network": "Mreža",
"components.TvDetails.manageModalTitle": "Upravljaj Serijama",
"components.TvDetails.manageModalRequests": "Zahtevi",
"components.TvDetails.manageModalNoRequests": "Nema Zahteva",
"components.TvDetails.manageModalClearMediaWarning": "Ovo će ukloniti sve podatke o mediji uključujući i zahteve za ovaj sadrzaj nepovratno. Ako ovaj sadržaj postoji u vašoj plex biblioteci infomacija o mediji će biti kreirana pri sledećoj sinhronizaciji.",
"components.TvDetails.declinerequests": "Odbij {requestCount} {requestCount, plural, jedan {Request} drugih {Requests}}",
"components.TvDetails.decline": "Odbij",
"components.TvDetails.cast": "Repertoar",
"components.TvDetails.cancelrequest": "Otkaži Zahtev",
"components.TvDetails.available": "Dostupno",
"components.TvDetails.approve": "Odobri",
"components.TvDetails.anime": "Anime",
"components.TvDetails.TvCast.fullseriescast": "Ceo Repertoar Serije",
"components.TitleCard.tvshow": "Serija",
"components.TitleCard.movie": "Film",
"components.Slider.noresults": "Nema Rezultata",
"components.Setup.welcome": "Dobrodošli u Overseerr",
"components.Setup.tip": "Savet",
"components.Setup.syncingbackground": "Sinhronizacija će raditi u pozadini. Možete da nastavite podešavanja u toku rada.",
"components.Setup.signinMessage": "Započni sa prijavljivanjem sa Vašim Plex profilom",
"components.Setup.loginwithplex": "Prijavljivanje sa Plexom",
"components.Setup.finishing": "Završavam…",
"components.Setup.finish": "Završi Podešavanja",
"components.Setup.continue": "Nastavi",
"components.Setup.configureservices": "Konfigurišite Servise",
"components.Setup.configureplex": "Konfigurišite Plex",
"components.Settings.validationPortRequired": "Morate dodati port",
"components.Settings.validationHostnameRequired": "Morate dodati hostname ili IP adresu",
"components.Settings.toastSettingsSuccess": "Podešavanja sačuvana.",
"components.Settings.toastSettingsFailure": "Nešto se pokvarilo pri čuvanju podešavanja.",
"components.Settings.toastApiKeySuccess": "Novi API Ključ generisan!",
"components.Settings.toastApiKeyFailure": "Nešto nije odradjeno kako treba pri generisanju API ključa.",
"components.Settings.syncing": "Sinhronizacija u toku…",
"components.Settings.sync": "Sinhronizuj Plex Sadržaj",
"components.Settings.startscan": "Pokreni Skeniranje",
"components.Settings.ssl": "SSL",
"components.Settings.sonarrsettings": "Sonarr Podešavanja",
"components.Settings.sonarrSettingsDescription": "Podesite vaš Sonarr server ispod. Možete imati više, ali samo 2 mogu biti aktivna u bilo kom trenutku (jedan za HD, drugi za 4K). Administratori mogu da odaberu koji server je za nove zahteve.",
"components.Settings.servernamePlaceholder": "Ime Plex Servera",
"components.Settings.servername": "Ime Servera (Automatski se podesi kada sačuvate)",
"components.Settings.saving": "Sačuvavanje u toku…",
"components.Settings.save": "Sačuvaj Promene",
"components.Settings.runnow": "Pokreni Sad",
"components.Settings.radarrsettings": "Radarr Podešavanja",
"components.Settings.radarrSettingsDescription": "Podesite vaš Radarr server ispod. Možete imati više od jednog, ali samo 2 mogu biti aktivna u bilo kom trenutku (jedan za HD, drugi za 4K). Administratori mogu da odaberu koji server je za nove zahteve.",
"components.Settings.port": "Port",
"components.Settings.plexsettingsDescription": "Konfigurišite podešavanja za Vaš Plex server. Overseerr koristi vaš Plex server da skenira vaš sadržaj u nekom intervalu da proveri šta je dostupno.",
"components.Settings.plexsettings": "Plex Podešavanja",
"components.Settings.plexlibrariesDescription": "Overseerr skenira sadržaj za imena. Dodajte informacije o Plex konekciji i kliknite dole dugme ako nijedan nije na listi.",
"components.Settings.plexlibraries": "Plex Sadržaj",
"components.Settings.notificationsettingsDescription": "Ovde možete odabrati tipove notifikacija koji se šalju i ka kojim servisima.",
"components.Settings.notificationsettings": "Podešavanje Notifikacija",
"components.Settings.nodefaultdescription": "Barem jedan server mora biti označen kao defaultni da bi se zahtevi poslali ka servisima.",
"components.Settings.nodefault": "Nijedan defaultni server nije odabran!",
"components.Settings.no4kimplemented": "(Defaultni 4K serveri nisu trenutno implementirani)",
"components.Settings.nextexecution": "Sledeće pokretanje",
"components.Settings.menuServices": "Servisi",
"components.Settings.menuPlexSettings": "Plex",
"components.Settings.menuNotifications": "Notifikacije",
"components.Settings.menuLogs": "Logovi",
"components.Settings.menuJobs": "Poslovi",
"components.Settings.menuGeneralSettings": "Standardna Podešavanja",
"components.Settings.menuAbout": "O nama",
"components.Settings.manualscanDescription": "Normalno, ovo će biti pokrenutno na svakih 6 sati. Overseerr će proveriti nedavno dodati Plex sadržaj češće. Ako je ovo prvi put da se Plex konfiguriše, jedan ručni scan sadržaja je preporučen!",
"components.Settings.manualscan": "Ručno Skeniranje Sadržaja",
"components.Settings.librariesRemaining": "Broj sadržaja koji se obradjuje: {count}",
"components.Settings.jobname": "Ime posla",
"components.Settings.hostname": "Hostname/IP",
"components.Settings.generalsettingsDescription": "Ovo su podešavanja vezana za uobičajenu Overseerr konfiguraciju.",
"components.Settings.generalsettings": "Standarna Podešavanja",
"components.Settings.edit": "Izmena",
"components.Settings.deleteserverconfirm": "Da li ste sigurni da želite da izbrišete ovaj server?",
"components.Settings.delete": "Izbriši",
"components.Settings.default4k": "Defaultno 4K",
"components.Settings.default": "Defaultno",
"components.Settings.currentlibrary": "Trenutna Biblioteka: {name}",
"components.Settings.copied": "Kopiran API ključ",
"components.Settings.cancelscan": "Otkaži skeniranje",
"components.Settings.applicationurl": "URL Aplikacije",
"components.Settings.apikey": "API Ključ",
"components.Settings.addsonarr": "Dodaj Sonarr Server",
"components.Settings.address": "Adresa",
"components.Settings.addradarr": "Dodaj Radarr Server",
"components.Settings.activeProfile": "Aktivni Profil",
"components.Settings.SonarrModal.validationRootFolderRequired": "Morate odabrati root folder",
"components.Settings.SonarrModal.validationProfileRequired": "Morate odabrati profil kvaliteta",
"components.Settings.SonarrModal.validationPortRequired": "Morate popuniti port",
"components.Settings.SonarrModal.validationNameRequired": "Morate popuniti ime servera",
"components.Settings.SonarrModal.validationHostnameRequired": "Morate dodati hostname ili IP adresu",
"components.Settings.SonarrModal.validationApiKeyRequired": "Morate dodati API ključ",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Konekcija sa Sonarr serverom uspešna!",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Konekcija sa Sonarr Serverom neuspešna",
"components.Settings.SonarrModal.testing": "Testiranje…",
"components.Settings.SonarrModal.testFirstRootFolders": "Testirajte Vašu konekciju da učitate root foldere",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Testirajte Vašu konekciju da učitate profile kvaliteta",
"components.Settings.SonarrModal.test": "Test",
"components.Settings.SonarrModal.ssl": "SSL",
"components.Settings.SonarrModal.servernamePlaceholder": "Sonarr Server",
"components.Settings.SonarrModal.servername": "Ime Servera",
"components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.selectRootFolder": "Odaberi Root Folder",
"components.Settings.SonarrModal.selectQualityProfile": "Odaberi Profil Kvaliteta",
"components.Settings.SonarrModal.seasonfolders": "Folderi Sezone",
"components.Settings.SonarrModal.saving": "Čuvanje…",
"components.Settings.SonarrModal.save": "Sačuvaj Promene",
"components.Settings.SonarrModal.rootfolder": "Root Folder",
"components.Settings.SonarrModal.qualityprofile": "Profil Kvaliteta",
"components.Settings.SonarrModal.port": "Port",
"components.Settings.SonarrModal.loadingrootfolders": "Učitavanje root foldera…",
"components.Settings.SonarrModal.loadingprofiles": "Učitavanje profila kvaliteta…",
"components.Settings.SonarrModal.hostname": "Hostname",
"components.Settings.SonarrModal.editsonarr": "Edituj Sonarr Server",
"components.Settings.SonarrModal.defaultserver": "Defaultni Server",
"components.Settings.SonarrModal.createsonarr": "Dodajte Nov Sonarr Server",
"components.Settings.SonarrModal.baseUrlPlaceholder": "Primer: /sonarr",
"components.Settings.SonarrModal.baseUrl": "Osnovni URL",
"components.Settings.SonarrModal.apiKeyPlaceholder": "Vaš Sonarr API Ključ",
"components.Settings.SonarrModal.apiKey": "API Ključ",
"components.Settings.SonarrModal.animerootfolder": "Root folder za Anime",
"components.Settings.SonarrModal.animequalityprofile": "Kvalitet Profila za Anime",
"components.Settings.SonarrModal.add": "Dodaj Server",
"components.Settings.SettingsAbout.version": "Verzija",
"components.Settings.SettingsAbout.totalrequests": "Ukupno Zahteva",
"components.Settings.SettingsAbout.totalmedia": "Ukupno Medije",
"components.Settings.SettingsAbout.overseerrinformation": "Overseerr Informacije",
"components.Settings.SettingsAbout.githubdiscussions": "GitHub rasprave",
"components.Settings.SettingsAbout.gettingsupport": "Pomoć",
"components.Settings.SettingsAbout.clickheretojoindiscord": "Kliknite ovde da se učlanite u naš Discord server.",
"components.Settings.RadarrModal.validationRootFolderRequired": "Morate odabrati root folder",
"components.Settings.RadarrModal.validationProfileRequired": "Morate odabrati profil",
"components.Settings.RadarrModal.validationPortRequired": "Morate dodati port",
"components.Settings.RadarrModal.validationNameRequired": "Morate dodati ime servera",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Morate odabrati minimum dostupnost",
"components.Settings.RadarrModal.validationHostnameRequired": "Morate dodati hostname ili IP adresu",
"components.Settings.RadarrModal.validationApiKeyRequired": "Morate dodati API ključ",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Konekcija sa Radarr serverom uspostavljena!",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Neuspešna konekcija ka Radarr Serveru",
"components.Settings.RadarrModal.testing": "Testiranje…",
"components.Settings.RadarrModal.testFirstRootFolders": "Testirajte vašu konekciju da učitate root foldere",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Testirajte vašu konekciju da učitate profile kvaliteta",
"components.Settings.notrunning": "Ne Izvršava se",
"components.Settings.RadarrModal.test": "Test",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.servernamePlaceholder": "Radarr Server",
"components.Settings.RadarrModal.servername": "Ime Servera",
"components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.selectRootFolder": "Odaberi Root Folder",
"components.Settings.RadarrModal.selectQualityProfile": "Odaberi Profil Kvaliteta",
"components.Settings.RadarrModal.selectMinimumAvailability": "Odaberi minimum dostupnost",
"components.Settings.RadarrModal.saving": "Sačuvavanje…",
"components.Settings.RadarrModal.save": "Sačuvaj Promene",
"components.Settings.RadarrModal.rootfolder": "Root Fascikla",
"components.Settings.RadarrModal.qualityprofile": "Profil Kvaliteta",
"components.Settings.RadarrModal.port": "Port",
"components.Settings.RadarrModal.minimumAvailability": "Minimum Dostupnost",
"components.Settings.RadarrModal.loadingrootfolders": "Učitavanje root foldera…",
"components.Settings.RadarrModal.loadingprofiles": "Učitavanje nivoa kvaliteta…",
"components.Settings.RadarrModal.hostname": "Hostname",
"components.Settings.RadarrModal.editradarr": "Edituj Radarr Server",
"components.Settings.RadarrModal.defaultserver": "Standardni Server",
"components.Settings.RadarrModal.createradarr": "Napravi Novi Radarr Server",
"components.Settings.RadarrModal.baseUrlPlaceholder": "Primer: /radarr",
"components.Settings.RadarrModal.baseUrl": "Osnovni URL",
"components.Settings.RadarrModal.apiKeyPlaceholder": "Vaš Radarr API Ključ",
"components.Settings.RadarrModal.apiKey": "API Ključ",
"components.Settings.RadarrModal.add": "Dodaj Server",
"components.Settings.Notifications.webhookUrlPlaceholder": "Podešavanje Servera -> Integracije -> Webhooks",
"components.Settings.Notifications.webhookUrl": "Webhook adresa",
"components.Settings.Notifications.validationWebhookUrlRequired": "Morate dodati webhook URL",
"components.Settings.Notifications.validationSmtpPortRequired": "Morate popuniti SMTP port polje",
"components.Settings.Notifications.validationSmtpHostRequired": "Morate popuniti SMTP host polje",
"components.Settings.Notifications.validationFromRequired": "Morate dodati email adresu",
"components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.smtpHost": "SMTP Host",
"components.Settings.Notifications.saving": "Čuvam…",
"components.Settings.Notifications.save": "Sačuvaj Promene",
"components.Settings.Notifications.enableSsl": "Uključi SSL",
"components.Settings.Notifications.emailsettingssaved": "Email notifikacija uspešno sačuvana!",
"components.Settings.Notifications.emailsettingsfailed": "Email notifikacija nije uspešno sačuvana.",
"components.Settings.Notifications.emailsender": "Adresa email pošiljaoca",
"components.Settings.Notifications.discordsettingssaved": "Discord notifikacija uspešno sačuvana!",
"components.Settings.Notifications.discordsettingsfailed": "Discord notifikacija za podešavanje nije uspešno sačuvana.",
"components.Settings.Notifications.authUser": "Korisničko ime",
"components.Settings.Notifications.authPass": "Šifra",
"components.Settings.Notifications.agentenabled": "Agent Ukljucen",
"components.Search.searchresults": "Rezultati pretrage",
"components.RequestModal.status": "Status",
"components.RequestModal.selectseason": "Odaberi sezone",
"components.RequestModal.seasonnumber": "Sezona {number}",
"components.RequestModal.season": "Sezona",
"components.RequestModal.requesttitle": "Zatraži {title}",
"components.RequestModal.requesting": "Zahtevanje…",
"components.RequestModal.requestfrom": "Trenutno postoji zahtev na čekanju od {username}",
"components.RequestModal.requestadmin": "Vaš zahtev će odmah biti prihvaćen.",
"components.RequestModal.requestSuccess": "poslat zahtev za <strong>{title}</strong> .",
"components.RequestModal.requestCancel": "Zahtev za <strong>{title}</strong> otkazan",
"components.RequestModal.request": "Zahtev",
"components.RequestModal.pendingrequest": "Zahtev za {title} na čekanju",
"components.RequestModal.numberofepisodes": "Broj Epizoda",
"components.RequestModal.notrequested": "Nije Zahtevano",
"components.RequestModal.extras": "Dodatno",
"components.RequestModal.close": "Zatvori",
"components.RequestModal.cancelrequest": "Ovo će poništiti vaš zahtev. Da li ste sigurni da želite da otkažete?",
"components.RequestModal.cancelling": "Otkazivanje…",
"components.RequestModal.cancel": "Otkaži Zahtev",
"components.RequestList.status": "Status",
"components.RequestList.requests": "Zahtevi",
"components.RequestList.requestedAt": "Zahtevano",
"components.RequestList.previous": "Prethodni",
"components.RequestList.next": "Sledeći",
"components.RequestList.modifiedBy": "Zadnji put modifikovao",
"components.RequestList.RequestItem.seasons": "Sezone",
"components.RequestList.RequestItem.requestedby": "Zahtevao {username}",
"components.RequestList.RequestItem.notavailable": "Nije dostupno",
"components.RequestCard.seasons": "Sezone",
"components.RequestCard.requestedby": "Zahtevao {username}",
"components.RequestCard.all": "Sve",
"components.RequestBlock.seasons": "Sezone",
"components.PlexLoginButton.loginwithplex": "Prijavljivanje sa Plexom",
"components.PlexLoginButton.loggingin": "Prijavljivanje…",
"components.PlexLoginButton.loading": "Učitavanje…",
"components.PersonDetails.nobiography": "Biografija nije dostupna.",
"components.PersonDetails.ascharacter": "kao {character}",
"components.PersonDetails.appearsin": "Pojavljuje se u",
"components.MovieDetails.viewrequest": "Pogledaj Zahtev",
"components.MovieDetails.userrating": "Korisnički rejting",
"components.MovieDetails.unavailable": "Nije dostupno",
"components.MovieDetails.studio": "Studio",
"components.MovieDetails.status": "Status",
"components.MovieDetails.similarsubtext": "Drugi filmovi slični {title}",
"components.MovieDetails.similar": "Slični Naslovi",
"components.MovieDetails.runtime": "{minutes} minuta",
"components.MovieDetails.revenue": "Prihod",
"components.MovieDetails.request": "Zahtev",
"components.MovieDetails.releasedate": "Datum Izlaska",
"components.MovieDetails.recommendationssubtext": "Ako vam se svidja {title}, možda vam se dopadne…",
"components.MovieDetails.recommendations": "Predlozi",
"components.MovieDetails.pending": "Na čekanju",
"components.MovieDetails.overviewunavailable": "Pregled nije dostupan",
"components.MovieDetails.overview": "Pregled",
"components.MovieDetails.originallanguage": "Originalni Jezik",
"components.MovieDetails.manageModalTitle": "Upravljaj Filmom",
"components.MovieDetails.manageModalRequests": "Zahtevi",
"components.MovieDetails.manageModalNoRequests": "Nema zahteva",
"components.MovieDetails.manageModalClearMediaWarning": "Ovo će ukloniti sve media podatke uključujuci i sve zahteve za ovu stvar nepovratno. Ako ova stvar postoji u Vašoj Plex biblioteci, informacija o mediji će biti kreirana pri sledećoj sinhronizaciji.",
"components.MovieDetails.manageModalClearMedia": "Očisti sve media podatke",
"components.MovieDetails.cast": "Postava",
"components.MovieDetails.cancelrequest": "Otkaži Zahtev",
"components.MovieDetails.budget": "Budžet",
"components.MovieDetails.available": "Dostupno",
"components.MovieDetails.approve": "Odobri",
"components.MovieDetails.MovieCast.fullcast": "Kompletna Glumačka Postava",
"components.Login.signinplex": "Prijavite se da bi ste nastavili",
"components.Layout.alphawarning": "Ovo je softver u ALPHA stanju. Skoro sve je nestabilno. Molimo Vas prijavite sve probleme na Overseer GitHub stranici!",
"components.Layout.UserDropdown.signout": "Odjava",
"components.Layout.Sidebar.users": "Korisnici",
"components.Layout.Sidebar.settings": "Podešavanja",
"components.Layout.Sidebar.requests": "Zahtevi",
"components.Layout.SearchInput.searchPlaceholder": "Pretraži Filmove i Serije",
"components.Layout.LanguagePicker.changelanguage": "Promeni Jezik",
"components.Discover.upcomingmovies": "Predstojeći Filmovi",
"components.Discover.upcoming": "Predstojeći Filmovi",
"components.Discover.trending": "Popularno",
"components.Discover.recentrequests": "Nedavni Zahtevi",
"components.Discover.recentlyAdded": "Nedavno Dodati",
"components.Discover.populartv": "Popularne Serije",
"components.Discover.popularmovies": "Popularni Filmovi",
"components.Discover.nopending": "Nema zahteva na čekanju",
"components.Discover.discovertv": "Popularne Serije",
"components.Discover.discovermovies": "Popunarni Filmov"
}

350
src/i18n/locale/sv.json Normal file
View File

@@ -0,0 +1,350 @@
{
"components.Setup.finishing": "Slutför…",
"components.Setup.finish": "Slutför konfiguration",
"components.Setup.continue": "Fortsätt",
"components.Setup.configureservices": "Konfigurera Tjänster",
"components.Setup.configureplex": "Konfigurera Plex",
"components.Settings.validationPortRequired": "Du måste ange en port",
"components.Settings.validationHostnameRequired": "Du måste ange hostname/IP",
"components.Settings.toastSettingsSuccess": "Inställningar sparade.",
"components.Settings.toastSettingsFailure": "Något gick fel när inställningarna skulle sparas.",
"components.Settings.toastApiKeySuccess": "Ny API-nyckel skapad!",
"components.Settings.toastApiKeyFailure": "Något gick fel vid skapandet av ny API-nyckel.",
"components.Settings.syncing": "Synkroniserar…",
"components.Settings.sync": "Synkronisera Plex Bibliotek",
"components.Settings.startscan": "Starta scanning",
"components.Settings.ssl": "SSL",
"components.Settings.sonarrsettings": "Sonarrinställningar",
"components.Settings.sonarrSettingsDescription": "Konfigurera anslutningen till Sonarr nedan. Du kan ha multipla Sonarr-servrar men enbart två aktiva som standard (en för standard HD-material och en för 4K-material). Administratörer kan åsidosätta vilken server som används för nya förfrågningar.",
"components.Settings.servernamePlaceholder": "Plex Servernamn",
"components.Settings.servername": "Servernamn (Anges automatiskt efter att du sparat)",
"components.Settings.saving": "Sparar…",
"components.Settings.save": "Spara ändringar",
"components.Settings.runnow": "Kör Nu",
"components.Settings.radarrsettings": "Radarrinställningar",
"components.Settings.radarrSettingsDescription": "Konfigurera anslutningen till Radarr nedan. Du kan ha multipla Radarr-servrar men enbart två aktiva som standard (en för standard HD-material och en för 4K-material). Administratörer kan åsidosätta vilken server som används för nya förfrågningar.",
"components.Settings.port": "Port",
"components.Settings.plexsettingsDescription": "Konfigurera inställningarna för din Plex-server. Overseerr använder din Plex-server för att scanna dina mediabibliotek på givna intervaller för att se vilka media som är tillgängliga.",
"components.Settings.plexsettings": "Plexinställningar",
"components.Settings.plexlibrariesDescription": "Mediabiblioteken Overseer scannar för titlar. Konfigurera och spara dina Plex anslutningsinställningar och klicka på knappen nedan utifall inga är listade.",
"components.Settings.plexlibraries": "Plex Bibliotek",
"components.Settings.notrunning": "Körs ej",
"components.Settings.notificationsettingsDescription": "Här kan du välja vilka typer av notifikationer som skall skickas och med vilken typ av tjänst.",
"components.Settings.notificationsettings": "Notifikationsinställningar",
"components.Settings.nodefaultdescription": "Minst en server måste markeras som standard före du kan göra förfrågningar.",
"components.Settings.nodefault": "Ingen standard-server är vald!",
"components.Settings.no4kimplemented": "(Standard 4K servrar är för tillfället inte implementerat)",
"components.Settings.nextexecution": "Nästa körning",
"components.Settings.menuServices": "Tjänster",
"components.Settings.menuPlexSettings": "Plex",
"components.Settings.menuNotifications": "Notifikationer",
"components.Settings.menuLogs": "Loggar",
"components.Settings.menuJobs": "Jobb",
"components.Settings.menuGeneralSettings": "Generella Inställningar",
"components.Settings.menuAbout": "Om",
"components.Settings.manualscanDescription": "Normalt körs denna var sjätte timme. Overseerr kommer kontrollera till Plex Servers \"senast tillagda\" oftare. Om detta är första gången du konfigurerar din Plex Server är det starkt rekommenderat att göra en manuell full scan av ditt mediabibliotek!",
"components.Settings.manualscan": "Manuell Biblioteksscan",
"components.Settings.librariesRemaining": "Kvarvarande Bibliotek: {count}",
"components.Settings.jobname": "Jobbnamn",
"components.Settings.hostname": "Hostname/IP",
"components.Settings.generalsettingsDescription": "Dessa inställningar avser konfiguration av Overseerr.",
"components.Settings.generalsettings": "Generella Inställningar",
"components.Settings.edit": "Ändra",
"components.Settings.deleteserverconfirm": "Är du säker på att du vill radera servern?",
"components.Settings.delete": "Radera",
"components.Settings.default4k": "Standard 4K",
"components.Settings.default": "Standard",
"components.Settings.currentlibrary": "Nuvarande Bibliotek: {name}",
"components.Settings.copied": "API-nyckel kopierad",
"components.Settings.cancelscan": "Avbryt scanning",
"components.Settings.applicationurl": "Applikations-URL",
"components.Settings.apikey": "API-nyckel",
"components.Settings.addsonarr": "Lägg till Sonarr Server",
"components.Settings.address": "Adress",
"components.Settings.addradarr": "Lägg till Radarr Server",
"components.Settings.activeProfile": "Aktiv Profil",
"components.Settings.SonarrModal.validationRootFolderRequired": "Du måste ange en root-mapp",
"components.Settings.SonarrModal.validationProfileRequired": "Du måste ange en profil",
"components.Settings.SonarrModal.validationPortRequired": "Du måste ange en port",
"components.Settings.SonarrModal.validationNameRequired": "Du måste ange ett servernamn",
"components.Settings.SonarrModal.validationHostnameRequired": "Du måste ange hostname/IP",
"components.Settings.SonarrModal.validationApiKeyRequired": "Du måste ange en API-nyckel",
"components.Settings.SonarrModal.toastRadarrTestFailure": "Anslutningen till Sonarr misslyckades",
"components.Settings.SonarrModal.toastRadarrTestSuccess": "Anslutningen till Sonarr lyckades!",
"components.Settings.SonarrModal.testing": "Testar…",
"components.Settings.SonarrModal.testFirstRootFolders": "Testa din anslutning för att ladda in root-mappar",
"components.Settings.SonarrModal.testFirstQualityProfiles": "Testa din anslutning för att ladda in kvalitetsprofiler",
"components.Settings.SonarrModal.test": "Testa",
"components.Settings.SonarrModal.ssl": "SSL",
"components.Settings.SonarrModal.servernamePlaceholder": "En Sonarr Server",
"components.Settings.SonarrModal.servername": "Servernamn",
"components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.selectRootFolder": "Välj en root-mapp",
"components.Settings.SonarrModal.selectQualityProfile": "Välj en kvalitetsprofil",
"components.Settings.SonarrModal.seasonfolders": "Säsongsmappar",
"components.Settings.SonarrModal.saving": "Sparar…",
"components.Settings.SonarrModal.save": "Spara ändringar",
"components.Settings.SonarrModal.rootfolder": "Root-mapp",
"components.Settings.SonarrModal.qualityprofile": "Kvalitetsprofiler",
"components.Settings.SonarrModal.port": "Port",
"components.Settings.SonarrModal.loadingrootfolders": "Laddar root-mappar…",
"components.Settings.SonarrModal.loadingprofiles": "Laddar kvalitetsprofiler…",
"components.Settings.SonarrModal.hostname": "Hostname/IP",
"components.Settings.SonarrModal.editsonarr": "Ändra Sonarr Server",
"components.Settings.SonarrModal.defaultserver": "Standard Server",
"components.Settings.SonarrModal.createsonarr": "Skapa ny Sonarr Server",
"components.Settings.SonarrModal.baseUrlPlaceholder": "Exempel: /sonarr",
"components.Settings.SonarrModal.baseUrl": "Bas-URL",
"components.Settings.SonarrModal.apiKeyPlaceholder": "Din Sonarr API-nyckel",
"components.Settings.SonarrModal.apiKey": "API-nyckel",
"components.Settings.SonarrModal.animerootfolder": "Anime Root-mapp",
"components.Settings.SonarrModal.animequalityprofile": "Anime Kvalitetsprofil",
"components.Settings.SonarrModal.add": "Lägg till Server",
"components.Settings.SettingsAbout.version": "Version",
"components.Settings.SettingsAbout.totalrequests": "Totalt antal förfrågningar",
"components.Settings.SettingsAbout.totalmedia": "Totalt antal mediaobjekt",
"components.Settings.SettingsAbout.overseerrinformation": "Information om Overseerr",
"components.Settings.SettingsAbout.githubdiscussions": "GitHub Diskussioner",
"components.Settings.SettingsAbout.gettingsupport": "Få Support",
"components.Settings.SettingsAbout.clickheretojoindiscord": "Klicka här för att gå med i vår Discord server.",
"components.Settings.RadarrModal.validationRootFolderRequired": "Du måste ange en root-mapp",
"components.Settings.RadarrModal.validationProfileRequired": "Du måste ange en profil",
"components.Settings.RadarrModal.validationPortRequired": "Du måste ange en port",
"components.Settings.RadarrModal.validationNameRequired": "Du måste ange ett servernamn",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Du måste välja minsta tillgänglighet",
"components.Settings.RadarrModal.validationHostnameRequired": "Du måste ange hostname/IP",
"components.Settings.RadarrModal.validationApiKeyRequired": "Du måste ange en API-nyckel",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Anslutningen till Radarr lyckades!",
"components.Settings.RadarrModal.toastRadarrTestFailure": "Misslyckades att ansluta till Radarr Server",
"components.Settings.RadarrModal.testing": "Testar…",
"components.Settings.RadarrModal.testFirstRootFolders": "Testa din anslutning för att ladda in root-mappar",
"components.Settings.RadarrModal.testFirstQualityProfiles": "Testa din anslutning för att ladda in kvalitetsprofiler",
"components.Settings.RadarrModal.test": "Testa",
"components.Settings.RadarrModal.ssl": "SSL",
"components.Settings.RadarrModal.servernamePlaceholder": "En Radarr Server",
"components.Settings.RadarrModal.servername": "Servernamn",
"components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.selectRootFolder": "Välj en root-mapp",
"components.Settings.RadarrModal.selectQualityProfile": "Välj en kvalitetsprofil",
"components.Settings.RadarrModal.selectMinimumAvailability": "Ange minsta tillgänglighet",
"components.Settings.RadarrModal.saving": "Sparar…",
"components.Settings.RadarrModal.save": "Spara ändringar",
"components.Settings.RadarrModal.rootfolder": "Root-mapp",
"components.Settings.RadarrModal.qualityprofile": "Kvalitetsprofiler",
"components.Settings.RadarrModal.port": "Port",
"components.Settings.RadarrModal.minimumAvailability": "Minsta tillgänglighet",
"components.Settings.RadarrModal.loadingrootfolders": "Laddar root-mappar…",
"components.Settings.RadarrModal.loadingprofiles": "Laddar kvalitetsprofiler…",
"components.Settings.RadarrModal.hostname": "Hostname/IP",
"components.Settings.RadarrModal.editradarr": "Ändra Radarr Server",
"components.Settings.RadarrModal.defaultserver": "Standard Server",
"components.Settings.RadarrModal.createradarr": "Skapa ny Radarr Server",
"components.Settings.RadarrModal.baseUrlPlaceholder": "Exempel: /radarr",
"components.Settings.RadarrModal.baseUrl": "Bas-URL",
"components.Settings.RadarrModal.apiKeyPlaceholder": "Din Radarr API-Nyckel",
"components.Settings.RadarrModal.apiKey": "API-Nyckel",
"components.Settings.RadarrModal.add": "Lägg till Server",
"components.Settings.Notifications.webhookUrlPlaceholder": "Serverinställningar -> Integrationer -> Webhooks",
"components.Settings.Notifications.webhookUrl": "Webhook-URL",
"components.Settings.Notifications.validationWebhookUrlRequired": "Du måste ange en webhook URL",
"components.Settings.Notifications.validationSmtpPortRequired": "Du måste ange en SMTP Port",
"components.Settings.Notifications.validationSmtpHostRequired": "Du måste ange en SMTP Server",
"components.Settings.Notifications.validationFromRequired": "Du måste ange en avsändaradress",
"components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.smtpHost": "SMTP Server",
"components.Settings.Notifications.saving": "Sparar…",
"components.Settings.Notifications.save": "Spara ändringar",
"components.Settings.Notifications.enableSsl": "Aktivera SSL",
"components.Settings.Notifications.emailsettingssaved": "Notiferingsinställningar för e-post sparade!",
"components.Settings.Notifications.discordsettingsfailed": "Notifieringsinställningar för Discord misslyckades att sparas.",
"components.Settings.Notifications.emailsettingsfailed": "Notifieringsinställningar för e-post misslyckades att sparas.",
"components.Settings.Notifications.emailsender": "E-post avsändaradress",
"components.Settings.Notifications.discordsettingssaved": "Notiferingsinställningar för Discord sparade!",
"components.Settings.Notifications.authUser": "Användarnamn",
"components.Settings.Notifications.authPass": "Lösenord",
"components.Settings.Notifications.agentenabled": "Agent Aktiverad",
"components.Search.searchresults": "Sökresultat",
"components.RequestModal.status": "Status",
"components.RequestModal.selectseason": "Välj säsong(er)",
"components.RequestModal.seasonnumber": "Säsong {number}",
"components.RequestModal.season": "Säsong",
"components.RequestModal.requesttitle": "Begär {title}",
"components.RequestModal.requesting": "Skapar förfrågan…",
"components.RequestModal.requestfrom": "Det finns en väntande förfrågan från {username}",
"components.RequestModal.requestadmin": "Din förfrågan kommer bli godkänd omedelbart.",
"components.RequestModal.requestSuccess": "<strong>{title}</strong> begärd.",
"components.RequestModal.requestCancel": "Förfrågan för <strong>{title}</strong> avbruten",
"components.RequestModal.request": "Förfrågan",
"components.RequestModal.pendingrequest": "Väntande förfrågan för {title]",
"components.RequestModal.numberofepisodes": "Antal Avsnitt",
"components.RequestModal.notrequested": "Ej begärd",
"components.RequestModal.extras": "Extramaterial",
"components.RequestModal.close": "Stäng",
"components.RequestModal.cancelrequest": "Detta kommer ta bort din förfrågan. Är du säker på att du vill fortsätta?",
"components.RequestModal.cancelling": "Tar bort…",
"components.RequestModal.cancel": "Avbryt förfrågan",
"components.RequestList.status": "Status",
"components.RequestList.showingresults": "Visar <strong>{from}</strong> till <strong>{to}</strong> av <strong>{total}</strong> sökresultat",
"components.RequestList.requests": "Förfrågan",
"components.RequestList.requestedAt": "Begärd",
"components.RequestList.previous": "Föregående",
"components.RequestList.next": "Nästa",
"components.RequestList.modifiedBy": "Senast Ändrad Av",
"components.RequestList.mediaInfo": "Media Information",
"components.RequestList.RequestItem.seasons": "Säsonger",
"components.RequestList.RequestItem.requestedby": "Begärd av {username}",
"components.RequestList.RequestItem.notavailable": "N/A",
"components.RequestCard.seasons": "Säsonger",
"components.RequestCard.requestedby": "Begärd av {username}",
"components.RequestCard.all": "Alla",
"components.RequestBlock.seasons": "Säsonger",
"components.PlexLoginButton.loginwithplex": "Logga in med Plex",
"components.PlexLoginButton.loggingin": "Loggar in…",
"components.PlexLoginButton.loading": "Laddar…",
"components.PersonDetails.nobiography": "Ingen biografi tillgänglig.",
"components.PersonDetails.ascharacter": "som {character}",
"components.PersonDetails.appearsin": "Kan ses i",
"components.MovieDetails.viewrequest": "Visa förfrågan",
"components.MovieDetails.userrating": "Användarbetyg",
"components.MovieDetails.unavailable": "Otillgänglig",
"components.MovieDetails.studio": "Studio",
"components.MovieDetails.status": "Status",
"components.MovieDetails.similarsubtext": "Andra filmer som liknar {title}",
"components.MovieDetails.similar": "Liknande Titlar",
"components.MovieDetails.runtime": "{minutes} minuter",
"components.MovieDetails.revenue": "Inkomster",
"components.MovieDetails.request": "Förfrågan",
"components.MovieDetails.releasedate": "Utgivningsdatum",
"components.MovieDetails.recommendationssubtext": "Om du tycker om {title}, kanske du också tycker om…",
"components.MovieDetails.recommendations": "Rekommendationer",
"components.MovieDetails.pending": "Väntande",
"components.MovieDetails.overview": "Beskrivning",
"components.MovieDetails.overviewunavailable": "Beskrivning otillgänglig",
"components.MovieDetails.originallanguage": "Ursprungligt Språk",
"components.MovieDetails.manageModalTitle": "Hantera Film",
"components.MovieDetails.manageModalRequests": "Förfrågningar",
"components.MovieDetails.manageModalNoRequests": "Inga förfrågningar",
"components.MovieDetails.manageModalClearMediaWarning": "Detta kommer radera all media data inklusive förfrågningar för det här objektet och kan inte ångras. Om det här objektet finns i ditt Plex-bibliotek kommer media information att återskapas vid nästa synkronisering.",
"components.MovieDetails.manageModalClearMedia": "Rensa All Media Data",
"components.MovieDetails.decline": "Avböj",
"components.MovieDetails.cast": "Roller",
"components.MovieDetails.cancelrequest": "Avbryt begäran",
"components.MovieDetails.budget": "Budget",
"components.MovieDetails.available": "Tillgänglig",
"components.MovieDetails.approve": "Godkänn",
"components.MovieDetails.MovieCast.fullcast": "Rollista",
"components.Login.signinplex": "Logga in för att fortsätta",
"components.Layout.alphawarning": "Detta är ALPHA-mjukvara. Nästan allt riskerar att gå sönder/vara ostabilt. Vänligen rapportera problem till Overseerr GitHub!",
"components.Layout.UserDropdown.signout": "Logga ut",
"components.Layout.Sidebar.users": "Användare",
"components.Layout.Sidebar.settings": "Inställningar",
"components.Layout.Sidebar.requests": "Förfrågningar",
"components.Layout.Sidebar.dashboard": "Upptäck",
"components.Layout.SearchInput.searchPlaceholder": "Sök Filmer & TV-Serier",
"components.Layout.LanguagePicker.changelanguage": "Byt språk",
"components.Discover.upcomingmovies": "Kommande Filmer",
"components.Discover.upcoming": "Kommande Filmer",
"components.Discover.trending": "Trendande",
"components.Discover.recentrequests": "Senaste förfrågningar",
"components.Discover.recentlyAdded": "Nyligen tillagda",
"components.Discover.populartv": "Populära TV-Serier",
"components.Discover.popularmovies": "Populära Filmer",
"components.Discover.nopending": "Inga väntande förfrågningar",
"components.Discover.discovertv": "Populära TV-Serier",
"components.Discover.discovermovies": "Populära Filmer",
"components.TvDetails.approve": "Godkänn",
"components.TvDetails.anime": "Anime",
"components.TvDetails.TvCast.fullseriescast": "Rollista",
"components.TitleCard.tvshow": "TV-serier",
"components.TitleCard.movie": "Film",
"components.Slider.noresults": "Inga resultat",
"components.Setup.welcome": "Välkommen till Overseerr",
"components.Setup.tip": "Tips",
"components.Setup.syncingbackground": "Synkronisering kommer ske i bakgrunden. Du kan fortsätta med konfigurationen under tiden.",
"components.Setup.signinMessage": "Kom igång genom att logga in med ditt Plex-konto",
"components.UserEdit.email": "E-post",
"components.RequestModal.requestseasons": "Förfrågan {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}",
"pages.somethingWentWrong": "{statusCode} - Något gick fel",
"pages.serviceUnavailable": "{statusCode} - Tjänsten är otillgänglig",
"pages.returnHome": "Gå tillbaka till startsidan",
"pages.pageNotFound": "404 - Sidan kunde ej hittas",
"pages.oops": "Ojsan",
"pages.internalServerError": "{statusCode} - Internt serverfel",
"i18n.unavailable": "Otillgänglig",
"i18n.tvshows": "TV-serier",
"i18n.processing": "Behandlar…",
"i18n.pending": "Väntande",
"i18n.partiallyavailable": "Delvis Tillgänglig",
"i18n.movies": "Filmer",
"i18n.deleting": "Raderar…",
"i18n.delete": "Radera",
"i18n.declined": "Avböjd",
"i18n.decline": "Avböj",
"i18n.cancel": "Avbryt",
"i18n.available": "Tillgänglig",
"i18n.approved": "Godkänd",
"i18n.approve": "Godkänn",
"components.UserList.usertype": "Användartyp",
"components.UserList.username": "Användarnamn",
"components.UserList.userlist": "Användarlista",
"components.UserList.userdeleteerror": "Något gick fel när användaren skulle tas bort",
"components.UserList.userdeleted": "Användare borttagen",
"components.UserList.user": "Användare",
"components.UserList.totalrequests": "Totalt antal förfrågningar",
"components.UserList.role": "Roll",
"components.UserList.plexuser": "Plexanvändare",
"components.UserList.lastupdated": "Senast uppdaterad",
"components.UserList.edit": "Redigera",
"components.UserList.deleteuser": "Ta bort användare",
"components.UserList.deleteconfirm": "Är du säker på att du vill ta bort användaren? All data för användaren kommer att raderas.",
"components.UserList.delete": "Radera",
"components.UserList.created": "Skapad",
"components.UserList.admin": "Administratör",
"components.UserEdit.voteDescription": "Ger behörighet att rösta på förfrågningar (röstning är inte implementerat ännu)",
"components.UserEdit.vote": "Rösta",
"components.UserEdit.usersaved": "Användare sparad",
"components.UserEdit.usersDescription": "Ger behörighet att hantera användare i Overseerr. Användare med denna behörighet kan ge eller ändra användare med administratörstillgång.",
"components.UserEdit.users": "Hantera användare",
"components.UserEdit.username": "Användarnamn",
"components.UserEdit.userfail": "Något gick fel när användaren skulle sparas.",
"components.UserEdit.settingsDescription": "Ger behörighet att ändra alla inställningar i Overseerr. En användare måste ha denna behörighet för att kunna ge den till andra.",
"components.UserEdit.settings": "Hantera Inställningar",
"components.UserEdit.saving": "Sparar…",
"components.UserEdit.save": "Spara",
"components.UserEdit.requestDescription": "Ge behörighet att skapa förfrågningar för filmer och TV-serier.",
"components.UserEdit.request": "Förfrågan",
"components.UserEdit.permissions": "Behörigheter",
"components.UserEdit.managerequestsDescription": "Ger behörighet att hantera förfrågningar i Overseerr. Detta gäller godkännande och nekande av förfrågningar.",
"components.UserEdit.managerequests": "Hantera förfrågningar",
"components.UserEdit.edituser": "Redigera användare",
"components.UserEdit.avatar": "Avatar",
"components.UserEdit.autoapproveDescription": "Automatiskt godkännande av samtliga förfrågningar skapade av denna användare.",
"components.UserEdit.autoapprove": "Automatiskt Godkännande",
"components.UserEdit.adminDescription": "Full administratörstillgång. Slipper alla behörighetskontroller.",
"components.UserEdit.admin": "Administratör",
"components.TvDetails.userrating": "Användarbetyg",
"components.TvDetails.unavailable": "Otillgänglig",
"components.TvDetails.status": "Status",
"components.TvDetails.similarsubtext": "Andra TV-serier som liknar {title}",
"components.TvDetails.similar": "Liknande TV-serier",
"components.TvDetails.showtype": "Visa Typ",
"components.TvDetails.requestmore": "Fler förfrågningar",
"components.TvDetails.request": "Förfrågan",
"components.TvDetails.recommendationssubtext": "Om du tycker om {title}, kanske du också tycker om…",
"components.TvDetails.recommendations": "Rekommendationer",
"components.TvDetails.pending": "Väntande",
"components.TvDetails.overviewunavailable": "Beskrivning otillgänglig",
"components.TvDetails.overview": "Beskrivning",
"components.TvDetails.originallanguage": "Ursprungligt Språk",
"components.TvDetails.network": "Nätverk",
"components.TvDetails.manageModalTitle": "Hantera TV-serier",
"components.TvDetails.manageModalRequests": "Förfrågningar",
"components.TvDetails.manageModalNoRequests": "Inga förfrågningar",
"components.TvDetails.manageModalClearMediaWarning": "Detta kommer radera all media data inklusive förfrågningar som finns för objektet. Detta går inte att ångra. Om objektet finns i ditt Plexbibliotek kommer media informationen att återskapas vid nästa synkronisering.",
"components.TvDetails.manageModalClearMedia": "Rensa All Media Data",
"components.TvDetails.declinerequests": "Avböj {requestCount} {requestCount, plural, one {Request} other {Requests}}",
"components.TvDetails.decline": "Avböj",
"components.TvDetails.cast": "Roller",
"components.TvDetails.cancelrequest": "Avbryt begäran",
"components.TvDetails.available": "Tillgänglig"
}

View File

@@ -31,6 +31,14 @@ const loadLocaleData = (locale: string): Promise<any> => {
return import('../i18n/locale/nl.json');
case 'es':
return import('../i18n/locale/es.json');
case 'it':
return import('../i18n/locale/it.json');
case 'pt-BR':
return import('../i18n/locale/pt_BR.json');
case 'sr':
return import('../i18n/locale/sr.json');
case 'sv':
return import('../i18n/locale/sv.json');
default:
return import('../i18n/locale/en.json');
}
@@ -114,7 +122,7 @@ CoreApp.getInitialProps = async (initialProps) => {
if (ctx.res) {
// Check if app is initialized and redirect if necessary
const response = await axios.get<{ initialized: boolean }>(
`http://localhost:${process.env.PORT || 3000}/api/v1/settings/public`
`http://localhost:${process.env.PORT || 5055}/api/v1/settings/public`
);
const initialized = response.data.initialized;
@@ -130,7 +138,7 @@ CoreApp.getInitialProps = async (initialProps) => {
try {
// Attempt to get the user by running a request to the local api
const response = await axios.get<User>(
`http://localhost:${process.env.PORT || 3000}/api/v1/auth/me`,
`http://localhost:${process.env.PORT || 5055}/api/v1/auth/me`,
{ headers: ctx.req ? { cookie: ctx.req.headers.cookie } : undefined }
);
user = response.data;

View File

@@ -0,0 +1,9 @@
import { NextPage } from 'next';
import React from 'react';
import MovieCrew from '../../../components/MovieDetails/MovieCrew';
const MovieCrewPage: NextPage = () => {
return <MovieCrew />;
};
export default MovieCrewPage;

View File

@@ -17,7 +17,7 @@ MoviePage.getInitialProps = async (ctx) => {
if (ctx.req) {
const cookies = parseCookies(ctx);
const response = await axios.get<MovieDetailsType>(
`http://localhost:${process.env.PORT || 3000}/api/v1/movie/${
`http://localhost:${process.env.PORT || 5055}/api/v1/movie/${
ctx.query.movieId
}${cookies.locale ? `?language=${cookies.locale}` : ''}`,
{

View File

@@ -0,0 +1,9 @@
import { NextPage } from 'next';
import React from 'react';
import TvCrew from '../../../components/TvDetails/TvCrew';
const TvCrewPage: NextPage = () => {
return <TvCrew />;
};
export default TvCrewPage;

View File

@@ -17,7 +17,7 @@ TvPage.getInitialProps = async (ctx) => {
if (ctx.req) {
const cookies = parseCookies(ctx);
const response = await axios.get<TvDetailsType>(
`http://localhost:${process.env.PORT || 3000}/api/v1/tv/${
`http://localhost:${process.env.PORT || 5055}/api/v1/tv/${
ctx.query.tvId
}${cookies.locale ? `?language=${cookies.locale}` : ''}`,
{

View File

@@ -7,7 +7,7 @@ body {
}
.plex-button {
@apply w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 transition ease-in-out duration-150 text-center disabled:opacity-50;
@apply flex justify-center w-full px-4 py-2 text-sm font-medium text-center text-white transition duration-150 ease-in-out bg-indigo-600 border border-transparent rounded-md disabled:opacity-50;
background-color: #cc7b19;
}
@@ -16,7 +16,7 @@ body {
}
.titleCard {
@apply relative bg-cover rounded-lg bg-gray-800;
@apply relative bg-gray-800 bg-cover rounded-lg;
padding-bottom: 150%;
}
@@ -34,7 +34,12 @@ body {
}
.error-message {
@apply flex items-center justify-center text-center text-gray-300 relative top-0 left-0 bottom-0 right-0 h-screen flex-col;
@apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300;
}
/* Used for animating height */
.extra-max-height {
max-height: 100rem;
}
/* Hide scrollbar for Chrome, Safari and Opera */
@@ -49,5 +54,5 @@ body {
}
code {
@apply bg-gray-800 py-1 px-2 rounded-md;
@apply px-2 py-1 bg-gray-800 rounded-md;
}

View File

@@ -0,0 +1,24 @@
import { Crew } from '../../server/models/common';
const priorityJobs = [
'Director',
'Creator',
'Screenplay',
'Writer',
'Composer',
'Editor',
'Producer',
'Co-Producer',
'Executive Producer',
'Animation',
];
export const sortCrewPriority = (crew: Crew[]): Crew[] => {
return crew
.filter((person) => priorityJobs.includes(person.job))
.sort((a, b) => {
const aScore = priorityJobs.findIndex((job) => job.includes(a.job));
const bScore = priorityJobs.findIndex((job) => job.includes(b.job));
return aScore - bScore;
});
};

View File

@@ -5,9 +5,56 @@ module.exports = {
purge: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'],
theme: {
extend: {
transitionProperty: {
'max-height': 'max-height',
},
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
typography: (theme) => ({
DEFAULT: {
css: {
color: theme('colors.gray.300'),
a: {
color: theme('colors.indigo.500'),
'&:hover': {
color: theme('colors.indigo.400'),
},
},
h1: {
color: theme('colors.gray.300'),
},
h2: {
color: theme('colors.gray.300'),
},
h3: {
color: theme('colors.gray.300'),
},
h4: {
color: theme('colors.gray.300'),
},
h5: {
color: theme('colors.gray.300'),
},
h6: {
color: theme('colors.gray.300'),
},
strong: {
color: theme('colors.gray.400'),
},
code: {
color: theme('colors.gray.300'),
},
figcaption: {
color: theme('colors.gray.500'),
},
},
},
}),
},
},
variants: {
@@ -15,7 +62,7 @@ module.exports = {
borderWidth: ['first', 'last'],
margin: ['first', 'last', 'responsive'],
boxShadow: ['group-focus'],
opacity: ['disabled'],
opacity: ['disabled', 'hover'],
},
plugins: [
require('@tailwindcss/forms'),

600
yarn.lock
View File

@@ -62,10 +62,10 @@
call-me-maybe "^1.0.1"
js-yaml "^3.13.1"
"@babel/cli@^7.12.8":
version "7.12.8"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.12.8.tgz#3b24ed2fd5da353ee6f19e8935ff8c93b5fe8430"
integrity sha512-/6nQj11oaGhLmZiuRUfxsujiPDc9BBReemiXgIbxc+M5W+MIiFKYwvNDJvBfnGKNsJTKbUfEheKc9cwoPHAVQA==
"@babel/cli@^7.12.10":
version "7.12.10"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.12.10.tgz#67a1015b1cd505bde1696196febf910c4c339a48"
integrity sha512-+y4ZnePpvWs1fc/LhZRTHkTesbXkyBYuOB+5CyodZqrEuETXi3zOVfpAQIdgC3lXbHLTDG9dQosxR9BhvLKDLQ==
dependencies:
commander "^4.0.1"
convert-source-map "^1.1.0"
@@ -1365,26 +1365,26 @@
dependencies:
tslib "^2.0.1"
"@formatjs/intl-datetimeformat@3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@formatjs/intl-datetimeformat/-/intl-datetimeformat-3.1.0.tgz#d0f73a4b6147d23e08eb152c72ae06d1b0da0d9d"
integrity sha512-XKyDQ3xFgZK2w8GE2v+zE0nk/JqGKFE0UxTI716mp/+OVuws+dbQPiORfSrJceH7E3ZkfGrvO0BB8sksQNsZ+w==
"@formatjs/intl-datetimeformat@3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-datetimeformat/-/intl-datetimeformat-3.2.1.tgz#f20408cda0e932f2234ecb42fca1e90d2e75250d"
integrity sha512-teeUgUoieP0JjZYPWjJV72CoPQoukCMKGW1YUu00+TaHzZBNqVgPCdFJo2vgl1jKccOAT3VT79BHNEsR9DsBBQ==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
"@formatjs/intl-displaynames@4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-4.0.1.tgz#bb6a4e7881e666907e916da6a0cb5d532d93edc0"
integrity sha512-vhG9y+F0BudHU9ev0O9Tc5Uwz/MAcCzbBzceSnjcoUMyLLfFN6GSPBvU6+ocxWsfjhu/yL5ja+doZdhwDcSXrA==
"@formatjs/intl-displaynames@4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-4.0.2.tgz#31212238e7b07daa41fbac03186c532cbbb6c473"
integrity sha512-rOlDcFzr6UFYqH7BKI9vlpDC5MpTT48dsPxO9I6yciDlOb1IyqvIgUs+xsuNOj96akDCDrgwocrdJ1VEDO0Ntw==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
"@formatjs/intl-listformat@5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-5.0.1.tgz#25994d06acc81a2a0eaae9ac59e7a2fa851be8f0"
integrity sha512-x1gqI3xvTn8uTY0W+bL4ySW/5HFeQXkNNfsdoaRtX2b/HNa4fZoU1EaA6koAk9gUAWSR5Ofe1Ps49CXaMvwcTg==
"@formatjs/intl-listformat@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-5.0.2.tgz#090055c437bf7176a7268a285f5d06fc7e963280"
integrity sha512-Y+7/Dw3oe29kT4afbw2KCSzast6M04ibidBMMPqjxOHHxan1LeL0KQsY/iRHTgTAcfiSIqZnneJZjZi4MzjLJg==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
@@ -1396,30 +1396,39 @@
dependencies:
"@formatjs/ecma402-abstract" "^1.2.1"
"@formatjs/intl-relativetimeformat@8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-8.0.0.tgz#e7234f165932a22ca6faf015b53bf7a53dbe5350"
integrity sha512-GKJvd2+Sx0BJqsKt2rBbkgGAwfBjKVnvlRTZQ+OhgSEOeRBHOtaub1jUx8ScQoS5Xe0RFLvTLL2LSnajg6EXkw==
"@formatjs/intl-relativetimeformat@8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-8.0.1.tgz#9fcad0dba673cf0e21b4b9f83dab22ca6b482901"
integrity sha512-yMCtrDeQnqx95ucaYbHc1BP4XUP0q+JoMiP8kzMe04AgVvkfAScsoRuKfXw1EH1FkV51C/vqWIKDoGj1WoZnxQ==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
"@formatjs/intl@1.4.10":
version "1.4.10"
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.4.10.tgz#0b9b9970649630f7904f7ff930da3cdc8a897d17"
integrity sha512-CwbOmAnM2QKBUs6Eps1ry0YBe9nIQgQp9xQyxth/0BjJ8zRE3gIUzdNrLNCZ41nHuNPVFJRRIX79+yu5l+A56w==
"@formatjs/intl@1.4.13":
version "1.4.13"
resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.4.13.tgz#634e8e7d29385ade5cf7e7c0ba8aae63e585cba0"
integrity sha512-GEWwkaNFnskOGGd6gq0Y0RetiH2iNnARXzQ+glR2RqU0xk00aS5KpwkEDo1hN9NaO9fRr9UDvzDoEu9foQFVmA==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
"@formatjs/intl-datetimeformat" "3.1.0"
"@formatjs/intl-displaynames" "4.0.1"
"@formatjs/intl-listformat" "5.0.1"
"@formatjs/intl-relativetimeformat" "8.0.0"
"@formatjs/intl-datetimeformat" "3.2.1"
"@formatjs/intl-displaynames" "4.0.2"
"@formatjs/intl-listformat" "5.0.2"
"@formatjs/intl-relativetimeformat" "8.0.1"
fast-memoize "^2.5.2"
intl-messageformat "9.3.20"
intl-messageformat-parser "6.0.18"
intl-messageformat "9.4.0"
intl-messageformat-parser "6.1.0"
tslib "^2.0.1"
"@formatjs/ts-transformer@2.12.10", "@formatjs/ts-transformer@^2.6.0":
"@formatjs/ts-transformer@2.12.11":
version "2.12.11"
resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.12.11.tgz#fedbc44a44a7da6925d149e3051cd8a7a869d8c2"
integrity sha512-XjknAXQEy7s8Q9LsyECFo1369kctH/C841o/JeDqHRDhkgn1vV/IlF3v2qli7mxEc+L2JcO8LUwqOALpTBW/5A==
dependencies:
intl-messageformat-parser "6.1.0"
tslib "^2.0.1"
typescript "^4.0"
"@formatjs/ts-transformer@^2.6.0":
version "2.12.10"
resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-2.12.10.tgz#4f8758ea89e2536239b573da98f99454a4952ebf"
integrity sha512-H8mtPQcyXxLo3GJGkNVj3ZlmebeqxQfVTIvGsdpE1oXKZ/SxKqvC7ZeHlbZUyXUEiRwdJ4Hfsgw1QzsmTJnicw==
@@ -2076,6 +2085,13 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f"
integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==
"@types/mdast@^3.0.0", "@types/mdast@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw==
dependencies:
"@types/unist" "*"
"@types/mime@*":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"
@@ -2108,10 +2124,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785"
integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==
"@types/node@^14.14.11":
version "14.14.11"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.11.tgz#fc25a4248a5e8d0837019b1d170146d07334abe0"
integrity sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ==
"@types/node@^14.14.14":
version "14.14.14"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz#f7fd5f3cc8521301119f63910f0fb965c7d761ae"
integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==
"@types/nodemailer@*", "@types/nodemailer@^6.4.0":
version "6.4.0"
@@ -2215,6 +2231,11 @@
"@types/express" "*"
"@types/serve-static" "*"
"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
"@types/uuid@^8.3.0":
version "8.3.0"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f"
@@ -2232,71 +2253,71 @@
resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.31.tgz#b1a620b115c96db7b3bfdf0cf54aee0c57139245"
integrity sha512-QcJ5ZczaXAqbVD3o8mw/mEBhRvO5UAdTtbvgwL/OgoWubvNBh6/MxLBAigtcgIFaq3shon9m3POIxQaLQt4fxQ==
"@types/yup@^0.29.10":
version "0.29.10"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.10.tgz#1bfa4c4a47a6f57fcc8510948757b9e47c0d6ca3"
integrity sha512-kRKRZaWkxxnOK7H5C4oWqhCw9ID1QF3cBZ2oAPoXYsjIncwgpDGigWtXGjZ91t+hsc3cvPdBci9YoJo1A96CYg==
"@types/yup@^0.29.11":
version "0.29.11"
resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e"
integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g==
"@typescript-eslint/eslint-plugin@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz#66758cbe129b965fe9c63b04b405d0cf5280868b"
integrity sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ==
"@typescript-eslint/eslint-plugin@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.10.0.tgz#19ed3baf4bc4232c5a7fcd32eaca75c3a5baf9f3"
integrity sha512-h6/V46o6aXpKRlarP1AiJEXuCJ7cMQdlpfMDrcllIgX3dFkLwEBTXAoNP98ZoOmqd1xvymMVRAI4e7yVvlzWEg==
dependencies:
"@typescript-eslint/experimental-utils" "4.9.1"
"@typescript-eslint/scope-manager" "4.9.1"
"@typescript-eslint/experimental-utils" "4.10.0"
"@typescript-eslint/scope-manager" "4.10.0"
debug "^4.1.1"
functional-red-black-tree "^1.0.1"
regexpp "^3.0.0"
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz#86633e8395191d65786a808dc3df030a55267ae2"
integrity sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg==
"@typescript-eslint/experimental-utils@4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.10.0.tgz#dbf5d0f89802d5feaf7d11e5b32df29bbc2f3a0e"
integrity sha512-opX+7ai1sdWBOIoBgpVJrH5e89ra1KoLrJTz0UtWAa4IekkKmqDosk5r6xqRaNJfCXEfteW4HXQAwMdx+jjEmw==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/scope-manager" "4.9.1"
"@typescript-eslint/types" "4.9.1"
"@typescript-eslint/typescript-estree" "4.9.1"
"@typescript-eslint/scope-manager" "4.10.0"
"@typescript-eslint/types" "4.10.0"
"@typescript-eslint/typescript-estree" "4.10.0"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/parser@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.1.tgz#2d74c4db5dd5117379a9659081a4d1ec02629055"
integrity sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g==
"@typescript-eslint/parser@^4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.10.0.tgz#1a622b0847b765b2d8f0ede6f0cdd85f03d76031"
integrity sha512-amBvUUGBMadzCW6c/qaZmfr3t9PyevcSWw7hY2FuevdZVp5QPw/K76VSQ5Sw3BxlgYCHZcK6DjIhSZK0PQNsQg==
dependencies:
"@typescript-eslint/scope-manager" "4.9.1"
"@typescript-eslint/types" "4.9.1"
"@typescript-eslint/typescript-estree" "4.9.1"
"@typescript-eslint/scope-manager" "4.10.0"
"@typescript-eslint/types" "4.10.0"
"@typescript-eslint/typescript-estree" "4.10.0"
debug "^4.1.1"
"@typescript-eslint/scope-manager@4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz#cc2fde310b3f3deafe8436a924e784eaab265103"
integrity sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ==
"@typescript-eslint/scope-manager@4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.10.0.tgz#dbd7e1fc63d7363e3aaff742a6f2b8afdbac9d27"
integrity sha512-WAPVw35P+fcnOa8DEic0tQUhoJJsgt+g6DEcz257G7vHFMwmag58EfowdVbiNcdfcV27EFR0tUBVXkDoIvfisQ==
dependencies:
"@typescript-eslint/types" "4.9.1"
"@typescript-eslint/visitor-keys" "4.9.1"
"@typescript-eslint/types" "4.10.0"
"@typescript-eslint/visitor-keys" "4.10.0"
"@typescript-eslint/types@3.10.1":
version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
"@typescript-eslint/types@4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.1.tgz#a1a7dd80e4e5ac2c593bc458d75dd1edaf77faa2"
integrity sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA==
"@typescript-eslint/types@4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.10.0.tgz#12f983750ebad867f0c806e705c1953cd6415789"
integrity sha512-+dt5w1+Lqyd7wIPMa4XhJxUuE8+YF+vxQ6zxHyhLGHJjHiunPf0wSV8LtQwkpmAsRi1lEOoOIR30FG5S2HS33g==
"@typescript-eslint/typescript-estree@4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz#6e5b86ff5a5f66809e1f347469fadeec69ac50bf"
integrity sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw==
"@typescript-eslint/typescript-estree@4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.10.0.tgz#1e62e45fd57866afd42daf5e9fb6bd4e8dbcfa75"
integrity sha512-mGK0YRp9TOk6ZqZ98F++bW6X5kMTzCRROJkGXH62d2azhghmq+1LNLylkGe6uGUOQzD452NOAEth5VAF6PDo5g==
dependencies:
"@typescript-eslint/types" "4.9.1"
"@typescript-eslint/visitor-keys" "4.9.1"
"@typescript-eslint/types" "4.10.0"
"@typescript-eslint/visitor-keys" "4.10.0"
debug "^4.1.1"
globby "^11.0.1"
is-glob "^4.0.1"
@@ -2325,12 +2346,12 @@
dependencies:
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/visitor-keys@4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz#d76374a58c4ead9e92b454d186fea63487b25ae1"
integrity sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ==
"@typescript-eslint/visitor-keys@4.10.0":
version "4.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.10.0.tgz#9478822329a9bc8ebcc80623d7f79a01da5ee451"
integrity sha512-hPyz5qmDMuZWFtHZkjcCpkAKHX8vdu1G3YsCLEd25ryZgnJfj6FQuJ5/O7R+dB1ueszilJmAFMtlU4CA6se3Jg==
dependencies:
"@typescript-eslint/types" "4.9.1"
"@typescript-eslint/types" "4.10.0"
eslint-visitor-keys "^2.0.0"
"@webassemblyjs/ast@1.9.0":
@@ -2930,11 +2951,6 @@ ast-types@0.13.2:
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48"
integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==
astral-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
@@ -3063,18 +3079,18 @@ babel-plugin-react-intl@^7.0.0:
intl-messageformat-parser "^5.3.7"
schema-utils "^2.6.6"
babel-plugin-react-intl@^8.2.21:
version "8.2.21"
resolved "https://registry.yarnpkg.com/babel-plugin-react-intl/-/babel-plugin-react-intl-8.2.21.tgz#44aaf4aa59467fda661550740553abdc62f75251"
integrity sha512-FuKZt7Jv+rut8usU2AYjYQzggmx3tGnE16T5/rbXp8A1aecLs6tAgyFSqFg+9JJGEQheFot6lrQY5Lu+fq3x0g==
babel-plugin-react-intl@^8.2.22:
version "8.2.22"
resolved "https://registry.yarnpkg.com/babel-plugin-react-intl/-/babel-plugin-react-intl-8.2.22.tgz#076bbcf792a7d975803917dd49c9906799285ece"
integrity sha512-maUrmP2NY1falW5dYXGfq77YjGpQPLZUpzTgs/jRevTLTcey1u1+lTv01pA6so+iPtUfxkH3er7Z1P9zZNvKOg==
dependencies:
"@babel/core" "^7.9.0"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/types" "^7.9.5"
"@formatjs/ts-transformer" "2.12.10"
"@formatjs/ts-transformer" "2.12.11"
"@types/babel__core" "^7.1.7"
"@types/schema-utils" "^2.4.0"
intl-messageformat-parser "6.0.18"
intl-messageformat-parser "6.1.0"
schema-utils "^3.0.0"
tslib "^2.0.1"
@@ -3103,6 +3119,11 @@ babel-walk@3.0.0-canary-5:
dependencies:
"@babel/types" "^7.9.6"
bail@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -3655,6 +3676,16 @@ chalk@^3.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
character-entities@^1.0.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b"
integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
character-parser@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0"
@@ -3662,6 +3693,11 @@ character-parser@^2.2.0:
dependencies:
is-regex "^1.0.3"
character-reference-invalid@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560"
integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -5120,6 +5156,15 @@ dom-serializer@1.0.1:
domhandler "^3.0.0"
entities "^2.0.0"
dom-serializer@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
entities "^2.0.0"
dom-serializer@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
@@ -5143,6 +5188,11 @@ domelementtype@^2.0.1:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
domelementtype@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
domhandler@3.0.0, domhandler@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.0.0.tgz#51cd13efca31da95bbb0c5bee3a48300e333b3e9"
@@ -5157,6 +5207,20 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
domhandler@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a"
integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==
dependencies:
domelementtype "^2.0.1"
domhandler@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e"
integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==
dependencies:
domelementtype "^2.1.0"
domutils@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
@@ -5182,6 +5246,15 @@ domutils@^1.5.1, domutils@^1.7.0:
dom-serializer "0"
domelementtype "1"
domutils@^2.4.2:
version "2.4.4"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.0.1"
domhandler "^4.0.0"
dot-prop@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4"
@@ -5513,23 +5586,23 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-config-prettier@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz#c1ae4106f74e6c0357f44adb076771d032ac0e97"
integrity sha512-8Y8lGLVPPZdaNA7JXqnvETVC7IiVRgAP6afQu9gOQRn90YY3otMNh+x7Vr2vMePQntF+5erdSUBqSzCmU/AxaQ==
eslint-config-prettier@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz#5402eb559aa94b894effd6bddfa0b1ca051c858f"
integrity sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==
eslint-plugin-formatjs@^2.9.10:
version "2.9.10"
resolved "https://registry.yarnpkg.com/eslint-plugin-formatjs/-/eslint-plugin-formatjs-2.9.10.tgz#dc5b80792e4166f3b2c4ca927ca47a70c89f27d2"
integrity sha512-MFkJ6ZBs70Zdyeq2JdYn950jSgSROL4x9eWlxU/AzhNvDIiHiU0oXahx02X7wdAl1vzjCC7Ro4VWiGGecQ5cpA==
eslint-plugin-formatjs@^2.9.11:
version "2.9.11"
resolved "https://registry.yarnpkg.com/eslint-plugin-formatjs/-/eslint-plugin-formatjs-2.9.11.tgz#a44d0bc4c32f408e8091f2dcda7cfd18dda56fdc"
integrity sha512-2JY/hAqwQIyIMpJb1CANUhlZL0ZiB3uvGLrIUSSOlu8TJjGI8m57l5j+/8jDlKrAtHy9qvFp5ywR+8qi9vDvlA==
dependencies:
"@formatjs/ts-transformer" "2.12.10"
"@formatjs/ts-transformer" "2.12.11"
"@types/emoji-regex" "^8.0.0"
"@types/eslint" "^7.2.0"
"@types/estree" "^0.0.45"
"@typescript-eslint/typescript-estree" "^3.6.0"
emoji-regex "^9.0.0"
intl-messageformat-parser "6.0.18"
intl-messageformat-parser "6.1.0"
tslib "^2.0.1"
eslint-plugin-jsx-a11y@^6.4.1:
@@ -5549,10 +5622,10 @@ eslint-plugin-jsx-a11y@^6.4.1:
jsx-ast-utils "^3.1.0"
language-tags "^1.0.5"
eslint-plugin-prettier@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.2.0.tgz#af391b2226fa0e15c96f36c733f6e9035dbd952c"
integrity sha512-kOUSJnFjAUFKwVxuzy6sA5yyMx6+o9ino4gCdShzBNx4eyFRudWRYKCFolKjoM40PEiuU6Cn7wBLfq3WsGg7qg==
eslint-plugin-prettier@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.0.tgz#61e295349a65688ffac0b7808ef0a8244bdd8d40"
integrity sha512-tMTwO8iUWlSRZIwS9k7/E4vrTsfvsrcM5p1eftyuqWH25nKsz/o6/54I7jwQ/3zobISyC7wMy9ZsFwgTxOcOpQ==
dependencies:
prettier-linter-helpers "^1.0.0"
@@ -5611,10 +5684,10 @@ eslint-visitor-keys@^2.0.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
eslint@^7.15.0:
version "7.15.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.15.0.tgz#eb155fb8ed0865fcf5d903f76be2e5b6cd7e0bc7"
integrity sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==
eslint@^7.16.0:
version "7.16.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.16.0.tgz#a761605bf9a7b32d24bb7cde59aeb0fd76f06092"
integrity sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==
dependencies:
"@babel/code-frame" "^7.0.0"
"@eslint/eslintrc" "^0.2.2"
@@ -5650,7 +5723,7 @@ eslint@^7.15.0:
semver "^7.2.1"
strip-ansi "^6.0.0"
strip-json-comments "^3.1.0"
table "^5.2.3"
table "^6.0.4"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
@@ -5802,10 +5875,10 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
dependencies:
homedir-polyfill "^1.0.1"
express-openapi-validator@^4.8.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/express-openapi-validator/-/express-openapi-validator-4.8.0.tgz#f45d52728bd4efabd71c43c4a8424892173ffff9"
integrity sha512-cDcMOiawm0ZCMKKltn0ySwQum6gSV8kxImc19UDu3Wu67GFDYe7qQHwmVcAISR/AxfpPkzum/LphYqZDfVRr1w==
express-openapi-validator@^4.9.4:
version "4.9.4"
resolved "https://registry.yarnpkg.com/express-openapi-validator/-/express-openapi-validator-4.9.4.tgz#2ac0b5fcbcb0c4b74a6f4df2674dbd27b1ce81d8"
integrity sha512-TAk9DcEnfwewdvou3jXLYGCgx120UMKRF/bN6vcmNPiDQPuk5axMxL9QwG5pterRkuq0LJcVQkK2dfifVnyMPA==
dependencies:
ajv "^6.12.6"
content-type "^1.0.4"
@@ -5892,7 +5965,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@~3.0.2:
extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -6214,10 +6287,10 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formik@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.5.tgz#addf4ed7a15ebddf22c883a3d358cd27c8a91a55"
integrity sha512-KkOsyYmh5xsow+wlbdL9QSkqvbiHSb1RIToBKiooCFW4lyypn+ZlHGjTuuOqUWBqZaI5nCEupeI275Mo6tFBzg==
formik@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d"
integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA==
dependencies:
deepmerge "^2.1.1"
hoist-non-react-statics "^3.3.0"
@@ -6802,6 +6875,16 @@ html-tags@^3.1.0:
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
html-to-react@^1.3.4:
version "1.4.5"
resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.5.tgz#59091c11021d1ef315ef738460abb6a4a41fe1ce"
integrity sha512-KONZUDFPg5OodWaQu2ymfkDmU0JA7zB1iPfvyHehTmMUZnk0DS7/TyCMTzsLH6b4BvxX15g88qZCXFhJWktsmA==
dependencies:
domhandler "^3.3.0"
htmlparser2 "^5.0"
lodash.camelcase "^4.3.0"
ramda "^0.27.1"
html-to-text@6.0.0, html-to-text@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-6.0.0.tgz#8b48adb1b781a8378f374c5bb481864a169f59f4"
@@ -6835,6 +6918,16 @@ htmlparser2@^3.9.1:
inherits "^2.0.1"
readable-stream "^3.1.1"
htmlparser2@^5.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7"
integrity sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==
dependencies:
domelementtype "^2.0.1"
domhandler "^3.3.0"
domutils "^2.4.2"
entities "^2.0.0"
http-cache-semantics@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
@@ -6926,10 +7019,10 @@ humanize-ms@^1.2.1:
dependencies:
ms "^2.0.0"
husky@^4.3.5:
version "4.3.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.5.tgz#ab8d2a0eb6b62fef2853ee3d442c927d89290902"
integrity sha512-E5S/1HMoDDaqsH8kDF5zeKEQbYqe3wL9zJDyqyYqc8I4vHBtAoxkDBGXox0lZ9RI+k5GyB728vZdmnM4bYap+g==
husky@^4.3.6:
version "4.3.6"
resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.6.tgz#ebd9dd8b9324aa851f1587318db4cccb7665a13c"
integrity sha512-o6UjVI8xtlWRL5395iWq9LKDyp/9TE7XMOTvIpEVzW638UcGxTmV5cfel6fsk/jbZSTlvfGVJf2svFtybcIZag==
dependencies:
chalk "^4.0.0"
ci-info "^2.0.0"
@@ -7149,6 +7242,14 @@ intl-messageformat-parser@6.0.18:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
intl-messageformat-parser@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.1.0.tgz#134328151c41592d9e1a61f5c6779c06c8eb3f08"
integrity sha512-nPPh2kOrKqlh4D9bCAetxkrUiq5/6S1exPQyg52Ihusy0ECNGhZ0Qmq8pFRK9gWIuiQPVmLA7eSNp8diC2tX3w==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
tslib "^2.0.1"
intl-messageformat-parser@^5.3.7:
version "5.5.1"
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-5.5.1.tgz#f09a692755813e6220081e3374df3fb1698bd0c6"
@@ -7156,13 +7257,13 @@ intl-messageformat-parser@^5.3.7:
dependencies:
"@formatjs/intl-numberformat" "^5.5.2"
intl-messageformat@9.3.20:
version "9.3.20"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.20.tgz#87ec7e5f7a0f5d13157dc8bed88fe37b4c57b2a1"
integrity sha512-jmpjYHE076J/0CIofrPhtUC4LfmsAhuv4JMQxytl2KJd2bim+3+gQJh+Z1vyHUzcj4fIHdt388ZGchb8f0NwOA==
intl-messageformat@9.4.0:
version "9.4.0"
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.4.0.tgz#b9c9c00a6e88a8f1ffd9ee3e54340c9dfb765d13"
integrity sha512-zcF8OWG52dCwwePkykqqv7F038vCaixPR14Lr3YUFc9jRdGoCazl2dTE3BwBaeHr3pG/qYb6A/mwMKrj4LFt9Q==
dependencies:
fast-memoize "^2.5.2"
intl-messageformat-parser "6.0.18"
intl-messageformat-parser "6.1.0"
tslib "^2.0.1"
intl@^1.2.5:
@@ -7212,6 +7313,19 @@ is-accessor-descriptor@^1.0.0:
dependencies:
kind-of "^6.0.0"
is-alphabetical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d"
integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
is-alphanumerical@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf"
integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
dependencies:
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -7241,6 +7355,11 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-buffer@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
is-callable@^1.1.4, is-callable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
@@ -7298,6 +7417,11 @@ is-date-object@^1.0.1:
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
is-decimal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -7377,6 +7501,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-hexadecimal@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
is-installed-globally@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
@@ -8209,6 +8338,11 @@ lodash._root@~3.0.0:
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=
lodash.camelcase@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
lodash.capitalize@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
@@ -8524,6 +8658,29 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
mdast-add-list-metadata@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf"
integrity sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==
dependencies:
unist-util-visit-parents "1.1.2"
mdast-util-from-markdown@^0.8.0:
version "0.8.4"
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.4.tgz#2882100c1b9fc967d3f83806802f303666682d32"
integrity sha512-jj891B5pV2r63n2kBTFh8cRI2uR9LQHsXG1zSDqfhXkIlDzrTcIlbB5+5aaYEkl8vOPIOPLf8VT7Ere1wWTMdw==
dependencies:
"@types/mdast" "^3.0.0"
mdast-util-to-string "^2.0.0"
micromark "~2.11.0"
parse-entities "^2.0.0"
unist-util-stringify-position "^2.0.0"
mdast-util-to-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b"
integrity sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==
mdn-data@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
@@ -8672,6 +8829,14 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micromark@~2.11.0:
version "2.11.2"
resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.2.tgz#e8b6a05f54697d2d3d27fc89600c6bc40dd05f35"
integrity sha512-IXuP76p2uj8uMg4FQc1cRE7lPCLsfAXuEfdjtdO55VRiFO1asrCSQ5g43NmPqFtRwzEnEhafRVzn2jg0UiKArQ==
dependencies:
debug "^4.0.0"
parse-entities "^2.0.0"
micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -9243,6 +9408,11 @@ nodemailer@6.4.16, nodemailer@^6.4.16:
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.16.tgz#5cb6391b1d79ab7eff32d6f9f48366b5a7117293"
integrity sha512-68K0LgZ6hmZ7PVmwL78gzNdjpj5viqBdFqKrTtr9bZbJYj6BRj5W6WGkxXrEnUl3Co3CBXi3CZBUlpV/foGnOQ==
nodemailer@^6.4.17:
version "6.4.17"
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.17.tgz#8de98618028953b80680775770f937243a7d7877"
integrity sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==
nodemon@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.6.tgz#1abe1937b463aaf62f0d52e2b7eaadf28cc2240d"
@@ -10054,6 +10224,18 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
pbkdf2 "^3.0.3"
safe-buffer "^5.1.1"
parse-entities@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8"
integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
dependencies:
character-entities "^1.0.0"
character-entities-legacy "^1.0.0"
character-reference-invalid "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
@@ -11117,6 +11299,11 @@ qw@~1.0.1:
resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
integrity sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=
ramda@^0.27.1:
version "0.27.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9"
integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==
random-bytes@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@@ -11191,29 +11378,45 @@ react-intersection-observer@^8.31.0:
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.31.0.tgz#0ed21aaf93c4c0475b22b0ccaba6169076d01605"
integrity sha512-XraIC/tkrD9JtrmVA7ypEN1QIpKc52mXBH1u/bz/aicRLo8QQEJQAMUTb8mz4B6dqpPwyzgjrr7Ljv/2ACDtqw==
react-intl@^5.10.6:
version "5.10.6"
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.10.6.tgz#064dd69f3e96434f9145cac0b21c5a47f3ac6088"
integrity sha512-IWhPTGGggs/n/OKkhEHAZ7rCfQ8m/2hmYIwJtOPuNQVyKKU+R863q4xP/+uCW1NOXB+yvbF2p7CB/v2hkuEVCA==
react-intl@^5.10.9:
version "5.10.9"
resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.10.9.tgz#cab8d9445059d3544cffc762c3e6af47ef9bc8ad"
integrity sha512-DfUF4YMlZqaNRfgfvf46AcXxz7pDi7pkxRbQoimUJWEkjep+6QYLlH7ogypysGD1Sl5kbWi7b69bbG7wPqt1vA==
dependencies:
"@formatjs/ecma402-abstract" "1.5.0"
"@formatjs/intl" "1.4.10"
"@formatjs/intl-displaynames" "4.0.1"
"@formatjs/intl-listformat" "5.0.1"
"@formatjs/intl-relativetimeformat" "8.0.0"
"@formatjs/intl" "1.4.13"
"@formatjs/intl-displaynames" "4.0.2"
"@formatjs/intl-listformat" "5.0.2"
"@formatjs/intl-relativetimeformat" "8.0.1"
"@types/hoist-non-react-statics" "^3.3.1"
fast-memoize "^2.5.2"
hoist-non-react-statics "^3.3.2"
intl-messageformat "9.3.20"
intl-messageformat-parser "6.0.18"
intl-messageformat "9.4.0"
intl-messageformat-parser "6.1.0"
shallow-equal "^1.2.1"
tslib "^2.0.1"
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1:
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-markdown@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-5.0.3.tgz#41040ea7a9324b564b328fb81dd6c04f2a5373ac"
integrity sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w==
dependencies:
"@types/mdast" "^3.0.3"
"@types/unist" "^2.0.3"
html-to-react "^1.3.4"
mdast-add-list-metadata "1.0.1"
prop-types "^15.7.2"
react-is "^16.8.6"
remark-parse "^9.0.0"
unified "^9.0.0"
unist-util-visit "^2.0.0"
xtend "^4.0.1"
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@@ -11569,6 +11772,13 @@ regjsparser@^0.6.4:
dependencies:
jsesc "~0.5.0"
remark-parse@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640"
integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==
dependencies:
mdast-util-from-markdown "^0.8.0"
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -12180,15 +12390,6 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
slice-ansi@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
dependencies:
ansi-styles "^3.2.0"
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
slice-ansi@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787"
@@ -12836,22 +13037,22 @@ swagger-ui-express@^4.1.5:
dependencies:
swagger-ui-dist "^3.18.1"
swr@^0.3.9:
version "0.3.9"
resolved "https://registry.yarnpkg.com/swr/-/swr-0.3.9.tgz#a179a795244c7b68684af6a632f1ad579e6a69e0"
integrity sha512-lyN4SjBzpoW4+v3ebT7JUtpzf9XyzrFwXIFv+E8ZblvMa5enSNaUBs4EPkL8gGA/GDMLngEmB53o5LaNboAPfg==
swr@^0.3.11:
version "0.3.11"
resolved "https://registry.yarnpkg.com/swr/-/swr-0.3.11.tgz#f7f50ed26c06afea4249482cec504768a2272664"
integrity sha512-ya30LuRGK2R7eDlttnb7tU5EmJYJ+N6ytIOM2j0Hqs0qauJcDjVLDOGy7KmFeH5ivOwLHalFaIyYl2K+SGa7HQ==
dependencies:
dequal "2.0.2"
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
table@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/table/-/table-6.0.4.tgz#c523dd182177e926c723eb20e1b341238188aa0d"
integrity sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==
dependencies:
ajv "^6.10.2"
lodash "^4.17.14"
slice-ansi "^2.1.0"
string-width "^3.0.0"
ajv "^6.12.4"
lodash "^4.17.20"
slice-ansi "^4.0.0"
string-width "^4.2.0"
"tailwindcss@npm:@tailwindcss/postcss7-compat":
version "2.0.1"
@@ -13200,6 +13401,11 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
trough@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
ts-node@^9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d"
@@ -13350,10 +13556,10 @@ typescript@^4.0:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2"
integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==
typescript@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9"
integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==
typescript@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
uc.micro@^1.0.1:
version "1.0.6"
@@ -13422,6 +13628,18 @@ unicode-property-aliases-ecmascript@^1.0.4:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4"
integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==
unified@^9.0.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8"
integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==
dependencies:
bail "^1.0.0"
extend "^3.0.0"
is-buffer "^2.0.0"
is-plain-obj "^2.0.0"
trough "^1.0.0"
vfile "^4.0.0"
union-value@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -13465,6 +13683,40 @@ unique-string@^2.0.0:
dependencies:
crypto-random-string "^2.0.0"
unist-util-is@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.4.tgz#3e9e8de6af2eb0039a59f50c9b3e99698a924f50"
integrity sha512-3dF39j/u423v4BBQrk1AQ2Ve1FxY5W3JKwXxVFzBODQ6WEvccguhgp802qQLKSnxPODE6WuRZtV+ohlUg4meBA==
unist-util-stringify-position@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da"
integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==
dependencies:
"@types/unist" "^2.0.2"
unist-util-visit-parents@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06"
integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q==
unist-util-visit-parents@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6"
integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==
dependencies:
"@types/unist" "^2.0.0"
unist-util-is "^4.0.0"
unist-util-visit@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c"
integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==
dependencies:
"@types/unist" "^2.0.0"
unist-util-is "^4.0.0"
unist-util-visit-parents "^3.0.0"
universal-user-agent@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
@@ -13704,6 +13956,24 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vfile-message@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a"
integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==
dependencies:
"@types/unist" "^2.0.0"
unist-util-stringify-position "^2.0.0"
vfile@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624"
integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==
dependencies:
"@types/unist" "^2.0.0"
is-buffer "^2.0.0"
unist-util-stringify-position "^2.0.0"
vfile-message "^2.0.0"
vm-browserify@1.1.2, vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
@@ -14027,7 +14297,7 @@ xmlbuilder@~9.0.1:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1:
xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==