From 3e5eb4e148a9f88b871abc4ee1784b870f691534 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 2 Jan 2022 20:56:19 -0800
Subject: [PATCH 01/76] fix(radarr): remove PreDB minimum availability option
(#2386)
---
src/components/Settings/RadarrModal/index.tsx | 16 ++++++++++++----
src/i18n/locale/en.json | 3 +++
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx
index 63fe4fed..e9ec06c0 100644
--- a/src/components/Settings/RadarrModal/index.tsx
+++ b/src/components/Settings/RadarrModal/index.tsx
@@ -67,6 +67,9 @@ const messages = defineMessages({
validationBaseUrlTrailingSlash: 'Base URL must not end in a trailing slash',
notagoptions: 'No tags.',
selecttags: 'Select tags',
+ announced: 'Announced',
+ inCinemas: 'In Cinemas',
+ released: 'Released',
});
interface TestResponse {
@@ -584,10 +587,15 @@ const RadarrModal: React.FC = ({
id="minimumAvailability"
name="minimumAvailability"
>
-
-
-
-
+
+
+
{errors.minimumAvailability &&
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index bb63ca59..19b997ab 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -486,6 +486,7 @@
"components.Settings.Notifications.webhookUrl": "Webhook URL",
"components.Settings.Notifications.webhookUrlTip": "Create a webhook integration in your server",
"components.Settings.RadarrModal.add": "Add Server",
+ "components.Settings.RadarrModal.announced": "Announced",
"components.Settings.RadarrModal.apiKey": "API Key",
"components.Settings.RadarrModal.baseUrl": "URL Base",
"components.Settings.RadarrModal.create4kradarr": "Add New 4K Radarr Server",
@@ -497,6 +498,7 @@
"components.Settings.RadarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.RadarrModal.externalUrl": "External URL",
"components.Settings.RadarrModal.hostname": "Hostname or IP Address",
+ "components.Settings.RadarrModal.inCinemas": "In Cinemas",
"components.Settings.RadarrModal.loadingTags": "Loading tags…",
"components.Settings.RadarrModal.loadingprofiles": "Loading quality profiles…",
"components.Settings.RadarrModal.loadingrootfolders": "Loading root folders…",
@@ -504,6 +506,7 @@
"components.Settings.RadarrModal.notagoptions": "No tags.",
"components.Settings.RadarrModal.port": "Port",
"components.Settings.RadarrModal.qualityprofile": "Quality Profile",
+ "components.Settings.RadarrModal.released": "Released",
"components.Settings.RadarrModal.rootfolder": "Root Folder",
"components.Settings.RadarrModal.selectMinimumAvailability": "Select minimum availability",
"components.Settings.RadarrModal.selectQualityProfile": "Select quality profile",
From bd93168ba1ed650baf4024569bb6a76811a99820 Mon Sep 17 00:00:00 2001
From: "Weblate (bot)"
Date: Mon, 3 Jan 2022 21:05:01 +0100
Subject: [PATCH 02/76] feat(lang): translations update from Hosted Weblate
(#2379)
* feat(lang): translated using Weblate (Danish)
Currently translated at 99.6% (994 of 997 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Simon Thyregod
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/da/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Polish)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Patryk
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Chinese (Simplified))
Currently translated at 90.3% (903 of 1000 strings)
feat(lang): translated using Weblate (Chinese (Simplified))
Currently translated at 90.3% (901 of 997 strings)
feat(lang): translated using Weblate (Chinese (Simplified))
Currently translated at 90.5% (903 of 997 strings)
Co-authored-by: Eric
Co-authored-by: Hosted Weblate
Co-authored-by: TheCatLady
Co-authored-by: wolong gl
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: TheCatLady
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Dutch)
Currently translated at 100.0% (1000 of 1000 strings)
feat(lang): translated using Weblate (Dutch)
Currently translated at 100.0% (1000 of 1000 strings)
feat(lang): translated using Weblate (Dutch)
Currently translated at 100.0% (997 of 997 strings)
Co-authored-by: DJScias
Co-authored-by: Hosted Weblate
Co-authored-by: Kobe
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Tijuco
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/
Translation: Overseerr/Overseerr Frontend
Co-authored-by: Simon Thyregod
Co-authored-by: Patryk
Co-authored-by: Eric
Co-authored-by: TheCatLady
Co-authored-by: wolong gl
Co-authored-by: DJScias
Co-authored-by: Kobe
Co-authored-by: Tijuco
---
src/i18n/locale/da.json | 29 +++++++----
src/i18n/locale/nl.json | 9 +++-
src/i18n/locale/pl.json | 7 ++-
src/i18n/locale/pt_BR.json | 5 +-
src/i18n/locale/zh_Hans.json | 95 +++++++++++++++++++-----------------
src/i18n/locale/zh_Hant.json | 7 ++-
6 files changed, 92 insertions(+), 60 deletions(-)
diff --git a/src/i18n/locale/da.json b/src/i18n/locale/da.json
index c0ac8290..c3cd0d4c 100644
--- a/src/i18n/locale/da.json
+++ b/src/i18n/locale/da.json
@@ -20,7 +20,7 @@
"components.Layout.Sidebar.settings": "Indstillinger",
"components.Layout.Sidebar.requests": "Forespørgsler",
"components.Layout.Sidebar.dashboard": "Udforsk",
- "components.Layout.SearchInput.searchPlaceholder": "Søg Film & Serier",
+ "components.Layout.SearchInput.searchPlaceholder": "Søg i Film & Serier",
"components.Layout.LanguagePicker.displaylanguage": "Grænsefladesprog",
"components.LanguageSelector.originalLanguageDefault": "Alle Sprog",
"components.LanguageSelector.languageServerDefault": "Standard ({language})",
@@ -131,7 +131,7 @@
"components.ManageSlideOver.movie": "film",
"components.PersonDetails.lifespan": "{birthdate} – {deathdate}",
"components.QuotaSelector.movies": "{count, plural, one {film} other {film}}",
- "components.RequestButton.requestmore": "Forespørg Flere",
+ "components.RequestButton.requestmore": "Forespørg om mere",
"components.RequestModal.QuotaDisplay.quotaLinkUser": "Du kan se et overblik over denne brugers forespørgselsgrænser på deres profilside.",
"components.RequestModal.selectseason": "Vælg Sæson(er)",
"components.ResetPassword.confirmpassword": "Bekræft Kodeord",
@@ -162,7 +162,7 @@
"components.RequestBlock.server": "Destinationsserver",
"components.IssueModal.CreateIssueModal.extras": "Ekstra",
"components.IssueModal.CreateIssueModal.issomethingwrong": "Er der et problem med {title}?",
- "components.IssueModal.CreateIssueModal.problemseason": "Påvirket Sæson",
+ "components.IssueModal.CreateIssueModal.problemseason": "Berørt Sæson",
"components.IssueModal.CreateIssueModal.providedetail": "Giv venligst en detaljeret beskrivelse af problemet du stødte på.",
"components.IssueModal.CreateIssueModal.reportissue": "Rapportér et Problem",
"components.IssueModal.CreateIssueModal.season": "Sæson {seasonNumber}",
@@ -172,7 +172,7 @@
"components.PermissionEdit.viewrequestsDescription": "Giv tilladelse til at se medieforespørgsler indsendt af andre brugere.",
"components.RequestButton.declinerequest": "Afvis Forespørgsel",
"components.IssueDetails.playonplex": "Afspil i Plex",
- "components.IssueDetails.problemseason": "Påvirket Sæson",
+ "components.IssueDetails.problemseason": "Berørt Sæson",
"components.IssueDetails.issuetype": "Type",
"components.IssueDetails.nocomments": "Ingen kommentarer.",
"components.IssueDetails.openedby": "#{issueId} åbnet {relativeTime} af {username}",
@@ -195,7 +195,7 @@
"components.IssueList.issues": "Problemer",
"components.IssueList.showallissues": "Vis Alle Problemer",
"components.IssueList.sortAdded": "Seneste",
- "components.IssueList.sortModified": "Sidst Ændret",
+ "components.IssueList.sortModified": "Sidst ændret",
"components.IssueModal.CreateIssueModal.allepisodes": "Alle Episoder",
"components.IssueModal.CreateIssueModal.allseasons": "Alle Sæsoner",
"components.IssueModal.CreateIssueModal.episode": "Episode {episodeNumber}",
@@ -238,7 +238,7 @@
"components.RequestList.RequestItem.requested": "Forespurgt",
"components.RequestList.showallrequests": "Vis Alle Forespørgsler",
"components.RequestList.sortAdded": "Seneste",
- "components.RequestList.sortModified": "Sidst Ændret",
+ "components.RequestList.sortModified": "Sidst ændret",
"components.RequestModal.AdvancedRequester.advancedoptions": "Avanceret",
"components.RequestModal.AdvancedRequester.animenote": "* Denne serie er en anime.",
"components.RequestModal.AdvancedRequester.selecttags": "Vælg tags",
@@ -250,7 +250,7 @@
"components.RequestModal.AdvancedRequester.tags": "Tags",
"components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {film} other {film}}",
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Ikke tilstrækkelig med sæsonforespørgsler tilbage",
- "components.RequestModal.QuotaDisplay.quotaLink": "Du kan se et overblik af dine forespørgselsgrænser på din profilside.",
+ "components.RequestModal.QuotaDisplay.quotaLink": "Du kan se et overblik over dine forespørgselsgrænser på din profilside.",
"components.RequestModal.QuotaDisplay.allowedRequests": "Du kan forespørge om {limit} {type} hver {days} dag.",
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "Denne bruger kan forespørge om {limit} {type} hver {days} dag.",
"components.IssueDetails.IssueComment.validationComment": "Du skal skrive en besked",
@@ -533,7 +533,7 @@
"components.Settings.Notifications.encryptionDefault": "Brug STARTTLS hvis tilgængeligt",
"components.Settings.Notifications.pgpPassword": "PGP Kodeord",
"components.Settings.Notifications.pgpPasswordTip": "Signér krypterede emailbeskeder med OpenPGP",
- "components.Settings.Notifications.sendSilently": "Send lydløst",
+ "components.Settings.Notifications.sendSilently": "Send Lydløst",
"components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.telegramsettingsfailed": "Telegram notifikationsindstillinger kunne ikke gemmes.",
"components.Settings.Notifications.telegramsettingssaved": "Telegram notifikationsindstillinger er blevet gemt!",
@@ -954,7 +954,7 @@
"i18n.loading": "Indlæser…",
"i18n.movie": "Film",
"i18n.movies": "Film",
- "i18n.open": "Åbn",
+ "i18n.open": "Åben",
"i18n.partiallyavailable": "Delvist Tilgængelig",
"i18n.pending": "Afventer",
"i18n.requesting": "Forespørger…",
@@ -986,5 +986,14 @@
"pages.serviceunavailable": "Service Utilgængelig",
"pages.somethingwentwrong": "Noget Gik Galt",
"i18n.saving": "Gemmer…",
- "i18n.test": "Test"
+ "i18n.test": "Test",
+ "components.RequestModal.requestseasons4k": "Forespørg efter {seasonCount} {seasonCount, plural, one {Sæson} other {Sæsoner}} i 4K",
+ "components.IssueDetails.commentplaceholder": "Tilføj en kommentar…",
+ "components.RequestModal.approve": "Godkend Forespørgsel",
+ "components.RequestModal.requestApproved": "Forespørgslen for {title} er godkendt!",
+ "components.RequestModal.requestmovies4k": "Forespørg efter {count} Film i 4K",
+ "components.RequestModal.selectmovies": "Vælg Film",
+ "components.MovieDetails.productioncountries": "Produktions{countryCount, plural, one {land} other {lande}}",
+ "components.TvDetails.productioncountries": "Produktions{countryCount, plural, one {land} other {lande}}",
+ "components.RequestModal.requestmovies": "Forespørg efter {count} Film"
}
diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json
index ef32b2bb..75586897 100644
--- a/src/i18n/locale/nl.json
+++ b/src/i18n/locale/nl.json
@@ -736,7 +736,7 @@
"components.NotificationTypeSelector.notificationTypes": "Meldingtypes",
"components.Discover.noRequests": "Geen verzoeken.",
"components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "Er is voor jouw account momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord om in te kunnen loggen als een \"lokale gebruiker\" met uw e-mailadres.",
- "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Voor deze gebruikersaccount is er momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord om ervoor te zorgen dat dit account zich kan aanmelden als een \"lokale gebruiker\".",
+ "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "Deze gebruikersaccount heeft momenteel geen wachtwoord ingesteld. Configureer hieronder een wachtwoord zodat deze account in staat is om zich aan te melden als een \"lokale gebruiker\".",
"components.Settings.serviceSettingsDescription": "Configureer je {serverType} server(s) hieronder. Je kunt meerdere {serverType} servers verbinden, maar slechts twee ervan kunnen als standaard worden gemarkeerd (één niet-4K en één 4K). Beheerders kunnen vóór de goedkeuring de server die gebruikt wordt om nieuwe aanvragen te verwerken aanpassen.",
"components.Settings.noDefaultServer": "Ten minste één {serverType} server moet als standaard worden gemarkeerd om {mediaType}verzoeken te kunnen verwerken.",
"components.Settings.noDefaultNon4kServer": "Als je slechts één enkele {serverType} server hebt voor zowel niet-4K als 4K-inhoud (of als je alleen 4K-inhoud downloadt), dan moet je {serverType} server NIET aangeduid worden als een 4K-server.",
@@ -993,5 +993,10 @@
"components.MovieDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}",
"components.RequestModal.requestmovies4k": "{count} {count, plural, one {film} other {films}} in 4K aanvragen",
"components.TvDetails.productioncountries": "Productie{countryCount, plural, one {land} other {landen}}",
- "components.IssueDetails.commentplaceholder": "Voeg een opmerking toe…"
+ "components.IssueDetails.commentplaceholder": "Voeg een opmerking toe…",
+ "components.RequestModal.requestApproved": "Verzoek voor {title} goedgekeurd!",
+ "components.RequestModal.approve": "Verzoek goedkeuren",
+ "components.Settings.RadarrModal.inCinemas": "In de bioscoop",
+ "components.Settings.RadarrModal.released": "Uitgekomen",
+ "components.Settings.RadarrModal.announced": "Aangekondigd"
}
diff --git a/src/i18n/locale/pl.json b/src/i18n/locale/pl.json
index d017c397..2ca225d9 100644
--- a/src/i18n/locale/pl.json
+++ b/src/i18n/locale/pl.json
@@ -993,5 +993,10 @@
"components.IssueDetails.commentplaceholder": "Dodaj komentarz…",
"components.MovieDetails.productioncountries": "Produkcja {countryCount, plural, one {kraj} other {kraje}}",
"components.RequestModal.requestseasons4k": "Prośba o {seasonCount} {seasonCount, plural, one {sezon} other {sezony}} w 4K",
- "components.TvDetails.productioncountries": "Produkcja {countryCount, plural, one {kraj} other {kraje}}"
+ "components.TvDetails.productioncountries": "Produkcja {countryCount, plural, one {kraj} other {kraje}}",
+ "components.RequestModal.approve": "Zatwierdź prośbę",
+ "components.RequestModal.requestApproved": "Prośba o {title} zatwierdzona!",
+ "components.Settings.RadarrModal.announced": "Zapowiedziany",
+ "components.Settings.RadarrModal.inCinemas": "W kinach",
+ "components.Settings.RadarrModal.released": "Wydany"
}
diff --git a/src/i18n/locale/pt_BR.json b/src/i18n/locale/pt_BR.json
index ff6ddf17..02e85214 100644
--- a/src/i18n/locale/pt_BR.json
+++ b/src/i18n/locale/pt_BR.json
@@ -995,5 +995,8 @@
"i18n.resolved": "Resolvido",
"components.IssueDetails.commentplaceholder": "Adicionar um comentário…",
"components.RequestModal.requestApproved": "Solicitação de {title} aprovada!",
- "components.RequestModal.approve": "Aprovar Solicitação"
+ "components.RequestModal.approve": "Aprovar Solicitação",
+ "components.Settings.RadarrModal.announced": "Anunciado",
+ "components.Settings.RadarrModal.inCinemas": "Nos Cinemas",
+ "components.Settings.RadarrModal.released": "Lançado"
}
diff --git a/src/i18n/locale/zh_Hans.json b/src/i18n/locale/zh_Hans.json
index b0d13134..36b37417 100644
--- a/src/i18n/locale/zh_Hans.json
+++ b/src/i18n/locale/zh_Hans.json
@@ -8,7 +8,7 @@
"components.UserList.userdeleteerror": "刪除用户中出了点问题。",
"components.UserList.userdeleted": "用户刪除成功!",
"components.UserList.usercreatedsuccess": "建立新用户成功!",
- "components.UserList.usercreatedfailedexisting": "您提供的电子邮件地址已由其他用户使用。",
+ "components.UserList.usercreatedfailedexisting": "你提供的电子邮件地址已由其他用户使用。",
"components.UserList.usercreatedfailed": "建立新用户中出了点问题。",
"components.UserList.user": "用户",
"components.UserList.totalrequests": "请求数",
@@ -67,10 +67,10 @@
"components.StatusBadge.status4k": "4K 版{status}",
"components.Setup.welcome": "欢迎來到 Overseerr",
"components.Setup.tip": "提示",
- "components.Setup.signinMessage": "首先,请使用您的 Plex 账户登入",
+ "components.Setup.signinMessage": "首先,请使用你的 Plex 账户登入",
"components.Setup.setup": "配置",
"components.Setup.scanbackground": "扫描将在后台继续执行。请继续设置的下一步。",
- "components.Setup.loginwithplex": "使用您的 Plex 账户",
+ "components.Setup.loginwithplex": "使用你的 Plex 账户",
"components.Setup.finishing": "完成配置中…",
"components.Setup.finish": "完成配置",
"components.Setup.continue": "继续",
@@ -78,7 +78,7 @@
"components.Setup.configureplex": "配置 Plex 服务器",
"components.Settings.webpush": "网络推送",
"components.Settings.webhook": "网络钩子",
- "components.Settings.webAppUrlTip": "使用服务器的网络应用代替「托管」的网络应用",
+ "components.Settings.webAppUrlTip": "使用服务器的网络应用代替“托管”的网络应用",
"components.Settings.webAppUrl": "网络应用网址(URL)",
"components.Settings.validationWebAppUrl": "请输入有效的 Plex 网络应用网址",
"components.Settings.validationPortRequired": "请输入有效的端口",
@@ -101,9 +101,9 @@
"components.Settings.startscan": "执行扫描",
"components.Settings.ssl": "SSL",
"components.Settings.sonarrsettings": "Sonarr 设置",
- "components.Settings.settingUpPlexDescription": "您可以手动输入您的 Plex 服务器资料,或从 plex.tv 返回的设置做选择以及自动配置。请点下拉式选单右边的按钮获取服务器列表。",
+ "components.Settings.settingUpPlexDescription": "你可以手动输入你的 Plex 服务器资料,或从 plex.tv 返回的设置做选择以及自动配置。请点下拉式选单右边的按钮获取服务器列表。",
"components.Settings.services": "服务器",
- "components.Settings.serviceSettingsDescription": "关于 {serverType} 服务器的设置。{serverType} 服务器数没有最大值限制,但您只能指定兩个服务器为默认(一个非 4K、一个 4K)。",
+ "components.Settings.serviceSettingsDescription": "关于 {serverType} 服务器的设置。{serverType} 服务器数没有最大值限制,但你只能指定兩个服务器为默认(一个非 4K、一个 4K)。",
"components.Settings.serverpresetRefreshing": "载入中…",
"components.Settings.serverpresetManualMessage": "手动设定",
"components.Settings.serverpresetLoad": "请点右边的按钮",
@@ -114,7 +114,7 @@
"components.Settings.scanning": "同步中…",
"components.Settings.scan": "媒体库同步",
"components.Settings.regionTip": "以地区可用性筛选結果",
- "components.Settings.region": "「探索」地区",
+ "components.Settings.region": "探索地区",
"components.Settings.radarrsettings": "Radarr 设置",
"components.Settings.port": "端口",
"components.Settings.plexsettingsDescription": "关于 Plex 服务器的设置。Overseerr 将定时执行媒体库扫描。",
@@ -124,14 +124,14 @@
"components.Settings.plex": "Plex",
"components.Settings.partialRequestsEnabled": "允许不完整的电视节目请求",
"components.Settings.originallanguageTip": "以原始语言筛选結果",
- "components.Settings.originallanguage": "「探索」语言",
+ "components.Settings.originallanguage": "探索语言",
"components.Settings.notrunning": "未运行",
"components.Settings.notificationsettings": "通知设置",
"components.Settings.notifications": "通知",
"components.Settings.notificationAgentSettingsDescription": "设置通知类型和代理服务。",
- "components.Settings.noDefaultServer": "您必须至少指定一个 {serverType} 服务器为默认,才能处理{mediaType}请求。",
+ "components.Settings.noDefaultServer": "你必须至少指定一个 {serverType} 服务器为默认,才能处理{mediaType}请求。",
"components.Settings.noDefaultNon4kServer": "如果你只有一台 {serverType} 服务器用于非 4K 和 4K 内容(或者如果你只下载 4k 内容),你的 {serverType} 服务器 不应该被指定为 4K 服务器。",
- "components.Settings.noDefault4kServer": "您必须指定一个 4K {serverType} 服务器为默认,才能处理 4K 的{mediaType}请求。",
+ "components.Settings.noDefault4kServer": "你必须指定一个 4K {serverType} 服务器为默认,才能处理 4K 的{mediaType}请求。",
"components.Settings.menuUsers": "用户",
"components.Settings.menuServices": "服务器",
"components.Settings.menuPlexSettings": "Plex",
@@ -142,7 +142,7 @@
"components.Settings.menuAbout": "关于 Overseerr",
"components.Settings.mediaTypeSeries": "电视节目",
"components.Settings.mediaTypeMovie": "电影",
- "components.Settings.manualscanDescription": "在正常情況下,Overseerr 会每24小时扫描您的 Plex 媒体库。最新添加的媒体将更频繁扫描。设置新的 Plex 服务器时,我们建议您执行一次手动扫描!",
+ "components.Settings.manualscanDescription": "在正常情況下,Overseerr 会每24小时扫描你的 Plex 媒体库。最新添加的媒体将更频繁扫描。设置新的 Plex 服务器时,我们建议你执行一次手动扫描!",
"components.Settings.manualscan": "媒体库手动扫描",
"components.Settings.locale": "显示语言",
"components.Settings.librariesRemaining": "媒体库剩余数: {count}",
@@ -159,7 +159,7 @@
"components.Settings.default": "默认",
"components.Settings.currentlibrary": "当前媒体库: {name}",
"components.Settings.csrfProtectionTip": "设置外部访问权限为只讀(Overseerr 必须重新启动)",
- "components.Settings.csrfProtectionHoverTip": "除非您了解此功能,请勿启用它!",
+ "components.Settings.csrfProtectionHoverTip": "除非你了解此功能,请勿启用它!",
"components.Settings.csrfProtection": "防止跨站请求伪造(CSRF)攻击",
"components.Settings.copied": "应用程序密钥已复制到剪贴板。",
"components.Settings.cancelscan": "取消扫描",
@@ -177,7 +177,7 @@
"components.Settings.SonarrModal.validationPortRequired": "请输入有效的端口",
"components.Settings.SonarrModal.validationNameRequired": "请输入服务器名称",
"components.Settings.SonarrModal.validationLanguageProfileRequired": "必须设置语言",
- "components.Settings.SonarrModal.validationHostnameRequired": "您必须提供有效的主机名或 IP 地址",
+ "components.Settings.SonarrModal.validationHostnameRequired": "你必须提供有效的主机名或 IP 地址",
"components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "必须刪除結尾斜線",
"components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "必须添加前置斜線",
"components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "必须刪除結尾斜線",
@@ -220,9 +220,9 @@
"components.PermissionEdit.admin": "管理员",
"components.NotificationTypeSelector.usermediarequestedDescription": "当其他用户提交需要管理员批准的请求时得到通知。",
"components.NotificationTypeSelector.usermediafailedDescription": "当 Radarr 或 Sonarr 处理请求失败时得到通知。",
- "components.NotificationTypeSelector.usermediadeclinedDescription": "当您的请求被拒绝时得到通知。",
- "components.NotificationTypeSelector.usermediaavailableDescription": "当您请求的媒体可观看时得到通知。",
- "components.NotificationTypeSelector.usermediaapprovedDescription": "当您的请求被手动批准时得到通知。",
+ "components.NotificationTypeSelector.usermediadeclinedDescription": "当你的请求被拒绝时得到通知。",
+ "components.NotificationTypeSelector.usermediaavailableDescription": "当你请求的媒体可观看时得到通知。",
+ "components.NotificationTypeSelector.usermediaapprovedDescription": "当你的请求被手动批准时得到通知。",
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "当其他用户提交自动批准的请求时得到通知。",
"components.NotificationTypeSelector.notificationTypes": "通知类型",
"components.NotificationTypeSelector.mediarequestedDescription": "当用户提交需要管理员批准的请求时发送通知。",
@@ -260,10 +260,10 @@
"components.MovieDetails.MovieCrew.fullcrew": "制作群",
"components.MovieDetails.MovieCast.fullcast": "演员阵容",
"components.MediaSlider.ShowMoreCard.seemore": "更多",
- "components.Login.validationpasswordrequired": "请输入您的密码",
+ "components.Login.validationpasswordrequired": "请输入你的密码",
"components.Login.validationemailrequired": "请输入有效的电子邮件地址",
- "components.Login.signinwithplex": "使用您的 Plex 账户",
- "components.Login.signinwithoverseerr": "使用您的 {applicationTitle} 账户",
+ "components.Login.signinwithplex": "使用你的 Plex 账户",
+ "components.Login.signinwithoverseerr": "使用你的 {applicationTitle} 账户",
"components.Login.signinheader": "请先登入",
"components.Login.signingin": "登入中…",
"components.Login.signin": "登入",
@@ -272,7 +272,7 @@
"components.Login.forgotpassword": "忘记密码?",
"components.Login.email": "电子邮件地址",
"components.Layout.VersionStatus.streamstable": "Overseerr 稳定版",
- "components.Layout.VersionStatus.streamdevelop": "Overseerr「develop」开发版",
+ "components.Layout.VersionStatus.streamdevelop": "Overseerr 开发版",
"components.Layout.VersionStatus.outofdate": "過时",
"components.Layout.VersionStatus.commitsbehind": "落后 {commitsBehind} 次提交",
"components.Layout.UserDropdown.signout": "登出",
@@ -380,12 +380,12 @@
"components.UserProfile.norequests": "没有请求。",
"components.UserProfile.movierequests": "电影请求",
"components.UserProfile.limit": "{limit} 之 {remaining}",
- "components.UserProfile.UserSettings.unauthorizedDescription": "您无权编辑此用户的设置。",
+ "components.UserProfile.UserSettings.unauthorizedDescription": "你无权编辑此用户的设置。",
"components.UserProfile.UserSettings.menuPermissions": "权限",
"components.UserProfile.UserSettings.menuNotifications": "通知",
"components.UserProfile.UserSettings.menuGeneralSettings": "一般",
"components.UserProfile.UserSettings.menuChangePass": "密码",
- "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "您不能编辑自己的权限。",
+ "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "你不能编辑自己的权限。",
"components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "权限设置保存成功!",
"components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "保存设置中出了点问题。",
"components.UserProfile.UserSettings.UserPermissions.permissions": "权限设置",
@@ -395,12 +395,12 @@
"components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPasswordSame": "密码必须匹配",
"components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "密码必须匹配",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "密码设置成功!",
- "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "重设密码中出了点问题。您确定输入的当前密码是正确的吗?",
+ "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "重设密码中出了点问题。你确定输入的当前密码是正确的吗?",
"components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "重设密码中出了点问题。",
"components.UserProfile.UserSettings.UserPasswordChange.password": "密码设置",
- "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "您无权设置此用户的密码。",
- "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的帐户目前没有设置密码。在下方配置密码,您能够作为「本地用户」登录。",
- "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此用户帐户目前没有设置密码。在下方配置密码,使该帐户能够作为「本地用户」登录。",
+ "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "你无权设置此用户的密码。",
+ "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "你的帐户目前没有设置密码。在下方配置密码,使你能够作为“本地用户”登录。",
+ "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此用户帐户目前没有设置密码。在下方配置密码,使该帐户能够作为“本地用户”登录。",
"components.UserProfile.UserSettings.UserPasswordChange.newpassword": "新密码",
"components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "当前的密码",
"components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "确认密码",
@@ -425,7 +425,7 @@
"components.UserProfile.UserSettings.UserNotificationSettings.email": "电子邮件",
"components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord 通知设置保存成功!",
"components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Discord 通知设置保存失败。",
- "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "您的用户身分证號碼",
+ "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "你的用户身分证號碼",
"components.UserProfile.UserSettings.UserNotificationSettings.discordId": "用户 ID",
"components.UserProfile.UserSettings.UserGeneralSettings.user": "用户",
"components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "设置保存成功!",
@@ -433,11 +433,11 @@
"components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "电视节目请求限制",
"components.UserProfile.UserSettings.UserGeneralSettings.role": "角色",
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "以地区可用性筛选結果",
- "components.UserProfile.UserSettings.UserGeneralSettings.region": "「探索」地区",
+ "components.UserProfile.UserSettings.UserGeneralSettings.region": "探索地区",
"components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex 用户",
"components.UserProfile.UserSettings.UserGeneralSettings.owner": "所有者",
"components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "以原始语言筛选結果",
- "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "「探索」语言",
+ "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "探索语言",
"components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "电影请求限制",
"components.UserProfile.UserSettings.UserGeneralSettings.localuser": "本地用户",
"components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "默认设置({language})",
@@ -560,7 +560,7 @@
"components.Settings.RadarrModal.validationPortRequired": "请输入有效的端口",
"components.Settings.RadarrModal.validationNameRequired": "请输入服务器名称",
"components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "必须设置最低状态",
- "components.Settings.RadarrModal.validationHostnameRequired": "您必须提供有效的主机名或 IP 地址",
+ "components.Settings.RadarrModal.validationHostnameRequired": "你必须提供有效的主机名或 IP 地址",
"components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "必须刪除結尾斜線",
"components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "必须添加前置斜線",
"components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "必须刪除結尾斜線",
@@ -600,7 +600,7 @@
"components.Settings.RadarrModal.baseUrl": "网站根目录",
"components.Settings.RadarrModal.apiKey": "应用程序密钥",
"components.Settings.RadarrModal.add": "添加服务器",
- "components.Settings.Notifications.webhookUrlTip": "在您的服务器里创建一个网络钩子",
+ "components.Settings.Notifications.webhookUrlTip": "在你的服务器里创建一个网络钩子",
"components.Settings.Notifications.webhookUrl": "网络钩子网址(URL)",
"components.Settings.Notifications.validationUrl": "请输入有效的网址",
"components.Settings.Notifications.validationTypes": "请选择通知类型",
@@ -641,7 +641,7 @@
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "网络推送测试通知发送失败。",
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Overseerr 必须通過 HTTPS 投放才能使用网络推送通知。",
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "启用通知",
- "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "创建一个「incoming webhook」整合",
+ "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "创建一个 incoming webhook 集成",
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "网络钩子网址(URL)",
"components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "请输入有效的网址",
"components.Settings.Notifications.NotificationsSlack.validationTypes": "请选择通知类型",
@@ -654,7 +654,7 @@
"components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "请输入有效的用户或群组令牌",
"components.Settings.Notifications.NotificationsPushover.validationTypes": "请选择通知类型",
"components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "请输入应用程序 API 令牌",
- "components.Settings.Notifications.NotificationsPushover.userTokenTip": "您的 30 个字符的用户或群组標識符",
+ "components.Settings.Notifications.NotificationsPushover.userTokenTip": "你的 30 个字符的用户或群组標識符",
"components.Settings.Notifications.NotificationsPushover.userToken": "用户或群组令牌",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover 测试通知已发送!",
"components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "发送 Pushover 测试通知中…",
@@ -672,7 +672,7 @@
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet 通知设置保存成功!",
"components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet 通知设置保存失败。",
"components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "启用通知",
- "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "从您的帳號设定取得 API 令牌",
+ "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "从你的账户设定取得 API 令牌",
"components.Settings.Notifications.NotificationsPushbullet.accessToken": "API 令牌",
"components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "用户或设備通知的网络钩子网址",
"components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "网络钩子网址(URL)",
@@ -715,7 +715,7 @@
"components.RequestModal.requestCancel": "{title} 的请求已被取消。",
"components.RequestModal.request4ktitle": "为 {title} 提交 4K 请求",
"components.RequestModal.pendingrequest": "{title} 的请求",
- "components.RequestModal.pendingapproval": "您的请求正在等待管理员批准。",
+ "components.RequestModal.pendingapproval": "你的请求正在等待管理员批准。",
"components.RequestModal.pending4krequest": "{title} 的 4K 请求",
"components.RequestModal.numberofepisodes": "集数",
"components.RequestModal.extras": "特輯",
@@ -724,20 +724,20 @@
"components.RequestModal.cancel": "取消请求",
"components.RequestModal.autoapproval": "自动批准",
"components.RequestModal.alreadyrequested": "已经有请求",
- "components.RequestModal.SearchByNameModal.notvdbiddescription": "无法自动配對您的请求。请从以下列表中选择正确的媒体项。",
+ "components.RequestModal.SearchByNameModal.notvdbiddescription": "无法自动配對你的请求。请从以下列表中选择正确的媒体项。",
"components.RequestModal.SearchByNameModal.nosummary": "没有简介。",
"components.RequestModal.QuotaDisplay.seasonlimit": "个季数",
"components.RequestModal.QuotaDisplay.season": "电视节目季数",
"components.RequestModal.QuotaDisplay.requiredquotaUser": "此用户的电视节目请求数量必须至少剩余 {seasons} 个季数才能为此节目提交请求。",
- "components.RequestModal.QuotaDisplay.requiredquota": "您的电视节目请求数量必须至少剩余 {seasons} 个季数才能为此节目提交请求。",
+ "components.RequestModal.QuotaDisplay.requiredquota": "你的电视节目请求数量必须至少剩余 {seasons} 个季数才能为此节目提交请求。",
"components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {电影请求剩余数不足} other {剩余 # 个{type}请求}}",
"components.RequestModal.QuotaDisplay.quotaLinkUser": "访问此用户的个人资料页面以查看用户的请求限制 。",
- "components.RequestModal.QuotaDisplay.quotaLink": "访问您的个人资料页面以查看您的请求限制 。",
+ "components.RequestModal.QuotaDisplay.quotaLink": "访问你的个人资料页面以查看你的请求限制 。",
"components.RequestModal.QuotaDisplay.notenoughseasonrequests": "请求剩余数不足",
"components.RequestModal.QuotaDisplay.movielimit": "部电影",
"components.RequestModal.QuotaDisplay.movie": "电影",
"components.RequestModal.QuotaDisplay.allowedRequestsUser": "此用户每 {days} 天能为 {limit} {type}提交请求。",
- "components.RequestModal.QuotaDisplay.allowedRequests": "您每 {days} 天能为 {limit} {type}提交请求。",
+ "components.RequestModal.QuotaDisplay.allowedRequests": "你每 {days} 天能为 {limit} {type}提交请求。",
"components.RequestModal.AdvancedRequester.tags": "标签",
"components.RequestModal.AdvancedRequester.selecttags": "设定标签",
"components.RequestModal.AdvancedRequester.rootfolder": "根目录",
@@ -825,7 +825,7 @@
"components.Settings.Notifications.emailsender": "发件人电子邮件地址",
"components.Settings.Notifications.discordsettingssaved": "Discord 通知设置保存成功!",
"components.Settings.Notifications.discordsettingsfailed": "Discord 通知设置保存失败。",
- "components.Settings.Notifications.chatIdTip": "先与您的机器人建立一个聊天室以及把 @get_id_bot 也加到聊天室,然后在聊天室里发出 /my_id 命令",
+ "components.Settings.Notifications.chatIdTip": "先与你的机器人建立一个聊天室以及把 @get_id_bot 也加到聊天室,然后在聊天室里发出 /my_id 命令",
"components.Settings.Notifications.chatId": "聊天室 ID",
"components.Settings.Notifications.botUsernameTip": "允许用户也把机器人加到自己的聊天室以及设定自己的通知",
"components.Settings.Notifications.botUsername": "Bot 机器人名",
@@ -855,12 +855,12 @@
"components.Settings.Notifications.encryptionDefault": "盡可能使用 STARTTLS",
"components.Settings.Notifications.encryption": "加密方式",
"components.StatusBadge.status": "{status}",
- "components.IssueDetails.IssueComment.areyousuredelete": "您确定删除此条评论吗?",
+ "components.IssueDetails.IssueComment.areyousuredelete": "你确定删除此条评论吗?",
"components.IssueDetails.IssueComment.delete": "删除评论",
"components.IssueDetails.IssueComment.edit": "编辑评论",
"components.IssueDetails.IssueComment.postedby": "由 {username} 发布于 {relativeTime}",
"components.IssueDetails.IssueComment.postedbyedited": "由 {username} 发布于 {relativeTime}(已编辑)",
- "components.IssueDetails.IssueComment.validationComment": "您必须输入一条消息",
+ "components.IssueDetails.IssueComment.validationComment": "你必须输入一条消息",
"components.IssueDetails.IssueDescription.deleteissue": "删除 Issue",
"components.IssueDetails.allseasons": "所有季数",
"components.IssueDetails.nocomments": "没有评论。",
@@ -872,7 +872,7 @@
"components.IssueDetails.closeissue": "关闭 Issue",
"components.IssueDetails.closeissueandcomment": "评论后关闭",
"components.IssueDetails.comments": "评论",
- "components.IssueDetails.deleteissueconfirm": "您是否确实要删除此 issue?",
+ "components.IssueDetails.deleteissueconfirm": "你是否确实要删除此 issue?",
"components.IssueDetails.episode": "第 {episodeNumber} 集",
"components.IssueDetails.issuepagetitle": "问题",
"components.IssueDetails.lastupdated": "最后更新时间",
@@ -894,5 +894,12 @@
"components.IssueDetails.reopenissueandcomment": "评论后重新打开",
"components.IssueDetails.season": "第 {seasonNumber} 季",
"components.IssueDetails.toastissuedeleted": "Issue 删除成功!",
- "components.IssueModal.CreateIssueModal.episode": "第 {episodeNumber} 集"
+ "components.IssueModal.CreateIssueModal.episode": "第 {episodeNumber} 集",
+ "components.IssueDetails.commentplaceholder": "添加评论…",
+ "components.IssueList.IssueItem.issuestatus": "状态",
+ "components.IssueList.IssueItem.issuetype": "类型",
+ "components.IssueList.IssueItem.openeduserdate": "{date} by {user}",
+ "components.IssueList.IssueItem.problemepisode": "受影响的剧集",
+ "components.IssueList.IssueItem.episodes": "集数",
+ "components.IssueList.IssueItem.opened": "打开"
}
diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json
index ddf084f0..f7054265 100644
--- a/src/i18n/locale/zh_Hant.json
+++ b/src/i18n/locale/zh_Hant.json
@@ -738,7 +738,7 @@
"components.Layout.VersionStatus.commitsbehind": "落後 {commitsBehind} 個提交",
"components.Layout.VersionStatus.outofdate": "非最新版本",
"components.Layout.VersionStatus.streamstable": "Overseerr 穩定版",
- "components.Layout.VersionStatus.streamdevelop": "Overseerr「develop」開發版",
+ "components.Layout.VersionStatus.streamdevelop": "Overseerr 開發版",
"components.Settings.SettingsAbout.outofdate": "非最新版本",
"components.Settings.SettingsAbout.uptodate": "最新",
"components.Settings.noDefaultNon4kServer": "如果您只有一個 {serverType} 伺服器,請勿把它設定為 4K 伺服器。",
@@ -995,5 +995,8 @@
"components.TvDetails.productioncountries": "製作國家",
"components.IssueDetails.commentplaceholder": "發表評論…",
"components.RequestModal.requestApproved": "{title} 的請求已被批准。",
- "components.RequestModal.approve": "批准請求"
+ "components.RequestModal.approve": "批准請求",
+ "components.Settings.RadarrModal.inCinemas": "已上映",
+ "components.Settings.RadarrModal.released": "已發佈",
+ "components.Settings.RadarrModal.announced": "已公佈"
}
From d2241a41877d126a802fc53c925d258af31f34fd Mon Sep 17 00:00:00 2001
From: "Weblate (bot)"
Date: Tue, 4 Jan 2022 20:42:18 +0100
Subject: [PATCH 03/76] feat(lang): translations update from Hosted Weblate
(#2389)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat(lang): translated using Weblate (Serbian)
Currently translated at 44.5% (445 of 1000 strings)
feat(lang): translated using Weblate (Serbian)
Currently translated at 31.8% (318 of 1000 strings)
Co-authored-by: Dalibor Radovanović
Co-authored-by: Hosted Weblate
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sr/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Russian)
Currently translated at 100.0% (1000 of 1000 strings)
feat(lang): translated using Weblate (Russian)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Sergey Moiseev
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (German)
Currently translated at 100.0% (1000 of 1000 strings)
feat(lang): translated using Weblate (German)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Ben Wallner
Co-authored-by: Hosted Weblate
Co-authored-by: TheCatLady
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/
Translation: Overseerr/Overseerr Frontend
Co-authored-by: Dalibor Radovanović
Co-authored-by: Sergey Moiseev
Co-authored-by: Ben Wallner
Co-authored-by: TheCatLady
---
src/i18n/locale/de.json | 71 +++---
src/i18n/locale/ru.json | 28 ++-
src/i18n/locale/sr.json | 487 +++++++++++++++++++++++++++++-----------
3 files changed, 423 insertions(+), 163 deletions(-)
diff --git a/src/i18n/locale/de.json b/src/i18n/locale/de.json
index 2aad43c7..be2621ca 100644
--- a/src/i18n/locale/de.json
+++ b/src/i18n/locale/de.json
@@ -20,7 +20,7 @@
"components.MovieDetails.overview": "Übersicht",
"components.MovieDetails.overviewunavailable": "Übersicht nicht verfügbar.",
"components.MovieDetails.recommendations": "Empfehlungen",
- "components.MovieDetails.releasedate": "{releaseCount, plural, one {Release Date} other {Release Dates}}",
+ "components.MovieDetails.releasedate": "{releaseCount, plural, one {Veröffentlichungstermin} other {Veröffentlichungstermine}}",
"components.MovieDetails.revenue": "Einnahmen",
"components.MovieDetails.runtime": "{minutes} Minuten",
"components.MovieDetails.similar": "Ähnliche Titel",
@@ -73,7 +73,7 @@
"components.Settings.RadarrModal.toastRadarrTestFailure": "Verbindung zu Radarr fehlgeschlagen.",
"components.Settings.RadarrModal.toastRadarrTestSuccess": "Radarr-Verbindung erfolgreich hergestellt!",
"components.Settings.RadarrModal.validationApiKeyRequired": "Du musst einen API-Schlüssel angeben",
- "components.Settings.RadarrModal.validationHostnameRequired": "Du musst einen Hostnamen oder eine IP-Adresse angeben",
+ "components.Settings.RadarrModal.validationHostnameRequired": "Es muss ein gültiger Hostname oder eine IP-Adresse angegeben werden",
"components.Settings.RadarrModal.validationPortRequired": "Du musst einen Port angeben",
"components.Settings.RadarrModal.validationProfileRequired": "Du musst ein Qualitätsprofil auswählen",
"components.Settings.RadarrModal.validationRootFolderRequired": "Du musst einen Stammordner auswählen",
@@ -118,7 +118,7 @@
"components.Settings.manualscanDescription": "Normalerweise wird dies nur einmal alle 24 Stunden ausgeführt. Overseerr überprüft die kürzlich hinzugefügten Plex-Server aggressiver. Falls du Plex zum ersten Mal konfigurierst, wird eine einmalige vollständige manuelle Bibliotheksdurchsuchung empfohlen!",
"components.Settings.menuAbout": "Über",
"components.Settings.menuGeneralSettings": "Allgemein",
- "components.Settings.menuJobs": "Aufgaben und Zwischenspeicher",
+ "components.Settings.menuJobs": "Aufgaben und Cache",
"components.Settings.menuLogs": "Protokolle",
"components.Settings.menuNotifications": "Benachrichtigungen",
"components.Settings.menuPlexSettings": "Plex",
@@ -333,9 +333,9 @@
"components.RequestBlock.rootfolder": "Stammordner",
"components.RequestBlock.profilechanged": "Qualitätsprofil",
"components.NotificationTypeSelector.mediadeclined": "Medien abgelehnt",
- "components.NotificationTypeSelector.mediadeclinedDescription": "Sende Benachrichtigungen, wenn Medienanfragen abgelehnt wurden.",
+ "components.NotificationTypeSelector.mediadeclinedDescription": "Sende eine Benachrichtigungen, wenn Medienanfragen abgelehnt wurden.",
"components.RequestModal.autoapproval": "Automatische Genehmigung",
- "i18n.experimental": "Experimental",
+ "i18n.experimental": "Experimentell",
"components.Settings.hideAvailable": "Verfügbare Medien ausblenden",
"components.RequestModal.requesterror": "Beim Senden der Anfragen ist etwas schief gelaufen.",
"components.RequestModal.SearchByNameModal.notvdbiddescription": "Wir konnten deine Anfrage nicht automatisch zuordnen. Bitte wähle eine korrekte Übereinstimmung aus der Liste aus.",
@@ -572,7 +572,7 @@
"components.RequestList.RequestItem.modifieduserdate": "{date} von {user}",
"components.RequestList.RequestItem.modified": "Geändert",
"components.Discover.StudioSlider.studios": "Filmstudio",
- "components.Discover.NetworkSlider.networks": "Streaming-Anbieter",
+ "components.Discover.NetworkSlider.networks": "Sender",
"components.Discover.DiscoverTvLanguage.languageSeries": "Serien auf {language}",
"components.Discover.DiscoverMovieLanguage.languageMovies": "Filme auf {language}",
"components.Settings.SettingsUsers.localLogin": "Lokale Anmeldung aktivieren",
@@ -638,7 +638,7 @@
"components.Settings.SettingsLogs.extraData": "Zusätzliche Daten",
"components.Settings.SettingsLogs.copyToClipboard": "In Zwischenablage kopieren",
"components.Settings.SettingsLogs.copiedLogMessage": "Protokollnachricht in die Zwischenablage kopiert.",
- "components.Settings.SettingsJobsCache.jobsandcache": "Aufgaben und Zwischenspeicher",
+ "components.Settings.SettingsJobsCache.jobsandcache": "Aufgaben und Cache",
"components.Settings.SettingsAbout.about": "Über",
"components.ResetPassword.passwordreset": "Passwort zurücksetzen",
"components.PersonDetails.lifespan": "{birthdate} – {deathdate}",
@@ -793,7 +793,7 @@
"components.PermissionEdit.requestMovies": "Filme anfragen",
"components.NotificationTypeSelector.usermediarequestedDescription": "Werde benachrichtigt, wenn andere Nutzer ein Medium anfordern, welches eine Genehmigung erfordert.",
"components.NotificationTypeSelector.usermediadeclinedDescription": "Werde benachrichtigt, wenn deine Medienanfrage abgelehnt wurde.",
- "components.NotificationTypeSelector.usermediaavailableDescription": "Werde benachrichtigt, wenn deine Medienanfrage verfügbar ist.",
+ "components.NotificationTypeSelector.usermediaavailableDescription": "Sende eine Benachrichtigung, wenn Ihre Medienanfragen verfügbar sind.",
"components.NotificationTypeSelector.usermediaapprovedDescription": "Werde benachrichtigt, wenn Ihre Medienanfrage angenommen wurde.",
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Werde benachrichtigt, wenn andere Nutzer Medien anfordern, welche automatisch angenommen werden.",
"components.DownloadBlock.estimatedtime": "Geschätzte {time}",
@@ -864,12 +864,12 @@
"components.IssueModal.CreateIssueModal.problemseason": "Betroffene Staffel",
"components.IssueModal.CreateIssueModal.providedetail": "Geben Sie eine detaillierte Erklärung des Problems an.",
"components.IssueModal.CreateIssueModal.reportissue": "Ein Problem melden",
- "components.IssueDetails.IssueComment.areyousuredelete": "Möchten Sie diesen Kommentar wirklich löschen?",
+ "components.IssueDetails.IssueComment.areyousuredelete": "Soll dieser Kommentar wirklich gelöscht werden?",
"components.IssueDetails.IssueComment.delete": "Kommentar löschen",
"components.IssueDetails.IssueComment.edit": "Kommentar bearbeiten",
"components.IssueDetails.IssueComment.postedby": "Gepostet {relativeTime} von {username}",
"components.IssueDetails.IssueComment.postedbyedited": "Gepostet {relativeTime} von {username} (Bearbeitet)",
- "components.IssueDetails.IssueComment.validationComment": "Sie müssen eine Nachricht eingeben",
+ "components.IssueDetails.IssueComment.validationComment": "Eine Nachricht muss eingegeben werden",
"components.IssueDetails.IssueDescription.deleteissue": "Problem löschen",
"components.IssueDetails.toasteditdescriptionsuccess": "Problembeschreibung erfolgreich bearbeitet!",
"components.IssueDetails.toastissuedeleted": "Problem erfolgreich gelöscht!",
@@ -882,10 +882,10 @@
"components.IssueDetails.closeissueandcomment": "Schließen mit Kommentar",
"components.IssueDetails.comments": "Kommentare",
"components.IssueDetails.deleteissue": "Problem löschen",
- "components.IssueDetails.deleteissueconfirm": "Möchten Sie dieses Problem wirklich löschen?",
+ "components.IssueDetails.deleteissueconfirm": "Soll dieses Problem wirklich gelöscht werden?",
"components.IssueDetails.episode": "Folge {episodeNumber}",
"components.IssueDetails.issuepagetitle": "Problem",
- "components.IssueDetails.issuetype": "Typ",
+ "components.IssueDetails.issuetype": "Art",
"components.IssueDetails.lastupdated": "Letzte Aktualisierung",
"components.IssueDetails.leavecomment": "Kommentar",
"components.IssueDetails.openinarr": "In {arr} öffnen",
@@ -917,15 +917,15 @@
"components.IssueModal.CreateIssueModal.toastFailedCreate": "Beim Senden des Problems ist ein Fehler aufgetreten.",
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Häufigkeit",
"components.Settings.SettingsJobsCache.editJobSchedule": "Job ändern",
- "components.NotificationTypeSelector.userissuecommentDescription": "Lassen Sie sich benachrichtigen, wenn Ihre Probleme neue Kommentare erhalten.",
+ "components.NotificationTypeSelector.userissuecommentDescription": "Sende eine Benachrichtigung, wenn andere Benutzer Kommentare zu Problemen abgeben.",
"components.NotificationTypeSelector.issuecomment": "Problem Kommentar",
"i18n.open": "Offen",
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Die Einstellungen für Pushbullet-Benachrichtigungen konnten nicht gespeichert werden.",
"components.IssueModal.CreateIssueModal.submitissue": "Problem einreichen",
"components.IssueModal.issueAudio": "Ton",
"components.IssueModal.issueSubtitles": "Untertitel",
- "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
- "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Erstellen Sie ein Token aus Ihren Kontoeinstellungen.",
+ "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Staffel} other {Staffeln}}",
+ "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Erstellen Sie ein Token aus Ihren Kontoeinstellungen",
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Zugangs-Token",
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet-Benachrichtigungseinstellungen erfolgreich gespeichert!",
"components.IssueModal.CreateIssueModal.extras": "Extras",
@@ -937,7 +937,7 @@
"components.ManageSlideOver.downloadstatus": "Download Status",
"components.ManageSlideOver.manageModalClearMedia": "Löschen der Mediendaten",
"components.ManageSlideOver.manageModalClearMediaWarning": "* Dadurch werden alle Daten für diesen {mediaType} unwiderruflich entfernt, einschließlich aller Anfragen. Wenn dieses Element in Ihrer Plex-Bibliothek existiert, werden die Medieninformationen beim nächsten Scan neu erstellt.",
- "components.ManageSlideOver.manageModalIssues": "Problem öffnen",
+ "components.ManageSlideOver.manageModalIssues": "Problem eröffnen",
"components.ManageSlideOver.manageModalNoRequests": "Keine Anfragen.",
"components.ManageSlideOver.manageModalRequests": "Anfragen",
"components.ManageSlideOver.manageModalTitle": "{mediaType} verwalten",
@@ -947,39 +947,56 @@
"components.ManageSlideOver.openarr": "Öffnen in {arr}",
"components.ManageSlideOver.openarr4k": "Öffnen in 4K {arr}",
"components.ManageSlideOver.tvshow": "Serie",
- "components.NotificationTypeSelector.issuecreatedDescription": "Senden Sie Benachrichtigungen, wenn Probleme gemeldet werden.",
+ "components.NotificationTypeSelector.issuecreatedDescription": "Senden eine Benachrichtigungen, wenn Probleme gemeldet werden.",
"components.NotificationTypeSelector.issueresolved": "Problem gelöst",
"components.NotificationTypeSelector.issueresolvedDescription": "Senden Sie Benachrichtigungen, wenn Probleme gelöst sind.",
"components.NotificationTypeSelector.userissuecreatedDescription": "Lassen Sie sich benachrichtigen, wenn andere Benutzer Probleme melden.",
- "components.NotificationTypeSelector.userissueresolvedDescription": "Lassen Sie sich benachrichtigen, wenn Ihre Probleme gelöst sind.",
+ "components.NotificationTypeSelector.userissueresolvedDescription": "Sende eine Benachrichtigung, wenn andere Benutzer Kommentare zu Problemen abgeben.",
"components.PermissionEdit.createissues": "Probleme melden",
"components.Settings.SettingsAbout.runningDevelop": "Sie benutzen den develop von Overseerr, der nur für diejenigen empfohlen wird, die an der Entwicklung mitwirken oder bei den neuesten Tests helfen.",
- "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Alle {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}",
- "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Alle {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}",
+ "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Alle {jobScheduleHours, plural, one {Stunde} other {{jobScheduleHours} Stunden}}",
+ "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Alle {jobScheduleMinutes, plural, one {Minute} other {{jobScheduleMinutes} Minuten}}",
"components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Beim Speichern des Auftrags ging etwas schief.",
"components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Auftrag erfolgreich bearbeitet!",
"components.IssueList.issues": "Probleme",
"components.IssueModal.CreateIssueModal.episode": "Folgen {episodeNumber}",
"components.IssueModal.CreateIssueModal.toastSuccessCreate": "Problembericht für {title} erfolgreich übermittelt!",
- "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episode} other {Episodes}}",
+ "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Folge} other {Folgen}}",
"components.IssueModal.CreateIssueModal.toastviewissue": "Problem ansehen",
"components.IssueModal.issueVideo": "Video",
- "components.NotificationTypeSelector.adminissuecommentDescription": "Lassen Sie sich benachrichtigen, wenn neue Kommentare zu einem Problem eingehen.",
- "components.NotificationTypeSelector.issuecommentDescription": "Senden Sie Benachrichtigungen, wenn Probleme neue Kommentare erhalten.",
+ "components.NotificationTypeSelector.adminissuecommentDescription": "Sende eine Benachrichtigung, wenn andere Benutzer Kommentare zu Problemen abgeben.",
+ "components.NotificationTypeSelector.issuecommentDescription": "Sende eine Benachrichtigungen, wenn Probleme neue Kommentare erhalten.",
"components.NotificationTypeSelector.issuecreated": "Gemeldetes Problem",
"components.PermissionEdit.manageissues": "Probleme verwalten",
"components.PermissionEdit.viewissues": "Probleme ansehen",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Anwendungs-API-Token",
- "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registrieren Sie eine Anwendung zur Verwendung mit {applicationTitle}",
+ "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Register eine Anwendung zur Verwendung mit {applicationTitle}",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Benutzer- oder Gruppenschlüssel",
- "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Ihre 30-stellige Benutzer- oder Gruppenkennung",
+ "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Die 30-stellige Benutzer- oder Gruppenkennung",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Die Einstellungen für die Pushover-Benachrichtigung konnten nicht gespeichert werden.",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover-Benachrichtigungseinstellungen erfolgreich gespeichert!",
- "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Sie müssen ein Zugriffs-Token angeben",
+ "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Ein Zugriffstoken muss angegeben werden",
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Sie müssen ein gültiges Anwendungs-Token angeben",
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Du musst einen gültigen Benutzer- oder Gruppenschlüssel angeben",
"i18n.resolved": "Gelöst",
"components.PermissionEdit.viewissuesDescription": "Berechtigt, von andereren Nutzern gemeldete Medienprobleme zu sehen.",
"components.PermissionEdit.createissuesDescription": "Berechtigt, Medienprobleme zu melden.",
- "components.PermissionEdit.manageissuesDescription": "Berechtigt, Medienprobleme zu verwalten."
+ "components.PermissionEdit.manageissuesDescription": "Berechtigt, Medienprobleme zu verwalten.",
+ "components.NotificationTypeSelector.issuereopened": "Problem wiedereröffnet",
+ "components.NotificationTypeSelector.userissuereopenedDescription": "Sende eine Benachrichtigung, wenn die von dir gemeldeten Probleme wieder geöffnet werden.",
+ "components.MovieDetails.productioncountries": "Produktions {countryCount, plural, one {Land} other {Länder}}",
+ "components.IssueDetails.commentplaceholder": "Kommentar hinzufügen…",
+ "components.NotificationTypeSelector.issuereopenedDescription": "Sende eine Benachrichtigung, wenn Probleme wieder geöffnet werden.",
+ "components.RequestModal.selectmovies": "Wähle Film(e)",
+ "components.NotificationTypeSelector.adminissueresolvedDescription": "Sende eine Benachrichtigung, wenn andere Benutzer Kommentare zu Themen abgeben.",
+ "components.RequestModal.approve": "Anfrage genehmigen",
+ "components.RequestModal.requestApproved": "Anfrage für {title} genehmigt!",
+ "components.RequestModal.requestmovies": "Anfrage {count} {count, plural, one {Film} other {Filme}}",
+ "components.RequestModal.requestmovies4k": "Anfrage {count} {count, plural, one {Film} other {Filme}} in 4K",
+ "components.RequestModal.requestseasons4k": "Anfrage {seasonCount} {seasonCount, plural, one {Serie} other {Serien}} in 4K",
+ "components.TvDetails.productioncountries": "Produktions {countryCount, plural, one {Land} other {Länder}}",
+ "components.Settings.RadarrModal.announced": "Angekündigt",
+ "components.Settings.RadarrModal.inCinemas": "Im Kino",
+ "components.Settings.RadarrModal.released": "Veröffentlicht",
+ "components.NotificationTypeSelector.adminissuereopenedDescription": "Sende eine Benachrichtigung, wenn Probleme von anderen Benutzern wieder geöffnet werden."
}
diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json
index d13e8a1c..94742c3e 100644
--- a/src/i18n/locale/ru.json
+++ b/src/i18n/locale/ru.json
@@ -165,7 +165,7 @@
"i18n.movies": "Фильмы",
"i18n.partiallyavailable": "Доступен частично",
"i18n.pending": "В ожидании",
- "i18n.processing": "Обработка",
+ "i18n.processing": "В обработке",
"i18n.tvshows": "Сериалы",
"i18n.unavailable": "Недоступен",
"pages.oops": "Упс",
@@ -615,8 +615,8 @@
"components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Контент фильтруется по доступности в выбранном регионе",
"components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Ограничение количества запросов на фильмы",
"components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Переопределить глобальные ограничения",
- "components.Settings.noDefaultServer": "По крайней мере один сервер {serverType} должен быть помечен как сервер по умолчанию для обработки запросов на {mediaType}.",
- "components.Settings.noDefault4kServer": "4K сервер {serverType} должен быть помечен как сервер по умолчанию, чтобы пользователи могли отправлять запросы на 4K {mediaType}.",
+ "components.Settings.noDefaultServer": "По крайней мере один сервер {serverType} должен быть помечен как сервер по умолчанию для обработки запросов на {mediaType}ы.",
+ "components.Settings.noDefault4kServer": "4K сервер {serverType} должен быть помечен как сервер по умолчанию, чтобы пользователи могли отправлять запросы на 4K {mediaType}ы.",
"components.Settings.SonarrModal.validationLanguageProfileRequired": "Вы должны выбрать языковой профиль",
"components.Settings.validationApplicationUrlTrailingSlash": "URL-адрес не должен заканчиваться косой чертой",
"components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Базовый URL-адрес должен иметь косую черту в начале",
@@ -876,7 +876,7 @@
"components.IssueDetails.allseasons": "Все сезоны",
"components.IssueDetails.allepisodes": "Все эпизоды",
"components.ManageSlideOver.manageModalClearMedia": "Очистить данные мультимедиа",
- "components.ManageSlideOver.manageModalClearMediaWarning": "* Это приведёт к необратимому удалению всех данных для этого {mediaType}, включая любые запросы. Если этот элемент существует в вашей библиотеке Plex, мультимедийная информация о нём будет воссоздана во время следующего сканирования.",
+ "components.ManageSlideOver.manageModalClearMediaWarning": "* Это приведёт к необратимому удалению всех данных для этого {mediaType}а, включая любые запросы. Если этот элемент существует в вашей библиотеке Plex, мультимедийная информация о нём будет воссоздана во время следующего сканирования.",
"components.IssueDetails.problemepisode": "Затронутый эпизод",
"components.ManageSlideOver.manageModalRequests": "Запросы",
"components.IssueDetails.closeissue": "Закрыть проблему",
@@ -893,13 +893,13 @@
"components.IssueModal.CreateIssueModal.episode": "Эпизод {episodeNumber}",
"components.ManageSlideOver.mark4kavailable": "Пометить как доступный в 4К",
"components.IssueModal.CreateIssueModal.extras": "Дополнительно",
- "components.IssueModal.CreateIssueModal.issomethingwrong": "Есть проблема с {title}?",
+ "components.IssueModal.CreateIssueModal.issomethingwrong": "Есть проблема с «{title}»?",
"components.IssueModal.CreateIssueModal.problemseason": "Затронутый сезон",
"components.ManageSlideOver.allseasonsmarkedavailable": "* Все сезоны будут помечены как доступные.",
"components.ManageSlideOver.downloadstatus": "Статус загрузки",
"components.ManageSlideOver.manageModalIssues": "Открытые проблемы",
"components.ManageSlideOver.manageModalNoRequests": "Запросов нет.",
- "components.ManageSlideOver.manageModalTitle": "Управлять {mediaType}",
+ "components.ManageSlideOver.manageModalTitle": "Управление {mediaType}ом",
"components.ManageSlideOver.markavailable": "Пометить как доступный",
"components.ManageSlideOver.movie": "фильм",
"components.ManageSlideOver.openarr": "Открыть в {arr}",
@@ -926,7 +926,7 @@
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Ключ пользователя или группы",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Не удалось сохранить настройки уведомлений Pushover.",
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Вы должны предоставить токен доступа",
- "i18n.resolved": "Решён",
+ "i18n.resolved": "Решённые",
"components.IssueDetails.openedby": "#{issueId} открыта {relativeTime} пользователем {username}",
"components.IssueDetails.openin4karr": "Открыть в 4К {arr}",
"components.IssueDetails.play4konplex": "Воспроизвести в Plex в 4К",
@@ -986,5 +986,17 @@
"components.NotificationTypeSelector.userissuereopenedDescription": "Получать уведомления, когда проблемы о которых вы сообщили будут открыты заново.",
"components.NotificationTypeSelector.adminissuereopenedDescription": "Получать уведомления, когда проблемы открыты заново другими пользователями.",
"components.NotificationTypeSelector.issuereopened": "Проблема открыта заново",
- "components.NotificationTypeSelector.adminissueresolvedDescription": "Получать уведомления, когда проблемы решены другими пользователями."
+ "components.NotificationTypeSelector.adminissueresolvedDescription": "Получать уведомления, когда проблемы решены другими пользователями.",
+ "components.RequestModal.requestseasons4k": "Запрос {seasonCount} {seasonCount, plural, one {сезона} other {сезонов}} в 4К",
+ "components.TvDetails.productioncountries": "{countryCount, plural, one {Страна} other {Страны}} производства",
+ "components.IssueDetails.commentplaceholder": "Добавить комментарий…",
+ "components.MovieDetails.productioncountries": "{countryCount, plural, one {Страна} other {Страны}} производства",
+ "components.RequestModal.selectmovies": "Выберите фильм(ы)",
+ "components.RequestModal.approve": "Одобрить запрос",
+ "components.RequestModal.requestmovies": "Запрос {count} {count, plural, one {фильма} other {фильмов}}",
+ "components.RequestModal.requestmovies4k": "Запрос {count} {count, plural, one {фильма} other {фильмов}} в 4К",
+ "components.Settings.RadarrModal.inCinemas": "В кино",
+ "components.Settings.RadarrModal.released": "Выпущен",
+ "components.RequestModal.requestApproved": "Запрос на {title} одобрен!",
+ "components.Settings.RadarrModal.announced": "Анонсирован"
}
diff --git a/src/i18n/locale/sr.json b/src/i18n/locale/sr.json
index e017d0ed..c4b4247a 100644
--- a/src/i18n/locale/sr.json
+++ b/src/i18n/locale/sr.json
@@ -5,213 +5,444 @@
"pages.oops": "Ups",
"i18n.unavailable": "Nije dostupno",
"i18n.tvshows": "Serije",
- "i18n.processing": "Obradjuje se…",
+ "i18n.processing": "Obradjuje se",
"i18n.pending": "Na čekanju",
- "i18n.partiallyavailable": "Polovično Dostupno",
+ "i18n.partiallyavailable": "Delimično dostupno",
"i18n.movies": "Filmovi",
"i18n.deleting": "Brisanje u toku…",
- "i18n.delete": "Izbriši",
+ "i18n.delete": "Obriši",
"i18n.declined": "Odbijeno",
- "i18n.decline": "Poništi",
+ "i18n.decline": "Odbij",
"i18n.cancel": "Poništi",
"i18n.available": "Dostupno",
- "i18n.approved": "Prihvaćeno",
- "i18n.approve": "Prihvati",
- "components.UserList.userlist": "Lista Korisnika",
- "components.UserList.userdeleteerror": "Neuspešno brisanje korisnika",
- "components.UserList.userdeleted": "Korisnik izbrisan.",
+ "i18n.approved": "Odobreno",
+ "i18n.approve": "Odobri",
+ "components.UserList.userlist": "Lista korisnika",
+ "components.UserList.userdeleteerror": "Nešto nije u redu prilikom brisanje korisnika.",
+ "components.UserList.userdeleted": "Korisnik izbrisan!",
"components.UserList.user": "Korisnik",
- "components.UserList.totalrequests": "Ukupno Zahteva",
+ "components.UserList.totalrequests": "Zahtevi",
"components.UserList.role": "Uloga",
- "components.UserList.plexuser": "Plex Korisnik",
- "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.created": "Napravljeno",
+ "components.UserList.plexuser": "Plex korisnik",
+ "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 trajno izbrisani.",
+ "components.UserList.created": "Pridružio",
"components.UserList.admin": "Administrator",
- "components.TvDetails.similar": "Slične Serije",
- "components.TvDetails.showtype": "Tip Serije",
+ "components.TvDetails.similar": "Slične serije",
+ "components.TvDetails.showtype": "Tip serije",
"components.TvDetails.recommendations": "Preporuke",
"components.TvDetails.overviewunavailable": "Pregled nije dostupan.",
"components.TvDetails.overview": "Pregled",
- "components.TvDetails.originallanguage": "Originalni Jezik",
- "components.TvDetails.network": "Mreža",
- "components.TvDetails.cast": "Repertoar",
+ "components.TvDetails.originallanguage": "Originalni jezik",
+ "components.TvDetails.network": "{networkCount, plural, one {Mreža} other {Mreže}}",
+ "components.TvDetails.cast": "Uloge",
"components.TvDetails.anime": "Anime",
- "components.TvDetails.TvCast.fullseriescast": "Ceo Repertoar Serije",
+ "components.TvDetails.TvCast.fullseriescast": "Glumci cele serije",
"components.Setup.welcome": "Dobrodošli u Overseerr",
"components.Setup.tip": "Savet",
- "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.signinMessage": "Započni tako što ćete se prijaviti sa svojim Plex nalogom",
+ "components.Setup.loginwithplex": "Prijavite se sa Plex-om",
+ "components.Setup.finishing": "Završavanje…",
+ "components.Setup.finish": "Završite 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.startscan": "Pokreni Skeniranje",
+ "components.Setup.configureservices": "Konfiguriši servise",
+ "components.Setup.configureplex": "Konfiguriši Plex",
+ "components.Settings.validationPortRequired": "Morate dodati važeći broj porta",
+ "components.Settings.validationHostnameRequired": "Morate dodati važeći hostname ili IP adresu",
+ "components.Settings.toastSettingsSuccess": "Podešavanja sačuvana!",
+ "components.Settings.toastSettingsFailure": "Nešto nije uredu pri čuvanju podešavanja.",
+ "components.Settings.toastApiKeySuccess": "Novi API Ključ generisan uspešno!",
+ "components.Settings.toastApiKeyFailure": "Nešto nije odradjeno kako treba pri generisanju novog API ključa.",
+ "components.Settings.startscan": "Pokreni skeniranje",
"components.Settings.ssl": "SSL",
- "components.Settings.sonarrsettings": "Sonarr Podešavanja",
- "components.Settings.radarrsettings": "Radarr Podešavanja",
+ "components.Settings.sonarrsettings": "Sonarr podešavanja",
+ "components.Settings.radarrsettings": "Radarr podešavanja",
"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.notificationsettings": "Podešavanje Notifikacija",
+ "components.Settings.plexsettingsDescription": "Konfigurišite podešavanja za Vaš Plex server. Overseerr skenira vaše Plex biblioteke da utvrdi šta je dostupno od sadržaja.",
+ "components.Settings.plexsettings": "Plex podešavanja",
+ "components.Settings.plexlibrariesDescription": "Overseerr skenira sadržaj za imena. Podesite informacije o Plex konekciji, a zatim kliknite dugme ispod ako nije navedena nijedna biblioteka.",
+ "components.Settings.plexlibraries": "Plex biblioteke",
+ "components.Settings.notificationsettings": "Podešavanje notifikacija",
"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.menuJobs": "Poslovi & Keš",
+ "components.Settings.menuGeneralSettings": "Opšta",
"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.manualscanDescription": "Normalno, ovo će biti pokrenutno na svakih 24 sata. 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.hostname": "Hostname/IP",
+ "components.Settings.hostname": "Hostname ili IP adresa",
"components.Settings.generalsettingsDescription": "Ovo su podešavanja vezana za uobičajenu Overseerr konfiguraciju.",
- "components.Settings.generalsettings": "Standarna Podešavanja",
+ "components.Settings.generalsettings": "Standarna podešavanja",
"components.Settings.deleteserverconfirm": "Da li ste sigurni da želite da izbrišete ovaj server?",
- "components.Settings.default4k": "Defaultno 4K",
- "components.Settings.default": "Defaultno",
- "components.Settings.currentlibrary": "Trenutna Biblioteka: {name}",
+ "components.Settings.default4k": "Podrazumevano 4K",
+ "components.Settings.default": "Podrazumeno",
+ "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.addsonarr": "Dodaj Sonarr server",
"components.Settings.address": "Adresa",
- "components.Settings.addradarr": "Dodaj Radarr Server",
- "components.Settings.activeProfile": "Aktivni Profil",
+ "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.validationPortRequired": "Morate popuniti važeći broj porta",
"components.Settings.SonarrModal.validationNameRequired": "Morate popuniti ime servera",
- "components.Settings.SonarrModal.validationHostnameRequired": "Morate dodati hostname ili IP adresu",
+ "components.Settings.SonarrModal.validationHostnameRequired": "Morate dodati važeći hostname ili IP adresu",
"components.Settings.SonarrModal.validationApiKeyRequired": "Morate dodati API ključ",
- "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.ssl": "SSL",
- "components.Settings.SonarrModal.servername": "Ime Servera",
+ "components.Settings.SonarrModal.testFirstRootFolders": "Testirajte konekciju da učitate root foldere",
+ "components.Settings.SonarrModal.testFirstQualityProfiles": "Testirajte konekciju da učitate profile kvaliteta",
+ "components.Settings.SonarrModal.ssl": "Koristi SSL",
+ "components.Settings.SonarrModal.servername": "Naziv 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.rootfolder": "Root Folder",
- "components.Settings.SonarrModal.qualityprofile": "Profil Kvaliteta",
+ "components.Settings.SonarrModal.selectRootFolder": "Odaberi root folder",
+ "components.Settings.SonarrModal.selectQualityProfile": "Odaberi profil kvaliteta",
+ "components.Settings.SonarrModal.seasonfolders": "Sezona folderi",
+ "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.hostname": "Hostname ili IP adresa",
+ "components.Settings.SonarrModal.editsonarr": "Izmeni Sonarr server",
+ "components.Settings.SonarrModal.defaultserver": "Podrazumevani server",
+ "components.Settings.SonarrModal.createsonarr": "Dodaj novi Sonarr server",
"components.Settings.SonarrModal.baseUrl": "Osnovni URL",
"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.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.totalrequests": "Ukupno zahteva",
+ "components.Settings.SettingsAbout.totalmedia": "Ukupno medija",
+ "components.Settings.SettingsAbout.overseerrinformation": "O Overseerr",
"components.Settings.SettingsAbout.githubdiscussions": "GitHub rasprave",
"components.Settings.SettingsAbout.gettingsupport": "Pomoć",
"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.validationProfileRequired": "Morate odabrati profil kvaliteta",
+ "components.Settings.RadarrModal.validationPortRequired": "Morate da navedete važeći broj porta",
+ "components.Settings.RadarrModal.validationNameRequired": "Morate popuniti ime servera",
+ "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Morate odabrati minimalnu 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.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.ssl": "SSL",
- "components.Settings.RadarrModal.servername": "Ime Servera",
+ "components.Settings.RadarrModal.toastRadarrTestSuccess": "Konekcija sa Radarr serverom uspešno uspostavljena!",
+ "components.Settings.RadarrModal.toastRadarrTestFailure": "Neuspešna konekcija ka Radarr.",
+ "components.Settings.RadarrModal.testFirstRootFolders": "Testirajte konekciju da učitate root foldere",
+ "components.Settings.RadarrModal.testFirstQualityProfiles": "Testirajte konekciju da učitate profile kvaliteta",
+ "components.Settings.notrunning": "Ne radi",
+ "components.Settings.RadarrModal.ssl": "Koristi SSL",
+ "components.Settings.RadarrModal.servername": "Naziv 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.rootfolder": "Root Fascikla",
- "components.Settings.RadarrModal.qualityprofile": "Profil Kvaliteta",
+ "components.Settings.RadarrModal.selectRootFolder": "Odaberi root folder",
+ "components.Settings.RadarrModal.selectQualityProfile": "Odaberi profil kvaliteta",
+ "components.Settings.RadarrModal.selectMinimumAvailability": "Odaberi minimalnu dostupnost",
+ "components.Settings.RadarrModal.rootfolder": "Root folder",
+ "components.Settings.RadarrModal.qualityprofile": "Profil kvaliteta",
"components.Settings.RadarrModal.port": "Port",
- "components.Settings.RadarrModal.minimumAvailability": "Minimum Dostupnost",
+ "components.Settings.RadarrModal.minimumAvailability": "Minimalna 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.hostname": "Hostname ili IP adresa",
+ "components.Settings.RadarrModal.editradarr": "Izmeni Radarr server",
+ "components.Settings.RadarrModal.defaultserver": "Podrazumevani server",
+ "components.Settings.RadarrModal.createradarr": "Dodaj novi Radarr server",
"components.Settings.RadarrModal.baseUrl": "Osnovni URL",
"components.Settings.RadarrModal.apiKey": "API Ključ",
"components.Settings.RadarrModal.add": "Dodaj Server",
"components.Settings.Notifications.webhookUrl": "Webhook adresa",
- "components.Settings.Notifications.validationSmtpPortRequired": "Morate popuniti SMTP port polje",
- "components.Settings.Notifications.validationSmtpHostRequired": "Morate popuniti SMTP host polje",
+ "components.Settings.Notifications.validationSmtpPortRequired": "Morate da navedete važeći broj porta",
+ "components.Settings.Notifications.validationSmtpHostRequired": "Morate da navedete važeće ime hosta ili IP adresu",
"components.Settings.Notifications.smtpPort": "SMTP Port",
"components.Settings.Notifications.smtpHost": "SMTP Host",
"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.emailsender": "Adresa 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.Settings.Notifications.authUser": "SMTP Korisničko ime",
+ "components.Settings.Notifications.authPass": "SMTP lozinka",
+ "components.Settings.Notifications.agentenabled": "Omogući agenta",
"components.Search.searchresults": "Rezultati pretrage",
- "components.RequestModal.selectseason": "Odaberi sezone",
+ "components.RequestModal.selectseason": "Odaberi sezonu(e)",
"components.RequestModal.seasonnumber": "Sezona {number}",
"components.RequestModal.season": "Sezona",
"components.RequestModal.requesttitle": "Zatraži {title}",
- "components.RequestModal.requestfrom": "Trenutno postoji zahtev na čekanju od {username}",
+ "components.RequestModal.requestfrom": "Trenutno postoji zahtev na čekanju od {username}.",
"components.RequestModal.requestadmin": "Ovaj zahtev će odmah biti prihvaćen.",
- "components.RequestModal.requestSuccess": "poslat zahtev za {title} .",
+ "components.RequestModal.requestSuccess": "Poslat zahtev za {title} !",
"components.RequestModal.requestCancel": "Zahtev za {title} otkazan.",
- "components.RequestModal.pendingrequest": "Zahtev za {title} na čekanju",
- "components.RequestModal.numberofepisodes": "Broj Epizoda",
- "components.RequestModal.extras": "Dodatno",
- "components.RequestModal.cancel": "Otkaži Zahtev",
+ "components.RequestModal.pendingrequest": "Zahtev na čekanju za {title}",
+ "components.RequestModal.numberofepisodes": "# broj epizoda",
+ "components.RequestModal.extras": "Dodaci",
+ "components.RequestModal.cancel": "Otkaži zahtev",
"components.RequestList.requests": "Zahtevi",
- "components.RequestList.RequestItem.seasons": "Sezone",
- "components.RequestCard.seasons": "Sezone",
- "components.RequestBlock.seasons": "Sezone",
+ "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Sezona} other {Sezona}}",
+ "components.RequestCard.seasons": "{seasonCount, plural, one {Sezona} other {Sezone}}",
+ "components.RequestBlock.seasons": "{seasonCount, plural, one {Sezona} other {Sezone}}",
"components.PersonDetails.ascharacter": "kao {character}",
- "components.PersonDetails.appearsin": "Pojavljuje se u",
- "components.MovieDetails.studio": "Studio",
- "components.MovieDetails.similar": "Slični Naslovi",
+ "components.PersonDetails.appearsin": "Pojavljivanja",
+ "components.MovieDetails.studio": "{studioCount, plural, one {Izdavačka kuća} other {Izdavačke kuće}}",
+ "components.MovieDetails.similar": "Slični naslovi",
"components.MovieDetails.runtime": "{minutes} minuta",
"components.MovieDetails.revenue": "Prihod",
- "components.MovieDetails.releasedate": "Datum Izlaska",
- "components.MovieDetails.recommendations": "Predlozi",
+ "components.MovieDetails.releasedate": "{releaseCount, plural, one {Datum izlaska} other {Datumi izlaska}}",
+ "components.MovieDetails.recommendations": "Preporuke",
"components.MovieDetails.overviewunavailable": "Pregled nije dostupan.",
"components.MovieDetails.overview": "Pregled",
- "components.MovieDetails.originallanguage": "Originalni Jezik",
- "components.MovieDetails.cast": "Postava",
+ "components.MovieDetails.originallanguage": "Originalni jezik",
+ "components.MovieDetails.cast": "Uloge",
"components.MovieDetails.budget": "Budžet",
- "components.MovieDetails.MovieCast.fullcast": "Kompletna Glumačka Postava",
+ "components.MovieDetails.MovieCast.fullcast": "Kompletna glumačka postava",
"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.Discover.upcomingmovies": "Predstojeći Filmovi",
- "components.Discover.upcoming": "Predstojeći Filmovi",
+ "components.Layout.SearchInput.searchPlaceholder": "Pretraži filmove i serije",
+ "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.discovertv": "Popularne Serije",
- "components.Discover.discovermovies": "Popunarni Filmov",
+ "components.Discover.recentrequests": "Nedavni zahtevi",
+ "components.Discover.recentlyAdded": "Nedavno dodato",
+ "components.Discover.populartv": "Popularne serije",
+ "components.Discover.popularmovies": "Popularni filmovi",
+ "components.Discover.discovertv": "Popularne serije",
+ "components.Discover.discovermovies": "Popunarni Filmovi",
"pages.errormessagewithcode": "{statusCode} - {error}",
"components.StatusBadge.status": "{status}",
- "components.UserProfile.movierequests": "Zahtev za film"
+ "components.UserProfile.movierequests": "Zahtev za film",
+ "i18n.advanced": "Napredno",
+ "i18n.back": "Nazad",
+ "components.Discover.discover": "Pronađi novo",
+ "components.MovieDetails.showless": "Prikaži manje",
+ "components.Settings.SettingsLogs.logs": "Logovi",
+ "components.StatusChacker.reloadOverseerr": "Osveži",
+ "components.MovieDetails.watchtrailer": "Pogledaj najavu",
+ "components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Notifikacije",
+ "components.UserList.create": "Kreiraj",
+ "components.Settings.SettingsJobsCache.process": "Proces",
+ "components.Settings.menuUsers": "Korisnici",
+ "components.Settings.plex": "Plex",
+ "components.UserProfile.UserSettings.menuGeneralSettings": "Opšte",
+ "i18n.requested": "Zahtevano",
+ "components.Discover.NetworkSlider.networks": "Mreže",
+ "components.Discover.StudioSlider.studios": "Studiji",
+ "i18n.failed": "Neuspešno",
+ "components.CollectionDetails.overview": "Pregled",
+ "components.MovieDetails.playonplex": "Pusti na Plexu",
+ "i18n.loading": "Učitavanje…",
+ "components.UserList.creating": "Kreiranje…",
+ "components.UserList.accounttype": "Tip",
+ "i18n.tvshow": "Serije",
+ "components.Layout.UserDropdown.myprofile": "Profil",
+ "components.Layout.UserDropdown.settings": "Podešavanja",
+ "components.Login.password": "Lozinka",
+ "components.MovieDetails.showmore": "Prikaži više",
+ "components.MovieDetails.viewfullcrew": "Prikažu celu postavu",
+ "components.PersonDetails.crewmember": "Uloge",
+ "components.PermissionEdit.request": "Zahtev",
+ "components.RequestModal.AdvancedRequester.tags": "Oznake",
+ "components.Settings.RadarrModal.tags": "Oznake",
+ "components.Settings.SettingsJobsCache.jobtype": "Tip",
+ "components.Settings.SettingsLogs.pauseLogs": "Pauza",
+ "components.Settings.email": "Email",
+ "components.Settings.serverLocal": "lokalni",
+ "components.Settings.serverRemote": "udaljeni",
+ "components.Settings.serverpreset": "Server",
+ "components.Settings.services": "Servisi",
+ "components.UserList.users": "Korisnici",
+ "i18n.all": "Sve",
+ "components.QuotaSelector.unlimited": "Neograničeno",
+ "components.RequestCard.deleterequest": "Obriši zahtev",
+ "components.RequestList.RequestItem.modified": "Modifikovano",
+ "components.RequestModal.QuotaDisplay.movie": "film",
+ "components.Settings.SettingsAbout.preferredmethod": "Poželjno",
+ "components.Settings.SettingsJobsCache.cache": "Keš",
+ "components.Settings.SettingsLogs.filterInfo": "Info",
+ "components.Settings.SonarrModal.tags": "Oznake",
+ "components.Settings.general": "Opšte",
+ "components.Settings.mediaTypeMovie": "film",
+ "components.UserList.owner": "Vlasnik",
+ "components.UserList.password": "Lozinka",
+ "i18n.request": "Zahtev",
+ "i18n.requesting": "Zahteva se…",
+ "i18n.saving": "Čuvanje…",
+ "i18n.testing": "Testiranje…",
+ "components.ManageSlideOver.movie": "film",
+ "components.Settings.SettingsAbout.Releases.currentversion": "Trenutni",
+ "components.Settings.SettingsAbout.Releases.latestversion": "Najnovije",
+ "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Administrator",
+ "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Vlasnik",
+ "components.UserProfile.UserSettings.UserGeneralSettings.general": "Opšte",
+ "components.UserProfile.UserSettings.UserPermissions.permissions": "Dozvole",
+ "components.PermissionEdit.admin": "Administrator",
+ "components.UserProfile.UserSettings.UserPasswordChange.password": "Lozinka",
+ "components.ResetPassword.password": "Lozinka",
+ "components.Search.search": "Pretraga",
+ "components.MovieDetails.originaltitle": "Originalni naslov",
+ "components.RequestList.RequestItem.requested": "Zahtevano",
+ "components.RequestModal.AdvancedRequester.advancedoptions": "Napredno",
+ "components.RequestModal.QuotaDisplay.season": "sezona",
+ "components.Settings.scanning": "Sinhronizacija u toku…",
+ "components.UserProfile.UserSettings.UserGeneralSettings.user": "Korisnik",
+ "components.Settings.SettingsAbout.Releases.releases": "Izdanja",
+ "components.Settings.SettingsJobsCache.jobs": "Poslovi",
+ "components.Settings.SettingsLogs.filterDebug": "Otklanjanje grešaka",
+ "components.Settings.SettingsLogs.filterError": "Greška",
+ "components.Settings.SettingsLogs.filterWarn": "Upozorenje",
+ "components.Settings.SettingsLogs.message": "Poruka",
+ "components.Settings.SettingsLogs.time": "Vremenska oznaka",
+ "components.Settings.SettingsAbout.about": "O nama",
+ "components.Settings.SettingsUsers.users": "Korisnici",
+ "components.Settings.notifications": "Notifikacije",
+ "components.Setup.setup": "Podesiti",
+ "components.UserProfile.UserSettings.menuChangePass": "Lozinka",
+ "components.UserProfile.UserSettings.menuNotifications": "Notifikacije",
+ "components.UserProfile.UserSettings.menuPermissions": "Dozvole",
+ "i18n.close": "Zatvori",
+ "i18n.canceling": "Otkazivanje…",
+ "i18n.movie": "Film",
+ "i18n.retry": "Pokušaj ponovo",
+ "i18n.edit": "Izmena",
+ "i18n.settings": "Podešavanja",
+ "i18n.next": "Sledeći",
+ "i18n.view": "Pogled",
+ "components.RequestList.RequestItem.deleterequest": "Obriši zahtev",
+ "components.RequestModal.selectmovies": "Selektuj film(ove)",
+ "components.Settings.SettingsAbout.documentation": "Dokumentacija",
+ "i18n.experimental": "Eksperimentalno",
+ "components.PermissionEdit.autoapprove": "Automatsko odobrenje",
+ "components.UserProfile.UserSettings.UserGeneralSettings.role": "Uloga",
+ "components.UserProfile.unlimited": "Neograničeno",
+ "i18n.previous": "Prethodni",
+ "components.UserList.sortCreated": "Datum pridruživanja",
+ "components.Discover.upcomingtv": "Predstojeće serije",
+ "components.IssueDetails.IssueComment.areyousuredelete": "Da li ste sigurni da želite da obrišete ovaj komentar?",
+ "components.IssueDetails.IssueComment.delete": "Obriši komentar",
+ "components.IssueDetails.IssueComment.edit": "Izmeni komentar",
+ "components.IssueDetails.IssueComment.validationComment": "Morate uneti poruku",
+ "components.Layout.Sidebar.issues": "Problemi",
+ "components.PermissionEdit.request4k": "Zahtevajte 4K",
+ "components.Settings.SettingsJobsCache.nextexecution": "Sledeće izvršenje",
+ "components.Settings.Notifications.senderName": "Ime pošiljaoca",
+ "components.DownloadBlock.estimatedtime": "Procenjeno {time}",
+ "components.RequestModal.AdvancedRequester.requestas": "Zahtevaj kao",
+ "components.Settings.applicationTitle": "Naziv aplikacije",
+ "i18n.retrying": "Ponovni pokušaj…",
+ "components.Discover.MovieGenreSlider.moviegenres": "Žanrovi filmova",
+ "components.Discover.TvGenreSlider.tvgenres": "Žanrovi serija",
+ "components.Discover.MovieGenreList.moviegenres": "Žanrovi filmova",
+ "components.Discover.TvGenreList.seriesgenres": "Žanrovi serija",
+ "components.RegionSelector.regionDefault": "Svi regioni",
+ "components.Settings.SonarrModal.externalUrl": "Eksterni URL",
+ "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} filmova",
+ "components.StatusChacker.newversionavailable": "Ažuriranje aplikacije",
+ "components.Settings.originallanguage": "Otkrijte jezik",
+ "components.CollectionDetails.numberofmovies": "{count} filmova",
+ "components.RequestList.sortAdded": "Najnoviji",
+ "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} filmovi",
+ "components.IssueDetails.comments": "Komentari",
+ "components.IssueDetails.commentplaceholder": "Dodajte komentar…",
+ "components.IssueDetails.issuetype": "Tip",
+ "components.IssueDetails.deleteissue": "Obriši problem",
+ "components.IssueDetails.episode": "Epizoda {episodeNumber}",
+ "components.IssueDetails.lastupdated": "Poslednja izmena",
+ "components.IssueDetails.nocomments": "Nema komentara.",
+ "components.IssueDetails.openin4karr": "Otvori u 4K {arr}",
+ "components.IssueDetails.openinarr": "Otvori u {arr}",
+ "components.IssueDetails.playonplex": "Pusti na Plexu",
+ "components.IssueList.IssueItem.issuetype": "Tip",
+ "components.IssueDetails.season": "Sezona {seasonNumber}",
+ "components.IssueList.IssueItem.opened": "Otvoren",
+ "components.IssueList.IssueItem.unknownissuetype": "Nepoznat",
+ "components.IssueModal.issueOther": "Ostalo",
+ "components.IssueModal.issueVideo": "Video",
+ "components.Login.signin": "Prijavite se",
+ "components.Login.signingin": "Prijavljivanje…",
+ "components.Login.forgotpassword": "Zaboravili ste lozinku?",
+ "components.ManageSlideOver.manageModalRequests": "Zahtevi",
+ "components.ManageSlideOver.tvshow": "serije",
+ "components.MediaSlider.ShowMoreCard.seemore": "Vidi više",
+ "components.RequestBlock.profilechanged": "Profil kvaliteta",
+ "components.PlexLoginButton.signingin": "Prijavljivanje…",
+ "components.PermissionEdit.autoapproveMovies": "Automatsko odobrenje filmova",
+ "components.PermissionEdit.autoapproveSeries": "Automatsko odobrenje serija",
+ "components.RequestButton.requestmore": "Zatraži još",
+ "components.RequestButton.viewrequest": "Pogledaj zahtev",
+ "components.RequestModal.AdvancedRequester.rootfolder": "Root folder",
+ "components.Settings.RadarrModal.externalUrl": "Eksterni URL",
+ "components.Settings.SettingsJobsCache.cacheksize": "Veličina ključa",
+ "components.Settings.serverpresetManualMessage": "Ručna konfiguracija",
+ "components.UserList.sortRequests": "Broj zahteva",
+ "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Lokalni korisnik",
+ "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Standardna podešavanja",
+ "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Plex korisnik",
+ "components.PermissionEdit.autoapprove4k": "Automatsko odobrenje 4K",
+ "components.Discover.noRequests": "Nema zahteva.",
+ "components.IssueModal.issueSubtitles": "Titl",
+ "components.PermissionEdit.managerequests": "Upravljajte zahtevima",
+ "components.RequestBlock.rootfolder": "Root folder",
+ "components.RequestButton.declinerequest": "Odbijte zahtev",
+ "components.RequestModal.AdvancedRequester.destinationserver": "Odredišni server",
+ "components.RequestList.RequestItem.requesteddate": "Zahtevano",
+ "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Pristupni token",
+ "components.Settings.SettingsJobsCache.jobname": "Ima posla",
+ "components.Settings.SonarrModal.languageprofile": "Jezik profila",
+ "components.Settings.is4k": "4K",
+ "components.Settings.mediaTypeSeries": "serije",
+ "components.Settings.serverSecure": "bezbedno",
+ "components.UserList.bulkedit": "Grupno uređivanje",
+ "components.UserList.localuser": "Lokalni korisnik",
+ "components.UserProfile.ProfileHeader.settings": "Izmena podešavanja",
+ "i18n.status": "Status",
+ "components.IssueDetails.IssueDescription.edit": "Izmeni opis",
+ "components.IssueList.IssueItem.issuestatus": "Status",
+ "components.Settings.SettingsJobsCache.jobstarted": "{jobname} je započeo.",
+ "components.UserProfile.ProfileHeader.profile": "Pogledaj profil",
+ "components.IssueDetails.allepisodes": "Sve epizode",
+ "components.IssueDetails.IssueDescription.deleteissue": "Izbriši problem",
+ "components.IssueDetails.IssueDescription.description": "Opis",
+ "components.IssueDetails.allseasons": "Sve sezone",
+ "components.IssueDetails.unknownissuetype": "Nepoznat",
+ "components.PermissionEdit.settings": "Upravljajte podešavanjima",
+ "components.Settings.SonarrModal.syncEnabled": "Omogući skeniranje",
+ "components.ResetPassword.confirmpassword": "Potvrdi lozinku",
+ "components.Settings.SettingsJobsCache.cachename": "Ime keša",
+ "components.IssueModal.CreateIssueModal.extras": "Dodaci",
+ "components.RequestModal.autoapproval": "Automatsko odobrenje",
+ "components.Settings.Notifications.encryptionNone": "Nijedan",
+ "components.Settings.SettingsJobsCache.runnow": "Pokreni sada",
+ "components.IssueDetails.play4konplex": "Pusti u 4K na Plexu",
+ "components.IssueList.issues": "Problemi",
+ "components.IssueModal.issueAudio": "Audio",
+ "components.PermissionEdit.viewrequests": "Pogledaj zahteve",
+ "components.RequestButton.approverequest": "Odobri zahtev",
+ "components.RequestModal.AdvancedRequester.languageprofile": "Jezik profila",
+ "components.Settings.SettingsAbout.timezone": "Vremenska zona",
+ "components.Settings.serverpresetRefreshing": "Preuzimanje servera…",
+ "components.Settings.RadarrModal.announced": "Najavljeno",
+ "components.Settings.RadarrModal.syncEnabled": "Omogući skeniranje",
+ "components.Settings.SettingsJobsCache.cachehits": "Pogodaka",
+ "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frekvencija",
+ "components.Settings.SettingsAbout.Releases.viewchangelog": "Prikaži dnevnik promena",
+ "components.Settings.SettingsJobsCache.cachekeys": "Ukupno ključeva",
+ "components.Settings.SettingsJobsCache.canceljob": "Otkaži posao",
+ "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} je otkazan.",
+ "components.Settings.region": "Otkrijte region",
+ "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "ID korisnika",
+ "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Podešavanje notifikacija",
+ "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Potvrdi lozinku",
+ "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Trenutna lozinka",
+ "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "Nova lozinka",
+ "components.UserProfile.recentrequests": "Nedavni zahtevi",
+ "i18n.open": "Otvori",
+ "i18n.resolved": "Rešeno",
+ "components.RequestBlock.server": "Odredišni server",
+ "components.PermissionEdit.advancedrequest": "Napredni zahtevi",
+ "components.IssueDetails.leavecomment": "Komentar",
+ "components.PlexLoginButton.signinwithplex": "Prijavite se",
+ "components.PermissionEdit.users": "Upravljajte korisnica"
}
From b4b2acd4fc682d4a90af941826021c44a9d2fee6 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 9 Jan 2022 06:16:50 -0800
Subject: [PATCH 04/76] build(docker): reduce image size (#2392)
---
Dockerfile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dockerfile b/Dockerfile
index c38730f5..a529a423 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,7 +26,7 @@ RUN yarn build
# remove development dependencies
RUN yarn install --production --ignore-scripts --prefer-offline
-RUN rm -rf src server
+RUN rm -rf src server .next/cache
RUN touch config/DOCKER
@@ -37,7 +37,7 @@ FROM node:14.18-alpine
WORKDIR /app
-RUN apk add --no-cache tzdata tini
+RUN apk add --no-cache tzdata tini && rm -rf /tmp/*
# copy from build image
COPY --from=BUILD_IMAGE /app ./
From 4e56bae98508c1a60aeb3a08560ba1c00acce7e7 Mon Sep 17 00:00:00 2001
From: Danshil Kokil Mungur
Date: Sun, 9 Jan 2022 18:26:06 +0400
Subject: [PATCH 05/76] feat(ui): add trakt external link (#2367)
* feat(ui): add trakt external link
* feat(ui): move trakt to end of list of external links
Co-authored-by: Ryan Cohen
---
src/assets/services/trakt.svg | 1 +
src/components/ExternalLinkBlock/index.tsx | 13 +++++++++++++
2 files changed, 14 insertions(+)
create mode 100644 src/assets/services/trakt.svg
diff --git a/src/assets/services/trakt.svg b/src/assets/services/trakt.svg
new file mode 100644
index 00000000..9d3941eb
--- /dev/null
+++ b/src/assets/services/trakt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/ExternalLinkBlock/index.tsx b/src/components/ExternalLinkBlock/index.tsx
index 2c3357b0..1b1f06ed 100644
--- a/src/components/ExternalLinkBlock/index.tsx
+++ b/src/components/ExternalLinkBlock/index.tsx
@@ -4,6 +4,7 @@ import ImdbLogo from '../../assets/services/imdb.svg';
import PlexLogo from '../../assets/services/plex.svg';
import RTLogo from '../../assets/services/rt.svg';
import TmdbLogo from '../../assets/services/tmdb.svg';
+import TraktLogo from '../../assets/services/trakt.svg';
import TvdbLogo from '../../assets/services/tvdb.svg';
import useLocale from '../../hooks/useLocale';
@@ -78,6 +79,18 @@ const ExternalLinkBlock: React.FC = ({
)}
+ {tmdbId && (
+
+
+
+ )}
);
};
From 399b0379186ed34dcc436bd95330fd1a05fef4b3 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Sun, 9 Jan 2022 06:46:33 -0800
Subject: [PATCH 06/76] fix(lang): rename 'Media' notification types for
clarity (#2400)
---
docs/using-overseerr/notifications/webhooks.md | 12 ++++++------
src/components/NotificationTypeSelector/index.tsx | 12 ++++++------
src/i18n/locale/en.json | 12 ++++++------
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/docs/using-overseerr/notifications/webhooks.md b/docs/using-overseerr/notifications/webhooks.md
index 686f82be..37a5c048 100644
--- a/docs/using-overseerr/notifications/webhooks.md
+++ b/docs/using-overseerr/notifications/webhooks.md
@@ -47,15 +47,15 @@ These variables are for the target recipient of the notification.
{% hint style="info" %}
The `notifyuser` variables are not defined for the following request notification types, as they are intended for application administrators rather than end users:
-- Media Requested
-- Media Automatically Approved
-- Media Failed
+- Request Pending Approval
+- Request Automatically Approved
+- Request Processing Failed
On the other hand, the `notifyuser` variables _will_ be replaced with the requesting user's information for the below notification types:
-- Media Approved
-- Media Declined
-- Media Available
+- Request Approved
+- Request Declined
+- Request Available
If you would like to use the requesting user's information in your webhook, please instead include the relevant variables from the [Request](#request) section below.
{% endhint %}
diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx
index 567ce052..2bbdd446 100644
--- a/src/components/NotificationTypeSelector/index.tsx
+++ b/src/components/NotificationTypeSelector/index.tsx
@@ -7,32 +7,32 @@ import NotificationType from './NotificationType';
const messages = defineMessages({
notificationTypes: 'Notification Types',
- mediarequested: 'Media Requested',
+ mediarequested: 'Request Pending Approval',
mediarequestedDescription:
'Send notifications when users submit new media requests which require approval.',
usermediarequestedDescription:
'Get notified when other users submit new media requests which require approval.',
- mediaapproved: 'Media Approved',
+ mediaapproved: 'Request Approved',
mediaapprovedDescription:
'Send notifications when media requests are manually approved.',
usermediaapprovedDescription:
'Get notified when your media requests are approved.',
- mediaAutoApproved: 'Media Automatically Approved',
+ mediaAutoApproved: 'Request Automatically Approved',
mediaAutoApprovedDescription:
'Send notifications when users submit new media requests which are automatically approved.',
usermediaAutoApprovedDescription:
'Get notified when other users submit new media requests which are automatically approved.',
- mediaavailable: 'Media Available',
+ mediaavailable: 'Request Available',
mediaavailableDescription:
'Send notifications when media requests become available.',
usermediaavailableDescription:
'Get notified when your media requests become available.',
- mediafailed: 'Media Failed',
+ mediafailed: 'Request Processing Failed',
mediafailedDescription:
'Send notifications when media requests fail to be added to Radarr or Sonarr.',
usermediafailedDescription:
'Get notified when media requests fail to be added to Radarr or Sonarr.',
- mediadeclined: 'Media Declined',
+ mediadeclined: 'Request Declined',
mediadeclinedDescription:
'Send notifications when media requests are declined.',
usermediadeclinedDescription:
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 19b997ab..bd769ba5 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -179,17 +179,17 @@
"components.NotificationTypeSelector.issuereopenedDescription": "Send notifications when issues are reopened.",
"components.NotificationTypeSelector.issueresolved": "Issue Resolved",
"components.NotificationTypeSelector.issueresolvedDescription": "Send notifications when issues are resolved.",
- "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatically Approved",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Request Automatically Approved",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Send notifications when users submit new media requests which are automatically approved.",
- "components.NotificationTypeSelector.mediaapproved": "Media Approved",
+ "components.NotificationTypeSelector.mediaapproved": "Request Approved",
"components.NotificationTypeSelector.mediaapprovedDescription": "Send notifications when media requests are manually approved.",
- "components.NotificationTypeSelector.mediaavailable": "Media Available",
+ "components.NotificationTypeSelector.mediaavailable": "Request Available",
"components.NotificationTypeSelector.mediaavailableDescription": "Send notifications when media requests become available.",
- "components.NotificationTypeSelector.mediadeclined": "Media Declined",
+ "components.NotificationTypeSelector.mediadeclined": "Request Declined",
"components.NotificationTypeSelector.mediadeclinedDescription": "Send notifications when media requests are declined.",
- "components.NotificationTypeSelector.mediafailed": "Media Failed",
+ "components.NotificationTypeSelector.mediafailed": "Request Processing Failed",
"components.NotificationTypeSelector.mediafailedDescription": "Send notifications when media requests fail to be added to Radarr or Sonarr.",
- "components.NotificationTypeSelector.mediarequested": "Media Requested",
+ "components.NotificationTypeSelector.mediarequested": "Request Pending Approval",
"components.NotificationTypeSelector.mediarequestedDescription": "Send notifications when users submit new media requests which require approval.",
"components.NotificationTypeSelector.notificationTypes": "Notification Types",
"components.NotificationTypeSelector.userissuecommentDescription": "Get notified when issues you reported receive new comments.",
From 1b29b15d7c9a7ec918cb59116d60e1ae2e797dc4 Mon Sep 17 00:00:00 2001
From: "Weblate (bot)"
Date: Sun, 9 Jan 2022 19:00:18 +0100
Subject: [PATCH 07/76] feat(lang): translations update from Hosted Weblate
(#2404)
* feat(lang): translated using Weblate (Polish)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Patryk
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Swedish)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Luna Jernberg
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: TheCatLady
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Tijuco
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/
Translation: Overseerr/Overseerr Frontend
Co-authored-by: Patryk
Co-authored-by: Luna Jernberg
Co-authored-by: TheCatLady
Co-authored-by: Tijuco
---
src/i18n/locale/pl.json | 12 ++++++------
src/i18n/locale/pt_BR.json | 12 ++++++------
src/i18n/locale/sv.json | 14 +++++++++++++-
src/i18n/locale/zh_Hant.json | 2 +-
4 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/src/i18n/locale/pl.json b/src/i18n/locale/pl.json
index 2ca225d9..47705ae0 100644
--- a/src/i18n/locale/pl.json
+++ b/src/i18n/locale/pl.json
@@ -48,7 +48,7 @@
"components.IssueModal.issueVideo": "Wideo",
"components.ManageSlideOver.manageModalIssues": "Otwarte problemy",
"components.ManageSlideOver.manageModalRequests": "Prośby",
- "components.NotificationTypeSelector.mediarequested": "Żądane media",
+ "components.NotificationTypeSelector.mediarequested": "Prośba o oczekujące na zatwierdzenie",
"components.PermissionEdit.admin": "Admin",
"components.PermissionEdit.autoapprove4kMovies": "Automatyczne zatwierdzanie filmów 4K",
"components.IssueDetails.openin4karr": "Otwórz w 4K {arr}",
@@ -94,12 +94,12 @@
"components.NotificationTypeSelector.issuecreatedDescription": "Wysyłaj powiadomienia, gdy zostaną zgłoszone problemy.",
"components.NotificationTypeSelector.issueresolved": "Problem rozwiązany",
"components.NotificationTypeSelector.issueresolvedDescription": "Wysyłaj powiadomienia, gdy problemy zostaną rozwiązane.",
- "components.NotificationTypeSelector.mediaAutoApproved": "Media automatycznie zatwierdzane",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Prośba o automatyczne zatwierdzenie",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Wysyłaj powiadomienia, gdy użytkownicy składają nowe prośby o media, które są automatycznie zatwierdzane.",
"components.NotificationTypeSelector.mediaapprovedDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia zostaną ręcznie zatwierdzone.",
"components.NotificationTypeSelector.mediaavailableDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia staną się dostępne.",
"components.NotificationTypeSelector.mediadeclinedDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia zostaną odrzucone.",
- "components.NotificationTypeSelector.mediafailed": "Awaria multimediów",
+ "components.NotificationTypeSelector.mediafailed": "Przetwarzanie żądania nie powiodło się",
"components.PermissionEdit.autoapproveSeriesDescription": "Przyznaj automatyczne zatwierdzanie próśb o filmy inne niż 4K.",
"components.PermissionEdit.createissues": "Zgłoś problemy",
"components.PermissionEdit.manageissues": "Zarządzaj problemami",
@@ -431,9 +431,9 @@
"components.IssueDetails.playonplex": "Odtwarzaj na Plex",
"components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Odcinek} other {Odcinki}}",
"components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {sezon} other {sezony}}",
- "components.NotificationTypeSelector.mediaapproved": "Zatwierdzone media",
- "components.NotificationTypeSelector.mediaavailable": "Multimedia dostępne",
- "components.NotificationTypeSelector.mediadeclined": "Multimedia odrzucone",
+ "components.NotificationTypeSelector.mediaapproved": "Prośba zatwierdzona",
+ "components.NotificationTypeSelector.mediaavailable": "Dostępne",
+ "components.NotificationTypeSelector.mediadeclined": "Prośba odrzucona",
"components.NotificationTypeSelector.mediafailedDescription": "Wysyłaj powiadomienia, gdy prośby o multimedia nie zostaną dodane do Radarr lub Sonarr.",
"components.PermissionEdit.createissuesDescription": "Udzielanie zgody na zgłaszanie problemów z multimediami.",
"components.PermissionEdit.managerequests": "Zarządzaj prośbami",
diff --git a/src/i18n/locale/pt_BR.json b/src/i18n/locale/pt_BR.json
index 02e85214..1b1e7f45 100644
--- a/src/i18n/locale/pt_BR.json
+++ b/src/i18n/locale/pt_BR.json
@@ -257,11 +257,11 @@
"components.StatusChacker.newversionavailable": "Atualização da Aplicação",
"components.Settings.SettingsAbout.documentation": "Documentação",
"components.NotificationTypeSelector.mediarequestedDescription": "Envia notificações quando outros usuários solicitarem novas mídias que requerem aprovação.",
- "components.NotificationTypeSelector.mediaavailable": "Mídia Disponível",
- "components.NotificationTypeSelector.mediaapproved": "Mídia Aprovada",
- "components.NotificationTypeSelector.mediarequested": "Mídia Solicitada",
+ "components.NotificationTypeSelector.mediaavailable": "Solicitação Disponível",
+ "components.NotificationTypeSelector.mediaapproved": "Solicitação Aprovada",
+ "components.NotificationTypeSelector.mediarequested": "Solicitação com Aprovação Pendente",
"components.NotificationTypeSelector.mediafailedDescription": "Envia notificaões quando solicitações de mídia falharem ao serem adicionadas ao Radarr ou Sonarr.",
- "components.NotificationTypeSelector.mediafailed": "Solicitação Falhou",
+ "components.NotificationTypeSelector.mediafailed": "Falha ao Processar Solicitação",
"components.NotificationTypeSelector.mediaavailableDescription": "Envia notificações quando mídias solicitadas estiverem disponíveis.",
"components.NotificationTypeSelector.mediaapprovedDescription": "Enviar notificações quando solicitações de mídia são aprovadas manualmente.",
"i18n.request": "Solicitar",
@@ -318,7 +318,7 @@
"components.Login.loginerror": "Algo deu errado ao tentar se autenticar.",
"components.Login.email": "Endereço de E-mail",
"components.NotificationTypeSelector.mediadeclinedDescription": "Envia notificações quando solicitações de mídia são recusadas.",
- "components.NotificationTypeSelector.mediadeclined": "Mídia Recusada",
+ "components.NotificationTypeSelector.mediadeclined": "Solicitação Recusada",
"components.MediaSlider.ShowMoreCard.seemore": "Ver Mais",
"components.RequestModal.requestedited": "Solicitação de {title} alterada com sucesso!",
"components.RequestModal.requestcancelled": "Solicitação de {title} foi cancelada.",
@@ -592,7 +592,7 @@
"components.Settings.Notifications.pgpPasswordTip": "Assina mensagens encriptadas de e-mail usando OpenPGP",
"components.Settings.Notifications.pgpPassword": "Senha PGP",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envia notificações quando usuários solicitarem mídias que são aprovadas automaticamente.",
- "components.NotificationTypeSelector.mediaAutoApproved": "Mídia Aprovada Automaticamente",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Solicitação Aprovada Automaticamente",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutos",
"components.RequestModal.AdvancedRequester.folder": "{path} ({space})",
"components.TvDetails.episodeRuntime": "Extensão de episódio",
diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json
index 90c5b5ab..9f16b05a 100644
--- a/src/i18n/locale/sv.json
+++ b/src/i18n/locale/sv.json
@@ -986,5 +986,17 @@
"components.NotificationTypeSelector.issuereopened": "Problem återöppnat",
"components.NotificationTypeSelector.issuereopenedDescription": "Skicka avisering när ett problem at återöppnats.",
"components.NotificationTypeSelector.userissuereopenedDescription": "Få avisering när ett problem som du har rapporterat har återöppnats.",
- "components.NotificationTypeSelector.adminissueresolvedDescription": "Få avisering när ett problem har blivit löst."
+ "components.NotificationTypeSelector.adminissueresolvedDescription": "Få avisering när ett problem har blivit löst.",
+ "components.MovieDetails.productioncountries": "Produktions{countryCount, plural, one {land} other {länder}}",
+ "components.RequestModal.selectmovies": "Välj Film(er)",
+ "components.IssueDetails.commentplaceholder": "Lägg till en kommentar…",
+ "components.RequestModal.approve": "Godkänn efterfrågan",
+ "components.RequestModal.requestApproved": "Begäran av {title} godkänd!",
+ "components.RequestModal.requestmovies": "Begär {count} {count, plural, one {Film} other {Filmer}}",
+ "components.RequestModal.requestmovies4k": "Begär {count} {count, plural, one {Film} other {Filmer}} i 4K",
+ "components.RequestModal.requestseasons4k": "Begär {seasonCount} {seasonCount, plural, one {Säsong} other {Säsonger}} i 4K",
+ "components.Settings.RadarrModal.inCinemas": "På Bio",
+ "components.Settings.RadarrModal.released": "Släppt",
+ "components.Settings.RadarrModal.announced": "Annonserat",
+ "components.TvDetails.productioncountries": "Produktions{countryCount, plural, one {land} other {länder}}"
}
diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json
index f7054265..f960c3e3 100644
--- a/src/i18n/locale/zh_Hant.json
+++ b/src/i18n/locale/zh_Hant.json
@@ -109,7 +109,7 @@
"components.NotificationTypeSelector.mediaapproved": "請求批准",
"components.NotificationTypeSelector.mediaavailableDescription": "當請求的媒體可觀看時發送通知。",
"components.NotificationTypeSelector.mediaapprovedDescription": "當請求被手動批准時發送通知。",
- "components.NotificationTypeSelector.mediaavailable": "媒體可觀看",
+ "components.NotificationTypeSelector.mediaavailable": "請求可觀看",
"components.MovieDetails.watchtrailer": "觀看預告片",
"components.MovieDetails.viewfullcrew": "檢視完整製作群",
"components.MovieDetails.studio": "製作公司",
From 5f7538ae2bf9c6e2feea385cc299bd08df071218 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Mon, 10 Jan 2022 17:39:12 -0800
Subject: [PATCH 08/76] feat(discord): add 'Enable Mentions' setting (#1779)
---
overseerr-api.yml | 2 +
server/lib/notifications/agents/discord.ts | 54 ++++++++++---------
server/lib/settings.ts | 2 +
server/routes/user/usersettings.ts | 10 ++--
.../Notifications/NotificationsDiscord.tsx | 16 ++++++
src/i18n/locale/en.json | 1 +
6 files changed, 55 insertions(+), 30 deletions(-)
diff --git a/overseerr-api.yml b/overseerr-api.yml
index f8be0895..213ef1d7 100644
--- a/overseerr-api.yml
+++ b/overseerr-api.yml
@@ -1138,6 +1138,8 @@ components:
type: string
webhookUrl:
type: string
+ enableMentions:
+ type: boolean
SlackSettings:
type: object
properties:
diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts
index bd07c4de..32120035 100644
--- a/server/lib/notifications/agents/discord.ts
+++ b/server/lib/notifications/agents/discord.ts
@@ -258,35 +258,37 @@ class DiscordAgent
const userMentions: string[] = [];
try {
- if (payload.notifyUser) {
- if (
- payload.notifyUser.settings?.hasNotificationType(
- NotificationAgentKey.DISCORD,
- type
- ) &&
- payload.notifyUser.settings.discordId
- ) {
- userMentions.push(`<@${payload.notifyUser.settings.discordId}>`);
+ if (settings.options.enableMentions) {
+ if (payload.notifyUser) {
+ if (
+ payload.notifyUser.settings?.hasNotificationType(
+ NotificationAgentKey.DISCORD,
+ type
+ ) &&
+ payload.notifyUser.settings.discordId
+ ) {
+ userMentions.push(`<@${payload.notifyUser.settings.discordId}>`);
+ }
}
- }
- if (payload.notifyAdmin) {
- const userRepository = getRepository(User);
- const users = await userRepository.find();
+ if (payload.notifyAdmin) {
+ const userRepository = getRepository(User);
+ const users = await userRepository.find();
- userMentions.push(
- ...users
- .filter(
- (user) =>
- user.settings?.hasNotificationType(
- NotificationAgentKey.DISCORD,
- type
- ) &&
- user.settings.discordId &&
- shouldSendAdminNotification(type, user, payload)
- )
- .map((user) => `<@${user.settings?.discordId}>`)
- );
+ userMentions.push(
+ ...users
+ .filter(
+ (user) =>
+ user.settings?.hasNotificationType(
+ NotificationAgentKey.DISCORD,
+ type
+ ) &&
+ user.settings.discordId &&
+ shouldSendAdminNotification(type, user, payload)
+ )
+ .map((user) => `<@${user.settings?.discordId}>`)
+ );
+ }
}
await axios.post(settings.options.webhookUrl, {
diff --git a/server/lib/settings.ts b/server/lib/settings.ts
index f7780dfc..eb50e25b 100644
--- a/server/lib/settings.ts
+++ b/server/lib/settings.ts
@@ -125,6 +125,7 @@ export interface NotificationAgentDiscord extends NotificationAgentConfig {
botUsername?: string;
botAvatarUrl?: string;
webhookUrl: string;
+ enableMentions: boolean;
};
}
@@ -304,6 +305,7 @@ class Settings {
types: 0,
options: {
webhookUrl: '',
+ enableMentions: true,
},
},
lunasea: {
diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts
index 6558115a..22a26531 100644
--- a/server/routes/user/usersettings.ts
+++ b/server/routes/user/usersettings.ts
@@ -252,10 +252,12 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>(
return res.status(200).json({
emailEnabled: settings?.email.enabled,
pgpKey: user.settings?.pgpKey,
- discordEnabled: settings?.discord.enabled,
- discordEnabledTypes: settings?.discord.enabled
- ? settings?.discord.types
- : 0,
+ discordEnabled:
+ settings?.discord.enabled && settings.discord.options.enableMentions,
+ discordEnabledTypes:
+ settings?.discord.enabled && settings.discord.options.enableMentions
+ ? settings.discord.types
+ : 0,
discordId: user.settings?.discordId,
pushbulletAccessToken: user.settings?.pushbulletAccessToken,
pushoverApplicationToken: user.settings?.pushoverApplicationToken,
diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx
index 9e2a6701..8c882dd6 100644
--- a/src/components/Settings/Notifications/NotificationsDiscord.tsx
+++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx
@@ -26,6 +26,7 @@ const messages = defineMessages({
toastDiscordTestFailed: 'Discord test notification failed to send.',
validationUrl: 'You must provide a valid URL',
validationTypes: 'You must select at least one notification type',
+ enableMentions: 'Enable Mentions',
});
const NotificationsDiscord: React.FC = () => {
@@ -64,6 +65,7 @@ const NotificationsDiscord: React.FC = () => {
botUsername: data?.options.botUsername,
botAvatarUrl: data?.options.botAvatarUrl,
webhookUrl: data.options.webhookUrl,
+ enableMentions: data?.options.enableMentions,
}}
validationSchema={NotificationsDiscordSchema}
onSubmit={async (values) => {
@@ -75,6 +77,7 @@ const NotificationsDiscord: React.FC = () => {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
+ enableMentions: values.enableMentions,
},
});
@@ -122,6 +125,7 @@ const NotificationsDiscord: React.FC = () => {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
+ enableMentions: values.enableMentions,
},
});
@@ -227,6 +231,18 @@ const NotificationsDiscord: React.FC = () => {
)}
+
+
+
+
+
+
{
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index bd769ba5..47f5f1c8 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -448,6 +448,7 @@
"components.Settings.Notifications.emailsender": "Sender Address",
"components.Settings.Notifications.emailsettingsfailed": "Email notification settings failed to save.",
"components.Settings.Notifications.emailsettingssaved": "Email notification settings saved successfully!",
+ "components.Settings.Notifications.enableMentions": "Enable Mentions",
"components.Settings.Notifications.encryption": "Encryption Method",
"components.Settings.Notifications.encryptionDefault": "Use STARTTLS if available",
"components.Settings.Notifications.encryptionImplicitTls": "Use Implicit TLS",
From 879df20022c8c5d9b32858ac5499d3e4369fc064 Mon Sep 17 00:00:00 2001
From: "Weblate (bot)"
Date: Tue, 11 Jan 2022 16:07:31 +0100
Subject: [PATCH 09/76] feat(lang): translations update from Hosted Weblate
(#2405)
* feat(lang): translated using Weblate (French)
Currently translated at 100.0% (1000 of 1000 strings)
feat(lang): translated using Weblate (French)
Currently translated at 100.0% (1000 of 1000 strings)
feat(lang): translated using Weblate (French)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Dylan
Co-authored-by: Hosted Weblate
Co-authored-by: TheCatLady
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Polish)
Currently translated at 100.0% (1001 of 1001 strings)
feat(lang): translated using Weblate (Polish)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Patryk
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Swedish)
Currently translated at 100.0% (1001 of 1001 strings)
Co-authored-by: Shjosan
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Chinese (Simplified))
Currently translated at 90.3% (903 of 1000 strings)
feat(lang): translated using Weblate (Chinese (Simplified))
Currently translated at 90.3% (903 of 1000 strings)
Co-authored-by: Eric
Co-authored-by: Hosted Weblate
Co-authored-by: TheCatLady
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1001 of 1001 strings)
feat(lang): translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: TheCatLady
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Dutch)
Currently translated at 100.0% (1001 of 1001 strings)
feat(lang): translated using Weblate (Dutch)
Currently translated at 100.0% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Kobe
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/
Translation: Overseerr/Overseerr Frontend
* feat(lang): translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1001 of 1001 strings)
Co-authored-by: Tijuco
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% (1000 of 1000 strings)
Co-authored-by: Hosted Weblate
Co-authored-by: Simone
Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/
Translation: Overseerr/Overseerr Frontend
Co-authored-by: Dylan
Co-authored-by: TheCatLady
Co-authored-by: Patryk
Co-authored-by: Shjosan
Co-authored-by: Eric
Co-authored-by: Kobe
Co-authored-by: Tijuco
Co-authored-by: Simone
---
src/i18n/locale/fr.json | 34 +++++++++++++++++++++++-----------
src/i18n/locale/it.json | 17 ++++++++++-------
src/i18n/locale/nl.json | 15 ++++++++-------
src/i18n/locale/pl.json | 5 +++--
src/i18n/locale/pt_BR.json | 7 ++++---
src/i18n/locale/sv.json | 15 ++++++++-------
src/i18n/locale/zh_Hans.json | 10 +++++-----
src/i18n/locale/zh_Hant.json | 21 +++++++++++----------
8 files changed, 72 insertions(+), 52 deletions(-)
diff --git a/src/i18n/locale/fr.json b/src/i18n/locale/fr.json
index dc2cf60b..5b562163 100644
--- a/src/i18n/locale/fr.json
+++ b/src/i18n/locale/fr.json
@@ -79,7 +79,7 @@
"components.Settings.RadarrModal.validationRootFolderRequired": "Vous devez sélectionner un dossier racine",
"components.Settings.SonarrModal.add": "Ajouter un serveur",
"components.Settings.SonarrModal.apiKey": "Clé API",
- "components.Settings.SonarrModal.baseUrl": "URL Base",
+ "components.Settings.SonarrModal.baseUrl": "URL Source",
"components.Settings.SonarrModal.createsonarr": "Ajouter un nouveau serveur Sonarr",
"components.Settings.SonarrModal.defaultserver": "Serveur par défaut",
"components.Settings.SonarrModal.editsonarr": "Modifier le serveur Sonarr",
@@ -257,13 +257,13 @@
"components.StatusChacker.newversionDescription": "Overseerr a été mis à jour ! Veuillez cliquer sur le bouton ci-dessous pour recharger la page.",
"components.Settings.SettingsAbout.documentation": "Documentation",
"components.NotificationTypeSelector.mediarequestedDescription": "Envoie une notification quand un média est demandé et nécessite une validation.",
- "components.NotificationTypeSelector.mediarequested": "Média demandé",
+ "components.NotificationTypeSelector.mediarequested": "Demande en attente d'approbation",
"components.NotificationTypeSelector.mediafailedDescription": "Envoie une notification lorsqu'un média demandé n'a pas pu être ajouté sur Radarr /Sonarr.",
- "components.NotificationTypeSelector.mediafailed": "Échec d’ajout du média",
+ "components.NotificationTypeSelector.mediafailed": "Échec d’ajout de la demande",
"components.NotificationTypeSelector.mediaavailableDescription": "Envoie une notification quand le média demandé devient disponible.",
- "components.NotificationTypeSelector.mediaavailable": "Média disponible",
+ "components.NotificationTypeSelector.mediaavailable": "Demande disponible",
"components.NotificationTypeSelector.mediaapprovedDescription": "Envoie une notification quand le média demandé est validé manuellement.",
- "components.NotificationTypeSelector.mediaapproved": "Média validé",
+ "components.NotificationTypeSelector.mediaapproved": "Demande validée",
"i18n.request": "Demander",
"components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Vous devez fournir un jeton utilisateur valide ou une clef partagée",
"components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Vous devez fournir un jeton d'application valide",
@@ -333,7 +333,7 @@
"components.RequestBlock.server": "Serveur de destination",
"components.RequestBlock.rootfolder": "Dossier racine",
"components.RequestBlock.profilechanged": "Profil qualité",
- "components.NotificationTypeSelector.mediadeclined": "Média refusé",
+ "components.NotificationTypeSelector.mediadeclined": "Demande refusée",
"components.NotificationTypeSelector.mediadeclinedDescription": "Envoie une notification lorsqu'un média demandé est refusé.",
"i18n.experimental": "Expérimentale",
"components.Settings.hideAvailable": "Masquer les médias disponibles",
@@ -563,7 +563,7 @@
"components.RequestList.RequestItem.modified": "Modifiée",
"components.RequestList.RequestItem.requested": "Demandé",
"components.RequestList.RequestItem.modifieduserdate": "{date} par {user}",
- "components.Discover.StudioSlider.studios": "Studios",
+ "components.Discover.StudioSlider.studios": "Studio",
"components.Discover.DiscoverTvLanguage.languageSeries": "Séries en {language}",
"components.Discover.DiscoverMovieLanguage.languageMovies": "Films {language}",
"components.Setup.scanbackground": "Le scan s'effectue en arrière-plan. Vous pouvez donc continuer le processus de configuration pendant ce temps.",
@@ -584,7 +584,7 @@
"components.Settings.menuUsers": "Utilisateurs",
"components.Settings.SettingsUsers.userSettings": "Paramètres utilisateur",
"components.Settings.SettingsUsers.toastSettingsSuccess": "Les paramètres utilisateur ont été enregistrés avec succès !",
- "components.NotificationTypeSelector.mediaAutoApproved": "Média validé automatiquement",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Demande validée automatiquement",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envoie une notification quand un utilisateur envoie une demande pour un nouveau média qui est validée automatiquement.",
"components.UserProfile.UserSettings.unauthorizedDescription": "Vous n'avez pas l'autorisation de modifier les paramètres de cet utilisateur.",
"components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Vous ne pouvez pas modifier vos propres permissions.",
@@ -735,8 +735,8 @@
"components.RequestCard.mediaerror": "Le titre associé à cette demande n’est plus disponible.",
"components.RequestCard.deleterequest": "Supprimer la Demande",
"components.NotificationTypeSelector.notificationTypes": "Types de Notification",
- "components.Layout.VersionStatus.streamstable": "Overseerr Stable",
- "components.Layout.VersionStatus.streamdevelop": "Overseerr Develop",
+ "components.Layout.VersionStatus.streamstable": "Overseerr stable",
+ "components.Layout.VersionStatus.streamdevelop": "Développement d'Overseerr",
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} en retard",
"components.Layout.VersionStatus.outofdate": "Obsolète",
"components.Discover.noRequests": "Aucune demande.",
@@ -986,5 +986,17 @@
"components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Vous devez fournir un jeton d'accès",
"components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Épisode} other {Épisodes}}",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Clef d'utilisateur ou de groupe",
- "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Votre identifiant d'utilisateur ou de groupe à 30 caractères"
+ "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Votre identifiant d'utilisateur ou de groupe à 30 caractères",
+ "components.RequestModal.requestmovies4k": "Demander {count} {count, plural, one {Film} autre {Films}} en 4K",
+ "components.RequestModal.selectmovies": "Selecter Film(s)",
+ "components.IssueDetails.commentplaceholder": "Ajouter un commentaire…",
+ "components.MovieDetails.productioncountries": "Pays de production",
+ "components.Settings.RadarrModal.announced": "Annoncé",
+ "components.RequestModal.approve": "Approuver la demande",
+ "components.RequestModal.requestseasons4k": "Demander {seasonCount} {seasonCount, plural, one {Saison} other {Saisons}} en 4K",
+ "components.RequestModal.requestmovies": "Demander {count} {count, plural, one {Film} other {Films}}",
+ "components.RequestModal.requestApproved": "Demande de {title} approuvée !",
+ "components.Settings.RadarrModal.inCinemas": "Au cinéma",
+ "components.Settings.RadarrModal.released": "Disponible",
+ "components.TvDetails.productioncountries": "Pays de production"
}
diff --git a/src/i18n/locale/it.json b/src/i18n/locale/it.json
index 9a17231e..bbcd341c 100644
--- a/src/i18n/locale/it.json
+++ b/src/i18n/locale/it.json
@@ -257,13 +257,13 @@
"components.StatusChacker.newversionDescription": "Overseerr è stato aggiornato! Premi il pulsante qui sotto per ricaricare la pagina.",
"components.Settings.SettingsAbout.documentation": "Documentazione",
"components.NotificationTypeSelector.mediarequestedDescription": "Invia notifiche quando gli utenti presentano nuove richieste di media che richiedono approvazione.",
- "components.NotificationTypeSelector.mediarequested": "Media Richiesto",
+ "components.NotificationTypeSelector.mediarequested": "Richiesta in attesa di approvazione",
"components.NotificationTypeSelector.mediafailedDescription": "Invia notifiche quando le richieste non vengono aggiunte a Radarr o Sonarr.",
- "components.NotificationTypeSelector.mediafailed": "Aggiunta media non riuscita",
+ "components.NotificationTypeSelector.mediafailed": "Elaborazione della richiesta non riuscita",
"components.NotificationTypeSelector.mediaavailableDescription": "Invia notifiche quando i media richiesti diventano disponibili.",
"components.NotificationTypeSelector.mediaapprovedDescription": "Invia notifiche quando le richieste sono approvate manualmente.",
- "components.NotificationTypeSelector.mediaapproved": "Media approvato",
- "components.NotificationTypeSelector.mediaavailable": "Media disponibile",
+ "components.NotificationTypeSelector.mediaapproved": "Richiesta approvata",
+ "components.NotificationTypeSelector.mediaavailable": "Richiesta disponibile",
"i18n.request": "Richiedi",
"components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "È necessario fornire una chiave utente o di gruppo valida",
"components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "È necessario fornire un token di applicazione valido",
@@ -312,7 +312,7 @@
"components.RequestBlock.rootfolder": "Cartella principale",
"components.RequestBlock.profilechanged": "Profilo Qualità",
"components.NotificationTypeSelector.mediadeclinedDescription": "Invia notifiche quando i media richiesti vengono rifiutati.",
- "components.NotificationTypeSelector.mediadeclined": "Media rifiutato",
+ "components.NotificationTypeSelector.mediadeclined": "Richiesta rifiutata",
"i18n.experimental": "Sperimentale",
"i18n.edit": "Modifica",
"components.StatusBadge.status4k": "4K {status}",
@@ -585,7 +585,7 @@
"components.Settings.SettingsUsers.localLogin": "Abilita Accesso Locale",
"components.Settings.SettingsUsers.defaultPermissions": "Autorizzazioni Predefinite",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Invia notifiche quando gli utenti presentano nuove richieste che vengono approvate automaticamente.",
- "components.NotificationTypeSelector.mediaAutoApproved": "Media approvati automaticamente",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Richiesta approvata automaticamente",
"components.Settings.Notifications.pgpPrivateKey": "Chiave privata PGP",
"components.Settings.Notifications.pgpPasswordTip": "Firma i messaggi di posta elettronica crittografati utilizzando OpenPGP ",
"components.Settings.Notifications.pgpPassword": "Password PGP",
@@ -995,5 +995,8 @@
"components.RequestModal.requestmovies4k": "Richiedi {count} {count, plural, one {Film} other {Films}} in 4K",
"components.IssueDetails.commentplaceholder": "Aggiungi un commento…",
"components.RequestModal.requestApproved": "Richiesta di {title} approvata!",
- "components.RequestModal.approve": "Approva richiesta"
+ "components.RequestModal.approve": "Approva richiesta",
+ "components.Settings.RadarrModal.announced": "Annunciato",
+ "components.Settings.RadarrModal.inCinemas": "Al cinema",
+ "components.Settings.RadarrModal.released": "Rilasciato"
}
diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json
index 75586897..8f4d6ff7 100644
--- a/src/i18n/locale/nl.json
+++ b/src/i18n/locale/nl.json
@@ -202,10 +202,10 @@
"components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studio's}}",
"components.CollectionDetails.overview": "Overzicht",
"components.CollectionDetails.numberofmovies": "{count} films",
- "components.NotificationTypeSelector.mediafailed": "Media mislukt",
+ "components.NotificationTypeSelector.mediafailed": "Verwerking van verzoek mislukt",
"components.NotificationTypeSelector.mediaapprovedDescription": "Een melding sturen wanneer mediaverzoeken handmatig worden goedgekeurd.",
"components.NotificationTypeSelector.mediaavailableDescription": "Een melding sturen wanneer een mediaverzoek beschikbaar is.",
- "components.NotificationTypeSelector.mediaapproved": "Media goedgekeurd",
+ "components.NotificationTypeSelector.mediaapproved": "Verzoek goedgekeurd",
"i18n.retry": "Opnieuw proberen",
"i18n.requested": "Aangevraagd",
"i18n.failed": "Mislukt",
@@ -250,8 +250,8 @@
"components.Settings.Notifications.NotificationsSlack.agentenabled": "Agent inschakelen",
"components.RequestList.RequestItem.failedretry": "Er ging opnieuw iets mis tijdens het aanvragen.",
"components.PersonDetails.crewmember": "Crew",
- "components.NotificationTypeSelector.mediarequested": "Media aangevraagd",
- "components.NotificationTypeSelector.mediaavailable": "Media beschikbaar",
+ "components.NotificationTypeSelector.mediarequested": "Verzoek in afwachting van goedkeuring",
+ "components.NotificationTypeSelector.mediaavailable": "Verzoek beschikbaar",
"components.MovieDetails.watchtrailer": "Trailer bekijken",
"components.MovieDetails.viewfullcrew": "Volledige crew bekijken",
"components.MovieDetails.MovieCrew.fullcrew": "Volledige crew",
@@ -333,7 +333,7 @@
"components.RequestBlock.profilechanged": "Kwaliteitsprofiel",
"components.RequestBlock.requestoverrides": "Overschrijvingen van verzoek",
"components.NotificationTypeSelector.mediadeclinedDescription": "Een melding sturen wanneer een mediaverzoek wordt afgewezen.",
- "components.NotificationTypeSelector.mediadeclined": "Media geweigerd",
+ "components.NotificationTypeSelector.mediadeclined": "Verzoek geweigerd",
"components.RequestModal.autoapproval": "Automatische goedkeuring",
"i18n.experimental": "Experimenteel",
"components.Settings.hideAvailable": "Beschikbare media verbergen",
@@ -585,7 +585,7 @@
"components.Settings.SettingsUsers.localLogin": "Lokaal aanmelden inschakelen",
"components.Settings.SettingsUsers.defaultPermissions": "Standaard machtigingen",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Een melding sturen wanneer een gebruiker nieuwe media aanvraagt die automatisch wordt goedgekeurd.",
- "components.NotificationTypeSelector.mediaAutoApproved": "Media automatisch goedgekeurd",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Verzoek automatisch goedgekeurd",
"components.UserProfile.UserSettings.unauthorizedDescription": "Je hebt geen toestemming om de instellingen van deze gebruiker te wijzigen.",
"components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Je kan je eigen machtigingen niet wijzigen.",
"components.UserProfile.norequests": "Geen verzoeken.",
@@ -998,5 +998,6 @@
"components.RequestModal.approve": "Verzoek goedkeuren",
"components.Settings.RadarrModal.inCinemas": "In de bioscoop",
"components.Settings.RadarrModal.released": "Uitgekomen",
- "components.Settings.RadarrModal.announced": "Aangekondigd"
+ "components.Settings.RadarrModal.announced": "Aangekondigd",
+ "components.Settings.Notifications.enableMentions": "Vermeldingen inschakelen"
}
diff --git a/src/i18n/locale/pl.json b/src/i18n/locale/pl.json
index 47705ae0..114fc957 100644
--- a/src/i18n/locale/pl.json
+++ b/src/i18n/locale/pl.json
@@ -48,7 +48,7 @@
"components.IssueModal.issueVideo": "Wideo",
"components.ManageSlideOver.manageModalIssues": "Otwarte problemy",
"components.ManageSlideOver.manageModalRequests": "Prośby",
- "components.NotificationTypeSelector.mediarequested": "Prośba o oczekujące na zatwierdzenie",
+ "components.NotificationTypeSelector.mediarequested": "Prośba o oczekuje na zatwierdzenie",
"components.PermissionEdit.admin": "Admin",
"components.PermissionEdit.autoapprove4kMovies": "Automatyczne zatwierdzanie filmów 4K",
"components.IssueDetails.openin4karr": "Otwórz w 4K {arr}",
@@ -998,5 +998,6 @@
"components.RequestModal.requestApproved": "Prośba o {title} zatwierdzona!",
"components.Settings.RadarrModal.announced": "Zapowiedziany",
"components.Settings.RadarrModal.inCinemas": "W kinach",
- "components.Settings.RadarrModal.released": "Wydany"
+ "components.Settings.RadarrModal.released": "Wydany",
+ "components.Settings.Notifications.enableMentions": "Włącz wzmianki"
}
diff --git a/src/i18n/locale/pt_BR.json b/src/i18n/locale/pt_BR.json
index 1b1e7f45..da197ad7 100644
--- a/src/i18n/locale/pt_BR.json
+++ b/src/i18n/locale/pt_BR.json
@@ -182,7 +182,7 @@
"components.TvDetails.recommendations": "Recomendações",
"components.TvDetails.overviewunavailable": "Sinopse indisponível.",
"components.TvDetails.overview": "Sinopse",
- "components.TvDetails.originallanguage": "Língua original",
+ "components.TvDetails.originallanguage": "Língua Original",
"components.TvDetails.network": "{networkCount, plural, one {Emissora} other {Emissoras}}",
"components.TvDetails.cast": "Elenco",
"components.TvDetails.anime": "Animes",
@@ -595,7 +595,7 @@
"components.NotificationTypeSelector.mediaAutoApproved": "Solicitação Aprovada Automaticamente",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutos",
"components.RequestModal.AdvancedRequester.folder": "{path} ({space})",
- "components.TvDetails.episodeRuntime": "Extensão de episódio",
+ "components.TvDetails.episodeRuntime": "Extensão de Episódio",
"components.Discover.TvGenreSlider.tvgenres": "Gêneros de Séries",
"components.Discover.TvGenreList.seriesgenres": "Gêneros de Séries",
"components.Discover.MovieGenreSlider.moviegenres": "Gêneros de Filmes",
@@ -998,5 +998,6 @@
"components.RequestModal.approve": "Aprovar Solicitação",
"components.Settings.RadarrModal.announced": "Anunciado",
"components.Settings.RadarrModal.inCinemas": "Nos Cinemas",
- "components.Settings.RadarrModal.released": "Lançado"
+ "components.Settings.RadarrModal.released": "Lançado",
+ "components.Settings.Notifications.enableMentions": "Habilitar Menções"
}
diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json
index 9f16b05a..43454532 100644
--- a/src/i18n/locale/sv.json
+++ b/src/i18n/locale/sv.json
@@ -290,13 +290,13 @@
"components.RequestButton.approverequest": "Godkänn begäran",
"components.RequestButton.approve4krequests": "Godkänn {requestCount, plural, one {4K förfrågning} other {{requestCount} 4K förfrågningar}}",
"components.NotificationTypeSelector.mediarequestedDescription": "Skicka meddelanden när användare skickar in nya medieförfrågningar som kräver godkännande.",
- "components.NotificationTypeSelector.mediarequested": "Media Begärd",
+ "components.NotificationTypeSelector.mediarequested": "Begäran väntar på godkännande",
"components.NotificationTypeSelector.mediafailedDescription": "Skicka meddelanden när medieförfrågningar inte kan läggas till i Radarr eller Sonarr.",
- "components.NotificationTypeSelector.mediafailed": "Media Misslyckades",
+ "components.NotificationTypeSelector.mediafailed": "Behandlingen av begäran misslyckades",
"components.NotificationTypeSelector.mediaavailableDescription": "Skicka meddelanden när medieförfrågningar blir tillgängliga.",
- "components.NotificationTypeSelector.mediaavailable": "Media Tillgängligt",
+ "components.NotificationTypeSelector.mediaavailable": "Begäran tillgänglig",
"components.NotificationTypeSelector.mediaapprovedDescription": "Skicka meddelanden när medieförfrågningar godkänns manuellt.",
- "components.NotificationTypeSelector.mediaapproved": "Media Godkänt",
+ "components.NotificationTypeSelector.mediaapproved": "Begäran godkänd",
"components.MovieDetails.viewfullcrew": "Visa Filmteam",
"components.MovieDetails.MovieCrew.fullcrew": "Filmteam",
"components.Settings.csrfProtection": "Aktivera CSRF-skydd",
@@ -363,7 +363,7 @@
"components.PermissionEdit.advancedrequest": "Avancerade Förfrågningar",
"components.PermissionEdit.admin": "Admin",
"components.NotificationTypeSelector.mediadeclinedDescription": "Skicka meddelanden när medieförfrågningar avvisas.",
- "components.NotificationTypeSelector.mediadeclined": "Media Avböjd",
+ "components.NotificationTypeSelector.mediadeclined": "Begäran nekad",
"components.MovieDetails.play4konplex": "Spela upp i 4K på Plex",
"components.MovieDetails.playonplex": "Spela upp på Plex",
"components.MovieDetails.markavailable": "Markera som Tillgänglig",
@@ -573,7 +573,7 @@
"components.PermissionEdit.autoapprove4kDescription": "Bevilja automatiskt godkännande för alla 4K-medieförfrågningar.",
"components.PermissionEdit.autoapprove4k": "Automatiskt godkännande av 4K",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "Skicka meddelanden när användare skickar in nya medieförfrågningar som godkänns automatiskt.",
- "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatiskt Godkänd",
+ "components.NotificationTypeSelector.mediaAutoApproved": "Begäran automatiskt godkänd",
"components.Layout.UserDropdown.settings": "Inställningar",
"components.Layout.UserDropdown.myprofile": "Profil",
"components.Discover.upcomingtv": "Kommande serier",
@@ -998,5 +998,6 @@
"components.Settings.RadarrModal.inCinemas": "På Bio",
"components.Settings.RadarrModal.released": "Släppt",
"components.Settings.RadarrModal.announced": "Annonserat",
- "components.TvDetails.productioncountries": "Produktions{countryCount, plural, one {land} other {länder}}"
+ "components.TvDetails.productioncountries": "Produktions{countryCount, plural, one {land} other {länder}}",
+ "components.Settings.Notifications.enableMentions": "Aktivera omnämnande"
}
diff --git a/src/i18n/locale/zh_Hans.json b/src/i18n/locale/zh_Hans.json
index 36b37417..f20d587a 100644
--- a/src/i18n/locale/zh_Hans.json
+++ b/src/i18n/locale/zh_Hans.json
@@ -226,15 +226,15 @@
"components.NotificationTypeSelector.usermediaAutoApprovedDescription": "当其他用户提交自动批准的请求时得到通知。",
"components.NotificationTypeSelector.notificationTypes": "通知类型",
"components.NotificationTypeSelector.mediarequestedDescription": "当用户提交需要管理员批准的请求时发送通知。",
- "components.NotificationTypeSelector.mediarequested": "请求提交",
+ "components.NotificationTypeSelector.mediarequested": "请求待批准",
"components.NotificationTypeSelector.mediafailedDescription": "当 Radarr 或 Sonarr 处理请求失败时发送通知。",
- "components.NotificationTypeSelector.mediafailed": "请求失败",
+ "components.NotificationTypeSelector.mediafailed": "请求处理失败",
"components.NotificationTypeSelector.mediadeclinedDescription": "当请求拒被絕时发送通知。",
- "components.NotificationTypeSelector.mediadeclined": "请求拒绝",
+ "components.NotificationTypeSelector.mediadeclined": "请求被拒",
"components.NotificationTypeSelector.mediaavailableDescription": "当请求的媒体可观看时发送通知。",
- "components.NotificationTypeSelector.mediaavailable": "媒体可观看",
+ "components.NotificationTypeSelector.mediaavailable": "请求可用",
"components.NotificationTypeSelector.mediaapprovedDescription": "当请求被手动批准时发送通知。",
- "components.NotificationTypeSelector.mediaapproved": "请求批准",
+ "components.NotificationTypeSelector.mediaapproved": "请求已获批准",
"components.NotificationTypeSelector.mediaAutoApprovedDescription": "当用户提交自动批准的请求时发送通知。",
"components.NotificationTypeSelector.mediaAutoApproved": "请求自动批准",
"components.MovieDetails.watchtrailer": "观看预告片",
diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json
index f960c3e3..f7fd5237 100644
--- a/src/i18n/locale/zh_Hant.json
+++ b/src/i18n/locale/zh_Hant.json
@@ -67,7 +67,7 @@
"components.MovieDetails.cast": "演員陣容",
"components.TvDetails.cast": "演員陣容",
"i18n.available": "可觀看",
- "i18n.approved": "已批准",
+ "i18n.approved": "獲批准",
"i18n.approve": "批准",
"components.TvDetails.anime": "動漫",
"i18n.processing": "處理中",
@@ -104,11 +104,11 @@
"components.RequestButton.approve4krequests": "批准{requestCount, plural, one { 4K 請求} other { {requestCount} 個 4K 請求}}",
"components.RequestBlock.seasons": "季數",
"components.PersonDetails.crewmember": "製作群成員",
- "components.NotificationTypeSelector.mediarequested": "請求提出",
- "components.NotificationTypeSelector.mediafailed": "請求失敗",
- "components.NotificationTypeSelector.mediaapproved": "請求批准",
+ "components.NotificationTypeSelector.mediarequested": "請求待批准",
+ "components.NotificationTypeSelector.mediafailed": "請求處理失敗",
+ "components.NotificationTypeSelector.mediaapproved": "請求獲批准",
"components.NotificationTypeSelector.mediaavailableDescription": "當請求的媒體可觀看時發送通知。",
- "components.NotificationTypeSelector.mediaapprovedDescription": "當請求被手動批准時發送通知。",
+ "components.NotificationTypeSelector.mediaapprovedDescription": "當請求獲手動批准時發送通知。",
"components.NotificationTypeSelector.mediaavailable": "請求可觀看",
"components.MovieDetails.watchtrailer": "觀看預告片",
"components.MovieDetails.viewfullcrew": "檢視完整製作群",
@@ -257,7 +257,7 @@
"components.RequestModal.selectseason": "請選擇季數",
"components.RequestModal.requesttitle": "為 {title} 提出請求",
"components.RequestModal.requestseasons": "提出請求",
- "components.RequestModal.requestadmin": "此請求將自動被批准。",
+ "components.RequestModal.requestadmin": "此請求將自動獲批准。",
"components.RequestModal.requestSuccess": "為 {title} 提出請求成功!",
"components.RequestModal.requestCancel": "{title} 的請求已被取消。",
"components.RequestModal.request4ktitle": "為 {title} 提出 4K 請求",
@@ -848,8 +848,8 @@
"components.NotificationTypeSelector.usermediafailedDescription": "當 Radarr 或 Sonarr 處理請求失敗時取得通知。",
"components.NotificationTypeSelector.usermediadeclinedDescription": "當您的請求被拒絕時取得通知。",
"components.NotificationTypeSelector.usermediaavailableDescription": "當您請求的媒體可觀看時取得通知。",
- "components.NotificationTypeSelector.usermediaapprovedDescription": "當您的請求被手動批准時取得通知。",
- "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "當其他使用者提出自動批准的請求時取得通知。",
+ "components.NotificationTypeSelector.usermediaapprovedDescription": "當您的請求獲批准時取得通知。",
+ "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "當其他使用者提出自動獲批准的請求時取得通知。",
"components.Settings.SettingsAbout.betawarning": "這是測試版軟體,有些功能可能損壞或不穩定。請在 GitHub 上回報問題!",
"components.Layout.LanguagePicker.displaylanguage": "顯示語言",
"components.MovieDetails.showmore": "顯示更多",
@@ -994,9 +994,10 @@
"components.RequestModal.selectmovies": "請選擇電影",
"components.TvDetails.productioncountries": "製作國家",
"components.IssueDetails.commentplaceholder": "發表評論…",
- "components.RequestModal.requestApproved": "{title} 的請求已被批准。",
+ "components.RequestModal.requestApproved": "{title} 的請求已獲批准。",
"components.RequestModal.approve": "批准請求",
"components.Settings.RadarrModal.inCinemas": "已上映",
"components.Settings.RadarrModal.released": "已發佈",
- "components.Settings.RadarrModal.announced": "已公佈"
+ "components.Settings.RadarrModal.announced": "已公佈",
+ "components.Settings.Notifications.enableMentions": "允許提及"
}
From e0b6abe4796f5a324c0ff78cff317fcaead671f1 Mon Sep 17 00:00:00 2001
From: Sean Chambers
Date: Thu, 13 Jan 2022 19:04:25 -0600
Subject: [PATCH 10/76] feat(notif): add Gotify agent (#2196)
* feat(notifications): adds gotify notifications
adds new settings screen for gotify notifications including url, token and types settings
fix #2183
* feat(notif): add Gotify agent
addresses PR comments, runs i18n:extract
fix #2183
* reword validationTokenRequired
change wording to indicate presence, not validity
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
* feat(notifications): gotify notifications fix
applies changes from #2077 in which Yup validation was failing for types
fix #2183
* feat(notifications): adds gotify notifications
adds new settings screen for gotify notifications including url, token and types settings
fix #2183
* feat(notif): add Gotify agent
addresses PR comments, runs i18n:extract
fix #2183
* reword validationTokenRequired
change wording to indicate presence, not validity
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
* feat(notifications): gotify notifications fix
applies changes from #2077 in which Yup validation was failing for types
fix #2183
* feat(notifications): incorporate issue feature into gotify notifications
* feat(notifications): adds gotify notifications
adds new settings screen for gotify notifications including url, token and types settings
fix #2183
* feat(notif): add Gotify agent
addresses PR comments, runs i18n:extract
fix #2183
* reword validationTokenRequired
change wording to indicate presence, not validity
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
* feat: add missing ts field
include notifyAdmin in test notification endpoint
* feat: apply formatting/line break items
add addition line break before conditional, change ordering of notifyAdmin/notifyUser in test
endpoint
* feat: remove duplicated endpoints
during rebase, notification endpoints were duplicated upon rebasing. remove duplicate routes
* feat: correct linting quirks
* feat: formatting improvements
* feat(gotify): refactor axios post to leverage 'getNotificationPayload'
Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
---
docs/using-overseerr/notifications/README.md | 1 +
docs/using-overseerr/notifications/gotify.md | 15 +
overseerr-api.yml | 62 +++++
server/index.ts | 2 +
server/lib/notifications/agents/gotify.ts | 148 ++++++++++
server/lib/settings.ts | 17 ++
server/routes/settings/notifications.ts | 43 +++
src/assets/extlogos/gotify.svg | 1 +
.../NotificationsGotify/index.tsx | 256 ++++++++++++++++++
.../Settings/SettingsNotifications.tsx | 12 +
src/i18n/locale/en.json | 12 +
src/pages/settings/notifications/gotify.tsx | 20 ++
12 files changed, 589 insertions(+)
create mode 100644 docs/using-overseerr/notifications/gotify.md
create mode 100644 server/lib/notifications/agents/gotify.ts
create mode 100644 src/assets/extlogos/gotify.svg
create mode 100644 src/components/Settings/Notifications/NotificationsGotify/index.tsx
create mode 100644 src/pages/settings/notifications/gotify.tsx
diff --git a/docs/using-overseerr/notifications/README.md b/docs/using-overseerr/notifications/README.md
index c894b0b2..2bff1388 100644
--- a/docs/using-overseerr/notifications/README.md
+++ b/docs/using-overseerr/notifications/README.md
@@ -7,6 +7,7 @@ Overseerr currently supports the following notification agents:
- [Email](./email.md)
- [Web Push](./webpush.md)
- [Discord](./discord.md)
+- [Gotify](./gotify.md)
- [LunaSea](./lunasea.md)
- [Pushbullet](./pushbullet.md)
- [Pushover](./pushover.md)
diff --git a/docs/using-overseerr/notifications/gotify.md b/docs/using-overseerr/notifications/gotify.md
new file mode 100644
index 00000000..16e7cd59
--- /dev/null
+++ b/docs/using-overseerr/notifications/gotify.md
@@ -0,0 +1,15 @@
+# Gotify
+
+## Configuration
+
+### Server URL
+
+Set this to the URL of your Gotify server.
+
+### Application Token
+
+Add an application to your Gotify server, and set this field to the generated application token.
+
+{% hint style="info" %}
+Please refer to the [Gotify API documentation](https://gotify.net/docs) for more details on configuring these notifications.
+{% endhint %}
diff --git a/overseerr-api.yml b/overseerr-api.yml
index 213ef1d7..211f029c 100644
--- a/overseerr-api.yml
+++ b/overseerr-api.yml
@@ -1231,6 +1231,22 @@ components:
type: string
userToken:
type: string
+ GotifySettings:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ example: false
+ types:
+ type: number
+ example: 2
+ options:
+ type: object
+ properties:
+ url:
+ type: string
+ token:
+ type: string
LunaSeaSettings:
type: object
properties:
@@ -2681,6 +2697,52 @@ paths:
responses:
'204':
description: Test notification attempted
+ /settings/notifications/gotify:
+ get:
+ summary: Get Gotify notification settings
+ description: Returns current Gotify notification settings in a JSON object.
+ tags:
+ - settings
+ responses:
+ '200':
+ description: Returned Gotify settings
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GotifySettings'
+ post:
+ summary: Update Gotify notification settings
+ description: Update Gotify notification settings with the provided values.
+ tags:
+ - settings
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GotifySettings'
+ responses:
+ '200':
+ description: 'Values were sucessfully updated'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GotifySettings'
+ /settings/notifications/gotify/test:
+ post:
+ summary: Test Gotify settings
+ description: Sends a test notification to the Gotify agent.
+ tags:
+ - settings
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GotifySettings'
+ responses:
+ '204':
+ description: Test notification attempted
/settings/notifications/slack:
get:
summary: Get Slack notification settings
diff --git a/server/index.ts b/server/index.ts
index 24c007f2..c8053012 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -17,6 +17,7 @@ import { startJobs } from './job/schedule';
import notificationManager from './lib/notifications';
import DiscordAgent from './lib/notifications/agents/discord';
import EmailAgent from './lib/notifications/agents/email';
+import GotifyAgent from './lib/notifications/agents/gotify';
import LunaSeaAgent from './lib/notifications/agents/lunasea';
import PushbulletAgent from './lib/notifications/agents/pushbullet';
import PushoverAgent from './lib/notifications/agents/pushover';
@@ -76,6 +77,7 @@ app
notificationManager.registerAgents([
new DiscordAgent(),
new EmailAgent(),
+ new GotifyAgent(),
new LunaSeaAgent(),
new PushbulletAgent(),
new PushoverAgent(),
diff --git a/server/lib/notifications/agents/gotify.ts b/server/lib/notifications/agents/gotify.ts
new file mode 100644
index 00000000..ecd54ce7
--- /dev/null
+++ b/server/lib/notifications/agents/gotify.ts
@@ -0,0 +1,148 @@
+import axios from 'axios';
+import { hasNotificationType, Notification } from '..';
+import { IssueStatus, IssueTypeName } from '../../../constants/issue';
+import logger from '../../../logger';
+import { getSettings, NotificationAgentGotify } from '../../settings';
+import { BaseAgent, NotificationAgent, NotificationPayload } from './agent';
+
+interface GotifyPayload {
+ title: string;
+ message: string;
+ priority: number;
+ extras: any;
+}
+
+class GotifyAgent
+ extends BaseAgent
+ implements NotificationAgent
+{
+ protected getSettings(): NotificationAgentGotify {
+ if (this.settings) {
+ return this.settings;
+ }
+
+ const settings = getSettings();
+
+ return settings.notifications.agents.gotify;
+ }
+
+ public shouldSend(): boolean {
+ const settings = this.getSettings();
+
+ if (settings.enabled && settings.options.url && settings.options.token) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private getNotificationPayload(
+ type: Notification,
+ payload: NotificationPayload
+ ): GotifyPayload {
+ const { applicationUrl, applicationTitle } = getSettings().main;
+ let priority = 0;
+
+ const title = payload.event
+ ? `${payload.event} - ${payload.subject}`
+ : payload.subject;
+ let message = payload.message ?? '';
+
+ if (payload.request) {
+ message += `\n\nRequested By: ${payload.request.requestedBy.displayName}`;
+
+ let status = '';
+ switch (type) {
+ case Notification.MEDIA_PENDING:
+ status = 'Pending Approval';
+ break;
+ case Notification.MEDIA_APPROVED:
+ case Notification.MEDIA_AUTO_APPROVED:
+ status = 'Processing';
+ break;
+ case Notification.MEDIA_AVAILABLE:
+ status = 'Available';
+ break;
+ case Notification.MEDIA_DECLINED:
+ status = 'Declined';
+ break;
+ case Notification.MEDIA_FAILED:
+ status = 'Failed';
+ break;
+ }
+
+ if (status) {
+ message += `\nRequest Status: ${status}`;
+ }
+ } else if (payload.comment) {
+ message += `\nComment from ${payload.comment.user.displayName}:\n${payload.comment.message}`;
+ } else if (payload.issue) {
+ message += `\n\nReported By: ${payload.issue.createdBy.displayName}`;
+ message += `\nIssue Type: ${IssueTypeName[payload.issue.issueType]}`;
+ message += `\nIssue Status: ${
+ payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved'
+ }`;
+
+ if (type == Notification.ISSUE_CREATED) {
+ priority = 1;
+ }
+ }
+
+ for (const extra of payload.extra ?? []) {
+ message += `\n\n**${extra.name}**\n${extra.value}`;
+ }
+
+ if (applicationUrl && payload.media) {
+ const actionUrl = `${applicationUrl}/${payload.media.mediaType}/${payload.media.tmdbId}`;
+ message += `\n\nOpen in ${applicationTitle}(${actionUrl})`;
+ }
+
+ return {
+ extras: {
+ 'client::display': {
+ contentType: 'text/markdown',
+ },
+ },
+ title,
+ message,
+ priority,
+ };
+ }
+
+ public async send(
+ type: Notification,
+ payload: NotificationPayload
+ ): Promise {
+ const settings = this.getSettings();
+
+ if (!hasNotificationType(type, settings.types ?? 0)) {
+ return true;
+ }
+
+ logger.debug('Sending Gotify notification', {
+ label: 'Notifications',
+ type: Notification[type],
+ subject: payload.subject,
+ });
+ try {
+ const endpoint = `${settings.options.url}/message?token=${settings.options.token}`;
+ const notificationPayload = this.getNotificationPayload(type, payload);
+
+ await axios.post(endpoint, notificationPayload);
+
+ return true;
+ } catch (e) {
+ logger.error('Error sending Gotify notification', {
+ label: 'Notifications',
+ type: Notification[type],
+ subject: payload.subject,
+ errorMessage: e.message,
+ response: e.response?.data,
+ });
+
+ return false;
+ }
+ }
+}
+
+export default GotifyAgent;
diff --git a/server/lib/settings.ts b/server/lib/settings.ts
index eb50e25b..74d13e53 100644
--- a/server/lib/settings.ts
+++ b/server/lib/settings.ts
@@ -189,9 +189,17 @@ export interface NotificationAgentWebhook extends NotificationAgentConfig {
};
}
+export interface NotificationAgentGotify extends NotificationAgentConfig {
+ options: {
+ url: string;
+ token: string;
+ };
+}
+
export enum NotificationAgentKey {
DISCORD = 'discord',
EMAIL = 'email',
+ GOTIFY = 'gotify',
PUSHBULLET = 'pushbullet',
PUSHOVER = 'pushover',
SLACK = 'slack',
@@ -203,6 +211,7 @@ export enum NotificationAgentKey {
interface NotificationAgents {
discord: NotificationAgentDiscord;
email: NotificationAgentEmail;
+ gotify: NotificationAgentGotify;
lunasea: NotificationAgentLunaSea;
pushbullet: NotificationAgentPushbullet;
pushover: NotificationAgentPushover;
@@ -359,6 +368,14 @@ class Settings {
enabled: false,
options: {},
},
+ gotify: {
+ enabled: false,
+ types: 0,
+ options: {
+ url: '',
+ token: '',
+ },
+ },
},
},
jobs: {
diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts
index d98debb7..5a337237 100644
--- a/server/routes/settings/notifications.ts
+++ b/server/routes/settings/notifications.ts
@@ -4,6 +4,7 @@ import { Notification } from '../../lib/notifications';
import { NotificationAgent } from '../../lib/notifications/agents/agent';
import DiscordAgent from '../../lib/notifications/agents/discord';
import EmailAgent from '../../lib/notifications/agents/email';
+import GotifyAgent from '../../lib/notifications/agents/gotify';
import LunaSeaAgent from '../../lib/notifications/agents/lunasea';
import PushbulletAgent from '../../lib/notifications/agents/pushbullet';
import PushoverAgent from '../../lib/notifications/agents/pushover';
@@ -377,4 +378,46 @@ notificationRoutes.post('/lunasea/test', async (req, res, next) => {
}
});
+notificationRoutes.get('/gotify', (_req, res) => {
+ const settings = getSettings();
+
+ res.status(200).json(settings.notifications.agents.gotify);
+});
+
+notificationRoutes.post('/gotify', (req, rest) => {
+ const settings = getSettings();
+
+ settings.notifications.agents.gotify = req.body;
+ settings.save();
+
+ rest.status(200).json(settings.notifications.agents.gotify);
+});
+
+notificationRoutes.post('/gotify/test', async (req, rest, next) => {
+ if (!req.user) {
+ return next({
+ status: 500,
+ message: 'User information is missing from request',
+ });
+ }
+
+ const gotifyAgent = new GotifyAgent(req.body);
+ if (
+ await gotifyAgent.send(Notification.TEST_NOTIFICATION, {
+ notifyAdmin: false,
+ notifyUser: req.user,
+ subject: 'Test Notification',
+ message:
+ 'This is a test notification! Check check, 1, 2, 3. Are we coming in clear?',
+ })
+ ) {
+ return rest.status(204).send();
+ } else {
+ return next({
+ status: 500,
+ message: 'Failed to send Gotify notification.',
+ });
+ }
+});
+
export default notificationRoutes;
diff --git a/src/assets/extlogos/gotify.svg b/src/assets/extlogos/gotify.svg
new file mode 100644
index 00000000..6d078992
--- /dev/null
+++ b/src/assets/extlogos/gotify.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/Settings/Notifications/NotificationsGotify/index.tsx b/src/components/Settings/Notifications/NotificationsGotify/index.tsx
new file mode 100644
index 00000000..214cf973
--- /dev/null
+++ b/src/components/Settings/Notifications/NotificationsGotify/index.tsx
@@ -0,0 +1,256 @@
+import { BeakerIcon, SaveIcon } from '@heroicons/react/solid';
+import axios from 'axios';
+import { Field, Form, Formik } from 'formik';
+import React, { useState } from 'react';
+import { defineMessages, useIntl } from 'react-intl';
+import { useToasts } from 'react-toast-notifications';
+import useSWR from 'swr';
+import * as Yup from 'yup';
+import globalMessages from '../../../../i18n/globalMessages';
+import Button from '../../../Common/Button';
+import LoadingSpinner from '../../../Common/LoadingSpinner';
+import NotificationTypeSelector from '../../../NotificationTypeSelector';
+
+const messages = defineMessages({
+ agentenabled: 'Enable Agent',
+ url: 'Server URL',
+ token: 'Application Token',
+ validationUrlRequired: 'You must provide a valid URL',
+ validationUrlTrailingSlash: 'URL must not end in a trailing slash',
+ validationTokenRequired: 'You must provide an application token',
+ gotifysettingssaved: 'Gotify notification settings saved successfully!',
+ gotifysettingsfailed: 'Gotify notification settings failed to save.',
+ toastGotifyTestSending: 'Sending Gotify test notification…',
+ toastGotifyTestSuccess: 'Gotify test notification sent!',
+ toastGotifyTestFailed: 'Gotify test notification failed to send.',
+ validationTypes: 'You must select at least one notification type',
+});
+
+const NotificationsGotify: React.FC = () => {
+ const intl = useIntl();
+ const { addToast, removeToast } = useToasts();
+ const [isTesting, setIsTesting] = useState(false);
+ const { data, error, revalidate } = useSWR(
+ '/api/v1/settings/notifications/gotify'
+ );
+
+ const NotificationsGotifySchema = Yup.object().shape({
+ url: Yup.string()
+ .when('enabled', {
+ is: true,
+ then: Yup.string()
+ .nullable()
+ .required(intl.formatMessage(messages.validationUrlRequired)),
+ otherwise: Yup.string().nullable(),
+ })
+ .matches(
+ // eslint-disable-next-line no-useless-escape
+ /^(https?:)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,
+ intl.formatMessage(messages.validationUrlRequired)
+ )
+ .test(
+ 'no-trailing-slash',
+ intl.formatMessage(messages.validationUrlTrailingSlash),
+ (value) => !value || !value.endsWith('/')
+ ),
+ token: Yup.string().when('enabled', {
+ is: true,
+ then: Yup.string()
+ .nullable()
+ .required(intl.formatMessage(messages.validationTokenRequired)),
+ otherwise: Yup.string().nullable(),
+ }),
+ });
+
+ if (!data && !error) {
+ return ;
+ }
+
+ return (
+ {
+ try {
+ await axios.post('/api/v1/settings/notifications/gotify', {
+ enabled: values.enabled,
+ types: values.types,
+ options: {
+ url: values.url,
+ token: values.token,
+ },
+ });
+ addToast(intl.formatMessage(messages.gotifysettingssaved), {
+ appearance: 'success',
+ autoDismiss: true,
+ });
+ } catch (e) {
+ addToast(intl.formatMessage(messages.gotifysettingsfailed), {
+ appearance: 'error',
+ autoDismiss: true,
+ });
+ } finally {
+ revalidate();
+ }
+ }}
+ >
+ {({
+ errors,
+ touched,
+ isSubmitting,
+ values,
+ isValid,
+ setFieldValue,
+ setFieldTouched,
+ }) => {
+ const testSettings = async () => {
+ setIsTesting(true);
+ let toastId: string | undefined;
+ try {
+ addToast(
+ intl.formatMessage(messages.toastGotifyTestSending),
+ {
+ autoDsmiss: false,
+ appearance: 'info',
+ },
+ (id) => {
+ toastId = id;
+ }
+ );
+ await axios.post('/api/v1/settings/notifications/gotify/test', {
+ enabled: true,
+ types: values.types,
+ options: {
+ url: values.url,
+ token: values.token,
+ },
+ });
+
+ if (toastId) {
+ removeToast(toastId);
+ }
+ addToast(intl.formatMessage(messages.toastGotifyTestSuccess), {
+ autoDismiss: true,
+ appearance: 'success',
+ });
+ } catch (e) {
+ if (toastId) {
+ removeToast(toastId);
+ }
+ addToast(intl.formatMessage(messages.toastGotifyTestFailed), {
+ autoDismiss: true,
+ appearance: 'error',
+ });
+ } finally {
+ setIsTesting(false);
+ }
+ };
+
+ return (
+
+ );
+ }}
+
+ );
+};
+
+export default NotificationsGotify;
diff --git a/src/components/Settings/SettingsNotifications.tsx b/src/components/Settings/SettingsNotifications.tsx
index 329ec679..fda48aea 100644
--- a/src/components/Settings/SettingsNotifications.tsx
+++ b/src/components/Settings/SettingsNotifications.tsx
@@ -2,6 +2,7 @@ import { CloudIcon, LightningBoltIcon, MailIcon } from '@heroicons/react/solid';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import DiscordLogo from '../../assets/extlogos/discord.svg';
+import GotifyLogo from '../../assets/extlogos/gotify.svg';
import LunaSeaLogo from '../../assets/extlogos/lunasea.svg';
import PushbulletLogo from '../../assets/extlogos/pushbullet.svg';
import PushoverLogo from '../../assets/extlogos/pushover.svg';
@@ -58,6 +59,17 @@ const SettingsNotifications: React.FC = ({ children }) => {
route: '/settings/notifications/discord',
regex: /^\/settings\/notifications\/discord/,
},
+ {
+ text: 'Gotify',
+ content: (
+
+
+ Gotify
+
+ ),
+ route: '/settings/notifications/gotify',
+ regex: /^\/settings\/notifications\/gotify/,
+ },
{
text: 'LunaSea',
content: (
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index 47f5f1c8..66f85895 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -365,6 +365,18 @@
"components.ResetPassword.validationpasswordrequired": "You must provide a password",
"components.Search.search": "Search",
"components.Search.searchresults": "Search Results",
+ "components.Settings.Notifications.NotificationsGotify.agentenabled": "Enable Agent",
+ "components.Settings.Notifications.NotificationsGotify.gotifysettingsfailed": "Gotify notification settings failed to save.",
+ "components.Settings.Notifications.NotificationsGotify.gotifysettingssaved": "Gotify notification settings saved successfully!",
+ "components.Settings.Notifications.NotificationsGotify.toastGotifyTestFailed": "Gotify test notification failed to send.",
+ "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSending": "Sending Gotify test notification…",
+ "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSuccess": "Gotify test notification sent!",
+ "components.Settings.Notifications.NotificationsGotify.token": "Application Token",
+ "components.Settings.Notifications.NotificationsGotify.url": "Server URL",
+ "components.Settings.Notifications.NotificationsGotify.validationTokenRequired": "You must provide a valid application token",
+ "components.Settings.Notifications.NotificationsGotify.validationTypes": "You must select at least one notification type",
+ "components.Settings.Notifications.NotificationsGotify.validationUrlRequired": "You must provide a valid URL",
+ "components.Settings.Notifications.NotificationsGotify.validationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsLunaSea.profileName": "Profile Name",
"components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Only required if not using the default profile",
diff --git a/src/pages/settings/notifications/gotify.tsx b/src/pages/settings/notifications/gotify.tsx
new file mode 100644
index 00000000..a47c9c28
--- /dev/null
+++ b/src/pages/settings/notifications/gotify.tsx
@@ -0,0 +1,20 @@
+import { NextPage } from 'next';
+import React from 'react';
+import NotificationsGotify from '../../../components/Settings/Notifications/NotificationsGotify';
+import SettingsLayout from '../../../components/Settings/SettingsLayout';
+import SettingsNotifications from '../../../components/Settings/SettingsNotifications';
+import useRouteGuard from '../../../hooks/useRouteGuard';
+import { Permission } from '../../../hooks/useUser';
+
+const NotificationsPage: NextPage = () => {
+ useRouteGuard(Permission.MANAGE_SETTINGS);
+ return (
+
+
+
+
+
+ );
+};
+
+export default NotificationsPage;
From b31cdbf074d5dbecbbf6da135a9b686aea9e3c0e Mon Sep 17 00:00:00 2001
From: Danshil Kokil Mungur
Date: Fri, 14 Jan 2022 11:52:10 +0400
Subject: [PATCH 11/76] feat(search): search by id (#2082)
* feat(search): search by id
This adds the ability to search by ID (starting with TMDb ID).
Since there doesn't seem to be way of searching across movies, tv and persons,
I have to search through all 3 and use the first one in the order: movie -> tv -> person
Searching by ID is triggered using a 'prefix' just like in the *arrs.
* fix: missed some refactoring
* feat(search): use locale language
* feat(search): search using imdb id
* feat(search): search using tvdb id
* fix: alias type import
* fix: missed some refactoring
* fix(search): account for id being a string
* feat(search): account for movies/tvs/persons with the same id
* feat(search): remove non-null assertion
Co-authored-by: Ryan Cohen
---
overseerr-api.yml | 4 +-
server/api/themoviedb/index.ts | 6 +-
server/api/themoviedb/interfaces.ts | 5 +-
server/lib/search.ts | 169 +++++++++++++++++++++++++
server/models/Person.ts | 8 +-
server/models/Search.ts | 54 ++++++++
server/routes/search.ts | 28 +++-
server/utils/typeHelpers.ts | 17 ++-
src/components/PersonDetails/index.tsx | 4 +-
9 files changed, 275 insertions(+), 20 deletions(-)
create mode 100644 server/lib/search.ts
diff --git a/overseerr-api.yml b/overseerr-api.yml
index 211f029c..e3fc90e3 100644
--- a/overseerr-api.yml
+++ b/overseerr-api.yml
@@ -1326,7 +1326,7 @@ components:
running:
type: boolean
example: false
- PersonDetail:
+ PersonDetails:
type: object
properties:
id:
@@ -4871,7 +4871,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/PersonDetail'
+ $ref: '#/components/schemas/PersonDetails'
/person/{personId}/combined_credits:
get:
diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts
index ddc18059..d565c35a 100644
--- a/server/api/themoviedb/index.ts
+++ b/server/api/themoviedb/index.ts
@@ -10,7 +10,7 @@ import {
TmdbMovieDetails,
TmdbNetwork,
TmdbPersonCombinedCredits,
- TmdbPersonDetail,
+ TmdbPersonDetails,
TmdbProductionCompany,
TmdbRegion,
TmdbSearchMovieResponse,
@@ -122,9 +122,9 @@ class TheMovieDb extends ExternalAPI {
}: {
personId: number;
language?: string;
- }): Promise => {
+ }): Promise => {
try {
- const data = await this.get(`/person/${personId}`, {
+ const data = await this.get(`/person/${personId}`, {
params: { language },
});
diff --git a/server/api/themoviedb/interfaces.ts b/server/api/themoviedb/interfaces.ts
index 7892fe46..2282fe05 100644
--- a/server/api/themoviedb/interfaces.ts
+++ b/server/api/themoviedb/interfaces.ts
@@ -67,6 +67,7 @@ export interface TmdbUpcomingMoviesResponse extends TmdbPaginatedResponse {
export interface TmdbExternalIdResponse {
movie_results: TmdbMovieResult[];
tv_results: TmdbTvResult[];
+ person_results: TmdbPersonResult[];
}
export interface TmdbCreditCast {
@@ -315,7 +316,7 @@ export interface TmdbKeyword {
name: string;
}
-export interface TmdbPersonDetail {
+export interface TmdbPersonDetails {
id: number;
name: string;
birthday: string;
@@ -324,7 +325,7 @@ export interface TmdbPersonDetail {
also_known_as?: string[];
gender: number;
biography: string;
- popularity: string;
+ popularity: number;
place_of_birth?: string;
profile_path?: string;
adult: boolean;
diff --git a/server/lib/search.ts b/server/lib/search.ts
new file mode 100644
index 00000000..b66079c8
--- /dev/null
+++ b/server/lib/search.ts
@@ -0,0 +1,169 @@
+import TheMovieDb from '../api/themoviedb';
+import {
+ TmdbMovieDetails,
+ TmdbMovieResult,
+ TmdbPersonDetails,
+ TmdbPersonResult,
+ TmdbSearchMultiResponse,
+ TmdbTvDetails,
+ TmdbTvResult,
+} from '../api/themoviedb/interfaces';
+import {
+ mapMovieDetailsToResult,
+ mapPersonDetailsToResult,
+ mapTvDetailsToResult,
+} from '../models/Search';
+import { isMovieDetails, isTvDetails } from '../utils/typeHelpers';
+
+type SearchProviderId = 'TMDb' | 'IMDb' | 'TVDB';
+
+interface SearchProvider {
+ id: SearchProviderId;
+ pattern: RegExp;
+ search: (id: string, language?: string) => Promise;
+}
+
+const searchProviders: SearchProvider[] = [];
+
+export const findSearchProvider = (
+ query: string
+): SearchProvider | undefined => {
+ return searchProviders.find((provider) => provider.pattern.test(query));
+};
+
+searchProviders.push({
+ id: 'TMDb',
+ pattern: new RegExp(/(?<=tmdb:)\d+/),
+ search: async (
+ id: string,
+ language?: string
+ ): Promise => {
+ const tmdb = new TheMovieDb();
+
+ const moviePromise = tmdb.getMovie({ movieId: parseInt(id), language });
+ const tvShowPromise = tmdb.getTvShow({ tvId: parseInt(id), language });
+ const personPromise = tmdb.getPerson({ personId: parseInt(id), language });
+
+ const responses = await Promise.allSettled([
+ moviePromise,
+ tvShowPromise,
+ personPromise,
+ ]);
+
+ const successfulResponses = responses.filter(
+ (r) => r.status === 'fulfilled'
+ ) as
+ | (
+ | PromiseFulfilledResult
+ | PromiseFulfilledResult
+ | PromiseFulfilledResult
+ )[];
+
+ const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = [];
+
+ if (successfulResponses.length) {
+ results.push(
+ ...successfulResponses.map((r) => {
+ if (isMovieDetails(r.value)) {
+ return mapMovieDetailsToResult(r.value);
+ } else if (isTvDetails(r.value)) {
+ return mapTvDetailsToResult(r.value);
+ } else {
+ return mapPersonDetailsToResult(r.value);
+ }
+ })
+ );
+ }
+
+ return {
+ page: 1,
+ total_pages: 1,
+ total_results: results.length,
+ results,
+ };
+ },
+});
+
+searchProviders.push({
+ id: 'IMDb',
+ pattern: new RegExp(/(?<=imdb:)(tt|nm)\d+/),
+ search: async (
+ id: string,
+ language?: string
+ ): Promise => {
+ const tmdb = new TheMovieDb();
+
+ const responses = await tmdb.getByExternalId({
+ externalId: id,
+ type: 'imdb',
+ language,
+ });
+
+ const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = [];
+
+ // set the media_type here since searching by external id doesn't return it
+ results.push(
+ ...(responses.movie_results.map((movie) => ({
+ ...movie,
+ media_type: 'movie',
+ })) as TmdbMovieResult[]),
+ ...(responses.tv_results.map((tv) => ({
+ ...tv,
+ media_type: 'tv',
+ })) as TmdbTvResult[]),
+ ...(responses.person_results.map((person) => ({
+ ...person,
+ media_type: 'person',
+ })) as TmdbPersonResult[])
+ );
+
+ return {
+ page: 1,
+ total_pages: 1,
+ total_results: results.length,
+ results,
+ };
+ },
+});
+
+searchProviders.push({
+ id: 'TVDB',
+ pattern: new RegExp(/(?<=tvdb:)\d+/),
+ search: async (
+ id: string,
+ language?: string
+ ): Promise => {
+ const tmdb = new TheMovieDb();
+
+ const responses = await tmdb.getByExternalId({
+ externalId: parseInt(id),
+ type: 'tvdb',
+ language,
+ });
+
+ const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = [];
+
+ // set the media_type here since searching by external id doesn't return it
+ results.push(
+ ...(responses.movie_results.map((movie) => ({
+ ...movie,
+ media_type: 'movie',
+ })) as TmdbMovieResult[]),
+ ...(responses.tv_results.map((tv) => ({
+ ...tv,
+ media_type: 'tv',
+ })) as TmdbTvResult[]),
+ ...(responses.person_results.map((person) => ({
+ ...person,
+ media_type: 'person',
+ })) as TmdbPersonResult[])
+ );
+
+ return {
+ page: 1,
+ total_pages: 1,
+ total_results: results.length,
+ results,
+ };
+ },
+});
diff --git a/server/models/Person.ts b/server/models/Person.ts
index 14925edb..087ab1c7 100644
--- a/server/models/Person.ts
+++ b/server/models/Person.ts
@@ -1,11 +1,11 @@
import type {
TmdbPersonCreditCast,
TmdbPersonCreditCrew,
- TmdbPersonDetail,
+ TmdbPersonDetails,
} from '../api/themoviedb/interfaces';
import Media from '../entity/Media';
-export interface PersonDetail {
+export interface PersonDetails {
id: number;
name: string;
birthday: string;
@@ -14,7 +14,7 @@ export interface PersonDetail {
alsoKnownAs?: string[];
gender: number;
biography: string;
- popularity: string;
+ popularity: number;
placeOfBirth?: string;
profilePath?: string;
adult: boolean;
@@ -62,7 +62,7 @@ export interface CombinedCredit {
crew: PersonCreditCrew[];
}
-export const mapPersonDetails = (person: TmdbPersonDetail): PersonDetail => ({
+export const mapPersonDetails = (person: TmdbPersonDetails): PersonDetails => ({
id: person.id,
name: person.name,
birthday: person.birthday,
diff --git a/server/models/Search.ts b/server/models/Search.ts
index 0dab4e58..73427a37 100644
--- a/server/models/Search.ts
+++ b/server/models/Search.ts
@@ -1,6 +1,9 @@
import type {
+ TmdbMovieDetails,
TmdbMovieResult,
+ TmdbPersonDetails,
TmdbPersonResult,
+ TmdbTvDetails,
TmdbTvResult,
} from '../api/themoviedb/interfaces';
import { MediaType as MainMediaType } from '../constants/media';
@@ -140,3 +143,54 @@ export const mapSearchResults = (
return mapPersonResult(result);
}
});
+
+export const mapMovieDetailsToResult = (
+ movieDetails: TmdbMovieDetails
+): TmdbMovieResult => ({
+ id: movieDetails.id,
+ media_type: 'movie',
+ adult: movieDetails.adult,
+ genre_ids: movieDetails.genres.map((genre) => genre.id),
+ original_language: movieDetails.original_language,
+ original_title: movieDetails.original_title,
+ overview: movieDetails.overview ?? '',
+ popularity: movieDetails.popularity,
+ release_date: movieDetails.release_date,
+ title: movieDetails.title,
+ video: movieDetails.video,
+ vote_average: movieDetails.vote_average,
+ vote_count: movieDetails.vote_count,
+ backdrop_path: movieDetails.backdrop_path,
+ poster_path: movieDetails.poster_path,
+});
+
+export const mapTvDetailsToResult = (
+ tvDetails: TmdbTvDetails
+): TmdbTvResult => ({
+ id: tvDetails.id,
+ media_type: 'tv',
+ first_air_date: tvDetails.first_air_date,
+ genre_ids: tvDetails.genres.map((genre) => genre.id),
+ name: tvDetails.name,
+ origin_country: tvDetails.origin_country,
+ original_language: tvDetails.original_language,
+ original_name: tvDetails.original_name,
+ overview: tvDetails.overview,
+ popularity: tvDetails.popularity,
+ vote_average: tvDetails.vote_average,
+ vote_count: tvDetails.vote_count,
+ backdrop_path: tvDetails.backdrop_path,
+ poster_path: tvDetails.poster_path,
+});
+
+export const mapPersonDetailsToResult = (
+ personDetails: TmdbPersonDetails
+): TmdbPersonResult => ({
+ id: personDetails.id,
+ media_type: 'person',
+ name: personDetails.name,
+ popularity: personDetails.popularity,
+ adult: personDetails.adult,
+ profile_path: personDetails.profile_path,
+ known_for: [],
+});
diff --git a/server/routes/search.ts b/server/routes/search.ts
index c843e78c..466045d0 100644
--- a/server/routes/search.ts
+++ b/server/routes/search.ts
@@ -1,18 +1,34 @@
import { Router } from 'express';
import TheMovieDb from '../api/themoviedb';
+import { TmdbSearchMultiResponse } from '../api/themoviedb/interfaces';
import Media from '../entity/Media';
+import { findSearchProvider } from '../lib/search';
import { mapSearchResults } from '../models/Search';
const searchRoutes = Router();
searchRoutes.get('/', async (req, res) => {
- const tmdb = new TheMovieDb();
+ const queryString = req.query.query as string;
+ const searchProvider = findSearchProvider(queryString.toLowerCase());
+ let results: TmdbSearchMultiResponse;
- const results = await tmdb.searchMulti({
- query: req.query.query as string,
- page: Number(req.query.page),
- language: req.locale ?? (req.query.language as string),
- });
+ if (searchProvider) {
+ const [id] = queryString
+ .toLowerCase()
+ .match(searchProvider.pattern) as RegExpMatchArray;
+ results = await searchProvider.search(
+ id,
+ req.locale ?? (req.query.language as string)
+ );
+ } else {
+ const tmdb = new TheMovieDb();
+
+ results = await tmdb.searchMulti({
+ query: queryString,
+ page: Number(req.query.page),
+ language: req.locale ?? (req.query.language as string),
+ });
+ }
const media = await Media.getRelatedMedia(
results.results.map((result) => result.id)
diff --git a/server/utils/typeHelpers.ts b/server/utils/typeHelpers.ts
index ca12ddf4..04070244 100644
--- a/server/utils/typeHelpers.ts
+++ b/server/utils/typeHelpers.ts
@@ -1,7 +1,10 @@
import type {
+ TmdbMovieDetails,
TmdbMovieResult,
- TmdbTvResult,
+ TmdbPersonDetails,
TmdbPersonResult,
+ TmdbTvDetails,
+ TmdbTvResult,
} from '../api/themoviedb/interfaces';
export const isMovie = (
@@ -15,3 +18,15 @@ export const isPerson = (
): person is TmdbPersonResult => {
return (person as TmdbPersonResult).known_for !== undefined;
};
+
+export const isMovieDetails = (
+ movie: TmdbMovieDetails | TmdbTvDetails | TmdbPersonDetails
+): movie is TmdbMovieDetails => {
+ return (movie as TmdbMovieDetails).title !== undefined;
+};
+
+export const isTvDetails = (
+ tv: TmdbMovieDetails | TmdbTvDetails | TmdbPersonDetails
+): tv is TmdbTvDetails => {
+ return (tv as TmdbTvDetails).number_of_seasons !== undefined;
+};
diff --git a/src/components/PersonDetails/index.tsx b/src/components/PersonDetails/index.tsx
index 82391445..28aa0550 100644
--- a/src/components/PersonDetails/index.tsx
+++ b/src/components/PersonDetails/index.tsx
@@ -5,7 +5,7 @@ import { defineMessages, useIntl } from 'react-intl';
import TruncateMarkup from 'react-truncate-markup';
import useSWR from 'swr';
import type { PersonCombinedCreditsResponse } from '../../../server/interfaces/api/personInterfaces';
-import type { PersonDetail } from '../../../server/models/Person';
+import type { PersonDetails as PersonDetailsType } from '../../../server/models/Person';
import Ellipsis from '../../assets/ellipsis.svg';
import globalMessages from '../../i18n/globalMessages';
import Error from '../../pages/_error';
@@ -27,7 +27,7 @@ const messages = defineMessages({
const PersonDetails: React.FC = () => {
const intl = useIntl();
const router = useRouter();
- const { data, error } = useSWR(
+ const { data, error } = useSWR(
`/api/v1/person/${router.query.personId}`
);
const [showBio, setShowBio] = useState(false);
From 256163971fa6b9ea7ecc838e789eba1c8934622f Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Fri, 14 Jan 2022 16:53:29 +0900
Subject: [PATCH 12/76] docs: add schambers as a contributor for code (#2419)
[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>
---
.all-contributorsrc | 9 +++++++++
README.md | 3 ++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 1c85e63f..f19a6d13 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -620,6 +620,15 @@
"contributions": [
"translation"
]
+ },
+ {
+ "login": "schambers",
+ "name": "Sean Chambers",
+ "avatar_url": "https://avatars.githubusercontent.com/u/31563?v=4",
+ "profile": "https://github.com/schambers",
+ "contributions": [
+ "code"
+ ]
}
],
"badgeTemplate": "
-orange.svg\"/>",
diff --git a/README.md b/README.md
index 5e8e928e..7136dd72 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
-
+
@@ -160,6 +160,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
 Shaaft 🌍 |
 sr093906 🌍 |
 Nackophilz 🌍 |
+  Sean Chambers 💻 |
From 9cb97db13ced5df2dc595cd9033470b1a0750093 Mon Sep 17 00:00:00 2001
From: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
Date: Fri, 14 Jan 2022 02:32:53 -0800
Subject: [PATCH 13/76] feat(plex): selective user import (#2188)
* feat(api): allow importing of only selected Plex users
* feat(frontend): modal for importing Plex users
* feat: add alert if 'Enable New Plex Sign-In' setting is enabled
* refactor: fetch all existing Plex users in a single DB query
Co-authored-by: Ryan Cohen
---
docs/using-overseerr/users/README.md | 4 +-
overseerr-api.yml | 43 +++-
server/api/plextv.ts | 2 +-
server/interfaces/api/settingsInterfaces.ts | 1 +
server/lib/settings.ts | 2 +
server/routes/settings/index.ts | 54 +++-
server/routes/user/index.ts | 3 +-
src/components/Settings/SettingsServices.tsx | 4 +-
src/components/UserList/PlexImportModal.tsx | 250 +++++++++++++++++++
src/components/UserList/index.tsx | 61 ++---
src/context/SettingsContext.tsx | 1 +
src/i18n/globalMessages.ts | 2 +
src/i18n/locale/en.json | 11 +-
src/pages/_app.tsx | 1 +
14 files changed, 389 insertions(+), 50 deletions(-)
create mode 100644 src/components/UserList/PlexImportModal.tsx
diff --git a/docs/using-overseerr/users/README.md b/docs/using-overseerr/users/README.md
index 275e469c..139e935a 100644
--- a/docs/using-overseerr/users/README.md
+++ b/docs/using-overseerr/users/README.md
@@ -8,9 +8,9 @@ The user account created during Overseerr setup is the "Owner" account, which ca
There are currently two methods to add users to Overseerr: importing Plex users and creating "local users." All new users are created with the [default permissions](../settings/README.md#default-permissions) defined in **Settings → Users**.
-### Importing Users from Plex
+### Importing Plex Users
-Clicking the **Import Users from Plex** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically.
+Clicking the **Import Plex Users** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically.
Importing Plex users is not required, however. Any user with access to the Plex server can log in to Overseerr even if they have not been imported, and will be assigned the configured [default permissions](../settings/README.md#default-permissions) upon their first login.
diff --git a/overseerr-api.yml b/overseerr-api.yml
index e3fc90e3..8ad5afa4 100644
--- a/overseerr-api.yml
+++ b/overseerr-api.yml
@@ -1994,6 +1994,36 @@ paths:
type: array
items:
$ref: '#/components/schemas/PlexDevice'
+ /settings/plex/users:
+ get:
+ summary: Get Plex users
+ description: |
+ Returns a list of Plex users in a JSON array.
+
+ Requires the `MANAGE_USERS` permission.
+ tags:
+ - settings
+ - users
+ responses:
+ '200':
+ description: Plex users
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ title:
+ type: string
+ username:
+ type: string
+ email:
+ type: string
+ thumb:
+ type: string
/settings/radarr:
get:
summary: Get Radarr settings
@@ -3196,11 +3226,22 @@ paths:
post:
summary: Import all users from Plex
description: |
- Requests users from the Plex Server and creates a new user for each of them
+ Fetches and imports users from the Plex server. If a list of Plex IDs is provided in the request body, only the specified users will be imported. Otherwise, all users will be imported.
Requires the `MANAGE_USERS` permission.
tags:
- users
+ requestBody:
+ required: false
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ plexIds:
+ type: array
+ items:
+ type: string
responses:
'201':
description: A list of the newly created users
diff --git a/server/api/plextv.ts b/server/api/plextv.ts
index 9efcecc2..1733a85a 100644
--- a/server/api/plextv.ts
+++ b/server/api/plextv.ts
@@ -224,7 +224,7 @@ class PlexTvAPI {
const users = friends.MediaContainer.User;
- const user = users.find((u) => Number(u.$.id) === userId);
+ const user = users.find((u) => parseInt(u.$.id) === userId);
if (!user) {
throw new Error(
diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts
index 336bab0b..2f556635 100644
--- a/server/interfaces/api/settingsInterfaces.ts
+++ b/server/interfaces/api/settingsInterfaces.ts
@@ -35,6 +35,7 @@ export interface PublicSettingsResponse {
enablePushRegistration: boolean;
locale: string;
emailEnabled: boolean;
+ newPlexLogin: boolean;
}
export interface CacheItem {
diff --git a/server/lib/settings.ts b/server/lib/settings.ts
index 74d13e53..c500157c 100644
--- a/server/lib/settings.ts
+++ b/server/lib/settings.ts
@@ -113,6 +113,7 @@ interface FullPublicSettings extends PublicSettings {
enablePushRegistration: boolean;
locale: string;
emailEnabled: boolean;
+ newPlexLogin: boolean;
}
export interface NotificationAgentConfig {
@@ -469,6 +470,7 @@ class Settings {
enablePushRegistration: this.data.notifications.agents.webpush.enabled,
locale: this.data.main.locale,
emailEnabled: this.data.notifications.agents.email.enabled,
+ newPlexLogin: this.data.main.newPlexLogin,
};
}
diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts
index bad91eac..c9908f4a 100644
--- a/server/routes/settings/index.ts
+++ b/server/routes/settings/index.ts
@@ -1,7 +1,7 @@
import { Router } from 'express';
import rateLimit from 'express-rate-limit';
import fs from 'fs';
-import { merge, omit } from 'lodash';
+import { merge, omit, sortBy } from 'lodash';
import { rescheduleJob } from 'node-schedule';
import path from 'path';
import { getRepository } from 'typeorm';
@@ -225,6 +225,58 @@ settingsRoutes.post('/plex/sync', (req, res) => {
return res.status(200).json(plexFullScanner.status());
});
+settingsRoutes.get(
+ '/plex/users',
+ isAuthenticated(Permission.MANAGE_USERS),
+ async (req, res) => {
+ const userRepository = getRepository(User);
+ const qb = userRepository.createQueryBuilder('user');
+
+ const admin = await userRepository.findOneOrFail({
+ select: ['id', 'plexToken'],
+ order: { id: 'ASC' },
+ });
+ const plexApi = new PlexTvAPI(admin.plexToken ?? '');
+ const plexUsers = (await plexApi.getUsers()).MediaContainer.User.map(
+ (user) => user.$
+ );
+
+ const unimportedPlexUsers: {
+ id: string;
+ title: string;
+ username: string;
+ email: string;
+ thumb: string;
+ }[] = [];
+
+ const existingUsers = await qb
+ .where('user.plexId IN (:...plexIds)', {
+ plexIds: plexUsers.map((plexUser) => plexUser.id),
+ })
+ .orWhere('user.email IN (:...plexEmails)', {
+ plexEmails: plexUsers.map((plexUser) => plexUser.email.toLowerCase()),
+ })
+ .getMany();
+
+ await Promise.all(
+ plexUsers.map(async (plexUser) => {
+ if (
+ !existingUsers.find(
+ (user) =>
+ user.plexId === parseInt(plexUser.id) ||
+ user.email === plexUser.email.toLowerCase()
+ ) &&
+ (await plexApi.checkUserAccess(parseInt(plexUser.id)))
+ ) {
+ unimportedPlexUsers.push(plexUser);
+ }
+ })
+ );
+
+ return res.status(200).json(sortBy(unimportedPlexUsers, 'username'));
+ }
+);
+
settingsRoutes.get(
'/logs',
rateLimit({ windowMs: 60 * 1000, max: 50 }),
diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts
index e6fa09cd..8352726b 100644
--- a/server/routes/user/index.ts
+++ b/server/routes/user/index.ts
@@ -400,6 +400,7 @@ router.post(
try {
const settings = getSettings();
const userRepository = getRepository(User);
+ const body = req.body as { plexIds: string[] } | undefined;
// taken from auth.ts
const mainUser = await userRepository.findOneOrFail({
@@ -434,7 +435,7 @@ router.post(
user.plexId = parseInt(account.id);
}
await userRepository.save(user);
- } else {
+ } else if (!body || body.plexIds.includes(account.id)) {
if (await mainPlexTv.checkUserAccess(parseInt(account.id))) {
const newUser = new User({
plexUsername: account.username,
diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx
index 377fda3e..1ffbd4cf 100644
--- a/src/components/Settings/SettingsServices.tsx
+++ b/src/components/Settings/SettingsServices.tsx
@@ -292,7 +292,7 @@ const SettingsServices: React.FC = () => {
serverType: 'Radarr',
strong: function strong(msg) {
return (
-
+
{msg}
);
@@ -382,7 +382,7 @@ const SettingsServices: React.FC = () => {
serverType: 'Sonarr',
strong: function strong(msg) {
return (
-
+
{msg}
);
diff --git a/src/components/UserList/PlexImportModal.tsx b/src/components/UserList/PlexImportModal.tsx
new file mode 100644
index 00000000..7e937793
--- /dev/null
+++ b/src/components/UserList/PlexImportModal.tsx
@@ -0,0 +1,250 @@
+import { InboxInIcon } from '@heroicons/react/solid';
+import axios from 'axios';
+import React, { useState } from 'react';
+import { defineMessages, useIntl } from 'react-intl';
+import { useToasts } from 'react-toast-notifications';
+import useSWR from 'swr';
+import useSettings from '../../hooks/useSettings';
+import globalMessages from '../../i18n/globalMessages';
+import Alert from '../Common/Alert';
+import Modal from '../Common/Modal';
+
+interface PlexImportProps {
+ onCancel?: () => void;
+ onComplete?: () => void;
+}
+
+const messages = defineMessages({
+ importfromplex: 'Import Plex Users',
+ importfromplexerror: 'Something went wrong while importing Plex users.',
+ importedfromplex:
+ '{userCount} {userCount, plural, one {user} other {users}} Plex users imported successfully!',
+ user: 'User',
+ nouserstoimport: 'There are no Plex users to import.',
+ newplexsigninenabled:
+ 'The Enable New Plex Sign-In setting is currently enabled. Plex users with library access do not need to be imported in order to sign in.',
+});
+
+const PlexImportModal: React.FC = ({
+ onCancel,
+ onComplete,
+}) => {
+ const intl = useIntl();
+ const settings = useSettings();
+ const { addToast } = useToasts();
+ const [isImporting, setImporting] = useState(false);
+ const [selectedUsers, setSelectedUsers] = useState([]);
+ const { data, error } = useSWR<
+ {
+ id: string;
+ title: string;
+ username: string;
+ email: string;
+ thumb: string;
+ }[]
+ >(`/api/v1/settings/plex/users`, {
+ revalidateOnMount: true,
+ });
+
+ const importUsers = async () => {
+ setImporting(true);
+
+ try {
+ const { data: createdUsers } = await axios.post(
+ '/api/v1/user/import-from-plex',
+ { plexIds: selectedUsers }
+ );
+
+ if (!createdUsers.length) {
+ throw new Error('No users were imported from Plex.');
+ }
+
+ addToast(
+ intl.formatMessage(messages.importedfromplex, {
+ userCount: createdUsers.length,
+ strong: function strong(msg) {
+ return {msg};
+ },
+ }),
+ {
+ autoDismiss: true,
+ appearance: 'success',
+ }
+ );
+
+ if (onComplete) {
+ onComplete();
+ }
+ } catch (e) {
+ addToast(intl.formatMessage(messages.importfromplexerror), {
+ autoDismiss: true,
+ appearance: 'error',
+ });
+ } finally {
+ setImporting(false);
+ }
+ };
+
+ const isSelectedUser = (plexId: string): boolean =>
+ selectedUsers.includes(plexId);
+
+ const isAllUsers = (): boolean => selectedUsers.length === data?.length;
+
+ const toggleUser = (plexId: string): void => {
+ if (selectedUsers.includes(plexId)) {
+ setSelectedUsers((users) => users.filter((user) => user !== plexId));
+ } else {
+ setSelectedUsers((users) => [...users, plexId]);
+ }
+ };
+
+ const toggleAllUsers = (): void => {
+ if (data && selectedUsers.length >= 0 && !isAllUsers()) {
+ setSelectedUsers(data.map((user) => user.id));
+ } else {
+ setSelectedUsers([]);
+ }
+ };
+
+ return (
+ }
+ onOk={() => {
+ importUsers();
+ }}
+ okDisabled={isImporting || !selectedUsers.length}
+ okText={intl.formatMessage(
+ isImporting ? globalMessages.importing : globalMessages.import
+ )}
+ onCancel={onCancel}
+ >
+ {data?.length ? (
+ <>
+ {settings.currentSettings.newPlexLogin && (
+ {msg}
+ );
+ },
+ })}
+ type="info"
+ />
+ )}
+
+
+
+
+
+
+
+ |
+ toggleAllUsers()}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === 'Space') {
+ toggleAllUsers();
+ }
+ }}
+ className="relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 pt-2 cursor-pointer focus:outline-none"
+ >
+
+
+
+ |
+
+ {intl.formatMessage(messages.user)}
+ |
+
+
+
+ {data?.map((user) => (
+
+ |
+ toggleUser(user.id)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === 'Space') {
+ toggleUser(user.id);
+ }
+ }}
+ className="relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 pt-2 cursor-pointer focus:outline-none"
+ >
+
+
+
+ |
+
+
+ 
+
+
+ {user.username}
+
+ {user.username &&
+ user.username.toLowerCase() !==
+ user.email && (
+
+ {user.email}
+
+ )}
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+ >
+ ) : (
+
+ )}
+
+ );
+};
+
+export default PlexImportModal;
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx
index a544c8f9..fdf7b4b0 100644
--- a/src/components/UserList/index.tsx
+++ b/src/components/UserList/index.tsx
@@ -33,15 +33,12 @@ import SensitiveInput from '../Common/SensitiveInput';
import Table from '../Common/Table';
import Transition from '../Transition';
import BulkEditModal from './BulkEditModal';
+import PlexImportModal from './PlexImportModal';
const messages = defineMessages({
users: 'Users',
userlist: 'User List',
- importfromplex: 'Import Users from Plex',
- importfromplexerror: 'Something went wrong while importing users from Plex.',
- importedfromplex:
- '{userCount, plural, one {# new user} other {# new users}} imported from Plex successfully!',
- nouserstoimport: 'No new users to import from Plex.',
+ importfromplex: 'Import Plex Users',
user: 'User',
totalrequests: 'Requests',
accounttype: 'Type',
@@ -103,7 +100,7 @@ const UserList: React.FC = () => {
);
const [isDeleting, setDeleting] = useState(false);
- const [isImporting, setImporting] = useState(false);
+ const [showImportModal, setShowImportModal] = useState(false);
const [deleteModal, setDeleteModal] = useState<{
isOpen: boolean;
user?: User;
@@ -193,35 +190,6 @@ const UserList: React.FC = () => {
}
};
- const importFromPlex = async () => {
- setImporting(true);
-
- try {
- const { data: createdUsers } = await axios.post(
- '/api/v1/user/import-from-plex'
- );
- addToast(
- createdUsers.length
- ? intl.formatMessage(messages.importedfromplex, {
- userCount: createdUsers.length,
- })
- : intl.formatMessage(messages.nouserstoimport),
- {
- autoDismiss: true,
- appearance: 'success',
- }
- );
- } catch (e) {
- addToast(intl.formatMessage(messages.importfromplexerror), {
- autoDismiss: true,
- appearance: 'error',
- });
- } finally {
- revalidate();
- setImporting(false);
- }
- };
-
if (!data && !error) {
return ;
}
@@ -354,7 +322,7 @@ const UserList: React.FC = () => {
title={intl.formatMessage(messages.localLoginDisabled, {
strong: function strong(msg) {
return (
-
+
{msg}
);
@@ -481,6 +449,24 @@ const UserList: React.FC = () => {
/>
+
+ setShowImportModal(false)}
+ onComplete={() => {
+ setShowImportModal(false);
+ revalidate();
+ }}
+ />
+
+
{intl.formatMessage(messages.userlist)}
@@ -496,8 +482,7 @@ const UserList: React.FC = () => {