Merge pull request #527 from Fallenbagel/fix-local-watchlists

fix(watchlist): discover local watchlist item display and profile local watchlist slider visibility
This commit is contained in:
Fallenbagel
2023-11-08 01:34:04 +05:00
committed by GitHub
15 changed files with 174 additions and 86 deletions

View File

@@ -848,7 +848,7 @@ discoverRoutes.get<Record<string, unknown>, WatchlistResponse>(
if (total) { if (total) {
return res.json({ return res.json({
page: page, page: page,
totalPages: total / itemsPerPage, totalPages: Math.ceil(total / itemsPerPage),
totalResults: total, totalResults: total,
results: result, results: result,
}); });

View File

@@ -717,14 +717,15 @@ router.get<{ id: string }, WatchlistResponse>(
const user = await getRepository(User).findOneOrFail({ const user = await getRepository(User).findOneOrFail({
where: { id: Number(req.params.id) }, where: { id: Number(req.params.id) },
select: { id: true, plexToken: true }, select: ['id', 'plexToken'],
}); });
if (!user?.plexToken) {
if (user) { if (user) {
const [result, total] = await getRepository(Watchlist).findAndCount({ const [result, total] = await getRepository(Watchlist).findAndCount({
where: { requestedBy: { id: user?.id } }, where: { requestedBy: { id: user?.id } },
relations: { requestedBy: true }, relations: {
/*requestedBy: true,media:true*/
},
// loadRelationIds: true, // loadRelationIds: true,
take: itemsPerPage, take: itemsPerPage,
skip: offset, skip: offset,
@@ -732,7 +733,7 @@ router.get<{ id: string }, WatchlistResponse>(
if (total) { if (total) {
return res.json({ return res.json({
page: page, page: page,
totalPages: total / itemsPerPage, totalPages: Math.ceil(total / itemsPerPage),
totalResults: total, totalResults: total,
results: result, results: result,
}); });
@@ -740,6 +741,7 @@ router.get<{ id: string }, WatchlistResponse>(
} }
// We will just return an empty array if the user has no Plex token // We will just return an empty array if the user has no Plex token
if (!user.plexToken) {
return res.json({ return res.json({
page: 1, page: 1,
totalPages: 1, totalPages: 1,

View File

@@ -10,8 +10,8 @@ import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr'; import useSWR from 'swr';
const messages = defineMessages({ const messages = defineMessages({
streamdevelop: 'Overseerr Develop', streamdevelop: 'Jellyseerr Develop',
streamstable: 'Overseerr Stable', streamstable: 'Jellyseerr Stable',
outofdate: 'Out of Date', outofdate: 'Out of Date',
commitsbehind: commitsbehind:
'{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind', '{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind',

View File

@@ -1,7 +1,9 @@
import type { PermissionItem } from '@app/components/PermissionOption'; import type { PermissionItem } from '@app/components/PermissionOption';
import PermissionOption from '@app/components/PermissionOption'; import PermissionOption from '@app/components/PermissionOption';
import useSettings from '@app/hooks/useSettings';
import type { User } from '@app/hooks/useUser'; import type { User } from '@app/hooks/useUser';
import { Permission } from '@app/hooks/useUser'; import { Permission } from '@app/hooks/useUser';
import { MediaServerType } from '@server/constants/server';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
export const messages = defineMessages({ export const messages = defineMessages({
@@ -72,9 +74,9 @@ export const messages = defineMessages({
viewrecent: 'View Recently Added', viewrecent: 'View Recently Added',
viewrecentDescription: viewrecentDescription:
'Grant permission to view the list of recently added media.', 'Grant permission to view the list of recently added media.',
viewwatchlists: 'View Plex Watchlists', viewwatchlists: 'View {mediaServerName} Watchlists',
viewwatchlistsDescription: viewwatchlistsDescription:
"Grant permission to view other users' Plex Watchlists.", "Grant permission to view other users' {mediaServerName} Watchlists.",
}); });
interface PermissionEditProps { interface PermissionEditProps {
@@ -91,6 +93,7 @@ export const PermissionEdit = ({
onUpdate, onUpdate,
}: PermissionEditProps) => { }: PermissionEditProps) => {
const intl = useIntl(); const intl = useIntl();
const settings = useSettings();
const permissionList: PermissionItem[] = [ const permissionList: PermissionItem[] = [
{ {
@@ -131,8 +134,24 @@ export const PermissionEdit = ({
}, },
{ {
id: 'viewwatchlists', id: 'viewwatchlists',
name: intl.formatMessage(messages.viewwatchlists), name: intl.formatMessage(messages.viewwatchlists, {
description: intl.formatMessage(messages.viewwatchlistsDescription), mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby',
}),
description: intl.formatMessage(messages.viewwatchlistsDescription, {
mediaServerName:
settings.currentSettings.mediaServerType === MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby',
}),
permission: Permission.WATCHLIST_VIEW, permission: Permission.WATCHLIST_VIEW,
}, },
], ],

View File

@@ -16,7 +16,7 @@ const messages = defineMessages({
agentenabled: 'Enable Agent', agentenabled: 'Enable Agent',
accessToken: 'Application API Token', accessToken: 'Application API Token',
accessTokenTip: accessTokenTip:
'<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr', '<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Jellyseerr',
userToken: 'User or Group Key', userToken: 'User or Group Key',
userTokenTip: userTokenTip:
'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>', 'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>',

View File

@@ -19,7 +19,7 @@ const messages = defineMessages({
'Allow users to also start a chat with your bot and configure their own notifications', 'Allow users to also start a chat with your bot and configure their own notifications',
botAPI: 'Bot Authorization Token', botAPI: 'Bot Authorization Token',
botApiTip: botApiTip:
'<CreateBotLink>Create a bot</CreateBotLink> for use with Overseerr', '<CreateBotLink>Create a bot</CreateBotLink> for use with Jellyseerr',
chatId: 'Chat ID', chatId: 'Chat ID',
chatIdTip: chatIdTip:
'Start a chat with your bot, add <GetIdBotLink>@get_id_bot</GetIdBotLink>, and issue the <code>/my_id</code> command', 'Start a chat with your bot, add <GetIdBotLink>@get_id_bot</GetIdBotLink>, and issue the <code>/my_id</code> command',

View File

@@ -18,7 +18,7 @@ const messages = defineMessages({
toastWebPushTestSuccess: 'Web push test notification sent!', toastWebPushTestSuccess: 'Web push test notification sent!',
toastWebPushTestFailed: 'Web push test notification failed to send.', toastWebPushTestFailed: 'Web push test notification failed to send.',
httpsRequirement: httpsRequirement:
'In order to receive web push notifications, Overseerr must be served over HTTPS.', 'In order to receive web push notifications, Jellyseerr must be served over HTTPS.',
}); });
const NotificationsWebPush = () => { const NotificationsWebPush = () => {

View File

@@ -16,7 +16,7 @@ import useSWR from 'swr';
const messages = defineMessages({ const messages = defineMessages({
about: 'About', about: 'About',
overseerrinformation: 'About Overseerr', overseerrinformation: 'About Jellyseerr',
version: 'Version', version: 'Version',
totalmedia: 'Total Media', totalmedia: 'Total Media',
totalrequests: 'Total Requests', totalrequests: 'Total Requests',
@@ -25,6 +25,7 @@ const messages = defineMessages({
timezone: 'Time Zone', timezone: 'Time Zone',
appDataPath: 'Data Directory', appDataPath: 'Data Directory',
supportoverseerr: 'Support Overseerr', supportoverseerr: 'Support Overseerr',
supportjellyseerr: 'Support Jellyseerr',
helppaycoffee: 'Help Pay for Coffee', helppaycoffee: 'Help Pay for Coffee',
documentation: 'Documentation', documentation: 'Documentation',
preferredmethod: 'Preferred', preferredmethod: 'Preferred',
@@ -33,7 +34,7 @@ const messages = defineMessages({
betawarning: betawarning:
'This is BETA software. Features may be broken and/or unstable. Please report any issues on GitHub!', 'This is BETA software. Features may be broken and/or unstable. Please report any issues on GitHub!',
runningDevelop: runningDevelop:
'You are running the <code>develop</code> branch of Overseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.', 'You are running the <code>develop</code> branch of Jellyseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.',
}); });
const SettingsAbout = () => { const SettingsAbout = () => {
@@ -187,6 +188,54 @@ const SettingsAbout = () => {
</List.Item> </List.Item>
</List> </List>
</div> </div>
<div className="section">
<List title={intl.formatMessage(messages.supportoverseerr)}>
<List.Item
title={`${intl.formatMessage(messages.helppaycoffee)} ☕️`}
>
<a
href="https://github.com/sponsors/sct"
target="_blank"
rel="noreferrer"
className="text-indigo-500 transition duration-300 hover:underline"
>
https://github.com/sponsors/sct
</a>
<Badge className="ml-2">
{intl.formatMessage(messages.preferredmethod)}
</Badge>
</List.Item>
<List.Item title="">
<a
href="https://patreon.com/overseerr"
target="_blank"
rel="noreferrer"
className="text-indigo-500 transition duration-300 hover:underline"
>
https://patreon.com/overseerr
</a>
</List.Item>
</List>
</div>
<div className="section">
<List title={intl.formatMessage(messages.supportjellyseerr)}>
<List.Item
title={`${intl.formatMessage(messages.helppaycoffee)} ☕️`}
>
<a
href="https://www.buymeacoffee.com/fallen.bagel"
target="_blank"
rel="noreferrer"
className="text-indigo-500 transition duration-300 hover:underline"
>
https://www.buymeacoffee.com/fallen.bagel
</a>
<Badge className="ml-2">
{intl.formatMessage(messages.preferredmethod)}
</Badge>
</List.Item>
</List>
</div>
<div className="section"> <div className="section">
<Releases currentVersion={data.version} /> <Releases currentVersion={data.version} />
</div> </div>

View File

@@ -9,7 +9,7 @@ const messages = defineMessages({
experimentalTooltip: experimentalTooltip:
'Enabling this setting may result in unexpected application behavior', 'Enabling this setting may result in unexpected application behavior',
restartrequiredTooltip: restartrequiredTooltip:
'Overseerr must be restarted for changes to this setting to take effect', 'Jellyseerr must be restarted for changes to this setting to take effect',
}); });
const SettingsBadge = ({ const SettingsBadge = ({

View File

@@ -30,7 +30,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
jobsandcache: 'Jobs & Cache', jobsandcache: 'Jobs & Cache',
jobs: 'Jobs', jobs: 'Jobs',
jobsDescription: jobsDescription:
'Overseerr performs certain maintenance tasks as regularly-scheduled jobs, but they can also be manually triggered below. Manually running a job will not alter its schedule.', 'Jellyseerr performs certain maintenance tasks as regularly-scheduled jobs, but they can also be manually triggered below. Manually running a job will not alter its schedule.',
jobname: 'Job Name', jobname: 'Job Name',
jobtype: 'Type', jobtype: 'Type',
nextexecution: 'Next Execution', nextexecution: 'Next Execution',
@@ -42,7 +42,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
command: 'Command', command: 'Command',
cache: 'Cache', cache: 'Cache',
cacheDescription: cacheDescription:
'Overseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.', 'Jellyseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.',
cacheflushed: '{cachename} cache flushed.', cacheflushed: '{cachename} cache flushed.',
cachename: 'Cache Name', cachename: 'Cache Name',
cachehits: 'Hits', cachehits: 'Hits',
@@ -76,7 +76,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
'Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}', 'Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}',
imagecache: 'Image Cache', imagecache: 'Image Cache',
imagecacheDescription: imagecacheDescription:
'When enabled in settings, Overseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.', 'When enabled in settings, Jellyseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.',
imagecachecount: 'Images Cached', imagecachecount: 'Images Cached',
imagecachesize: 'Total Cache Size', imagecachesize: 'Total Cache Size',
}); });

View File

@@ -27,7 +27,7 @@ const messages = defineMessages({
general: 'General', general: 'General',
generalsettings: 'General Settings', generalsettings: 'General Settings',
generalsettingsDescription: generalsettingsDescription:
'Configure global and default settings for Overseerr.', 'Configure global and default settings for Jellyseerr.',
apikey: 'API Key', apikey: 'API Key',
applicationTitle: 'Application Title', applicationTitle: 'Application Title',
applicationurl: 'Application URL', applicationurl: 'Application URL',
@@ -49,7 +49,7 @@ const messages = defineMessages({
'Cache externally sourced images (requires a significant amount of disk space)', 'Cache externally sourced images (requires a significant amount of disk space)',
trustProxy: 'Enable Proxy Support', trustProxy: 'Enable Proxy Support',
trustProxyTip: trustProxyTip:
'Allow Overseerr to correctly register client IP addresses behind a proxy', 'Allow Jellyseerr to correctly register client IP addresses behind a proxy',
validationApplicationTitle: 'You must provide an application title', validationApplicationTitle: 'You must provide an application title',
validationApplicationUrl: 'You must provide a valid URL', validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash', validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',

View File

@@ -49,12 +49,12 @@ const messages = defineMessages({
enablessl: 'Use SSL', enablessl: 'Use SSL',
plexlibraries: 'Plex Libraries', plexlibraries: 'Plex Libraries',
plexlibrariesDescription: plexlibrariesDescription:
'The libraries Overseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.', 'The libraries Jellyseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.',
scanning: 'Syncing…', scanning: 'Syncing…',
scan: 'Sync Libraries', scan: 'Sync Libraries',
manualscan: 'Manual Library Scan', manualscan: 'Manual Library Scan',
manualscanDescription: manualscanDescription:
"Normally, this will only be run once every 24 hours. Overseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!", "Normally, this will only be run once every 24 hours. Jellyseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!",
notrunning: 'Not Running', notrunning: 'Not Running',
currentlibrary: 'Current Library: {name}', currentlibrary: 'Current Library: {name}',
librariesRemaining: 'Libraries Remaining: {count}', librariesRemaining: 'Libraries Remaining: {count}',
@@ -67,7 +67,7 @@ const messages = defineMessages({
'Optionally direct users to the web app on your server instead of the "hosted" web app', 'Optionally direct users to the web app on your server instead of the "hosted" web app',
tautulliSettings: 'Tautulli Settings', tautulliSettings: 'Tautulli Settings',
tautulliSettingsDescription: tautulliSettingsDescription:
'Optionally configure the settings for your Tautulli server. Overseerr fetches watch history data for your Plex media from Tautulli.', 'Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.',
urlBase: 'URL Base', urlBase: 'URL Base',
tautulliApiKey: 'API Key', tautulliApiKey: 'API Key',
externalUrl: 'External URL', externalUrl: 'External URL',

View File

@@ -23,7 +23,7 @@ const messages = defineMessages({
toastSettingsFailure: 'Something went wrong while saving settings.', toastSettingsFailure: 'Something went wrong while saving settings.',
localLogin: 'Enable Local Sign-In', localLogin: 'Enable Local Sign-In',
localLoginTip: localLoginTip:
'Allow users to sign in using their email address and password, instead of Plex OAuth', 'Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth',
newPlexLogin: 'Enable New {mediaServerName} Sign-In', newPlexLogin: 'Enable New {mediaServerName} Sign-In',
newPlexLoginTip: newPlexLoginTip:
'Allow {mediaServerName} users to sign in without first being imported', 'Allow {mediaServerName} users to sign in without first being imported',
@@ -114,7 +114,16 @@ const SettingsUsers = () => {
<label htmlFor="localLogin" className="checkbox-label"> <label htmlFor="localLogin" className="checkbox-label">
{intl.formatMessage(messages.localLogin)} {intl.formatMessage(messages.localLogin)}
<span className="label-tip"> <span className="label-tip">
{intl.formatMessage(messages.localLoginTip)} {intl.formatMessage(messages.localLoginTip, {
mediaServerName:
settings.currentSettings.mediaServerType ===
MediaServerType.PLEX
? 'Plex'
: settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby',
})}
</span> </span>
</label> </label>
<div className="form-input-area"> <div className="form-input-area">

View File

@@ -34,6 +34,7 @@ const messages = defineMessages({
seriesrequest: 'Series Requests', seriesrequest: 'Series Requests',
recentlywatched: 'Recently Watched', recentlywatched: 'Recently Watched',
plexwatchlist: 'Plex Watchlist', plexwatchlist: 'Plex Watchlist',
localWatchlist: "{username}'s Watchlist",
emptywatchlist: emptywatchlist:
'Media added to your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> will appear here.', 'Media added to your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> will appear here.',
}); });
@@ -78,17 +79,17 @@ const UserProfile = () => {
? `/api/v1/user/${user.id}/watch_data` ? `/api/v1/user/${user.id}/watch_data`
: null : null
); );
const { data: watchlistItems, error: watchlistError } = const { data: watchlistItems, error: watchlistError } =
useSWR<WatchlistResponse>( useSWR<WatchlistResponse>(
user?.userType === UserType.PLEX && user?.id === currentUser?.id ||
(user.id === currentUser?.id ||
currentHasPermission( currentHasPermission(
[Permission.MANAGE_REQUESTS, Permission.WATCHLIST_VIEW], [Permission.MANAGE_REQUESTS, Permission.WATCHLIST_VIEW],
{ {
type: 'or', type: 'or',
} }
)) )
? `/api/v1/user/${user.id}/watchlist` ? `/api/v1/user/${user?.id}/watchlist`
: null, : null,
{ {
revalidateOnMount: true, revalidateOnMount: true,
@@ -117,6 +118,13 @@ const UserProfile = () => {
return <Error statusCode={404} />; return <Error statusCode={404} />;
} }
const watchlistSliderTitle = intl.formatMessage(
user.userType === UserType.PLEX
? messages.plexwatchlist
: messages.localWatchlist,
{ username: user.displayName }
);
return ( return (
<> <>
<PageTitle title={user.displayName} /> <PageTitle title={user.displayName} />
@@ -309,8 +317,7 @@ const UserProfile = () => {
/> />
</> </>
)} )}
{user.userType === UserType.PLEX && {(user.id === currentUser?.id ||
(user.id === currentUser?.id ||
currentHasPermission( currentHasPermission(
[Permission.MANAGE_REQUESTS, Permission.WATCHLIST_VIEW], [Permission.MANAGE_REQUESTS, Permission.WATCHLIST_VIEW],
{ type: 'or' } { type: 'or' }
@@ -327,11 +334,11 @@ const UserProfile = () => {
href={ href={
user.id === currentUser?.id user.id === currentUser?.id
? '/profile/watchlist' ? '/profile/watchlist'
: `/users/${user?.id}/watchlist` : `/users/${user.id}/watchlist`
} }
> >
<a className="slider-title"> <a className="slider-title">
<span>{intl.formatMessage(messages.plexwatchlist)}</span> <span>{watchlistSliderTitle}</span>
<ArrowRightCircleIcon /> <ArrowRightCircleIcon />
</a> </a>
</Link> </Link>

View File

@@ -94,7 +94,6 @@
"components.Discover.emptywatchlist": "Media added to your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> will appear here.", "components.Discover.emptywatchlist": "Media added to your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> will appear here.",
"components.Discover.moviegenres": "Movie Genres", "components.Discover.moviegenres": "Movie Genres",
"components.Discover.networks": "Networks", "components.Discover.networks": "Networks",
"components.Discover.noRequests": "No requests.",
"components.Discover.plexwatchlist": "Your Watchlist", "components.Discover.plexwatchlist": "Your Watchlist",
"components.Discover.popularmovies": "Popular Movies", "components.Discover.popularmovies": "Popular Movies",
"components.Discover.populartv": "Popular Series", "components.Discover.populartv": "Popular Series",
@@ -200,9 +199,9 @@
"components.LanguageSelector.originalLanguageDefault": "All Languages", "components.LanguageSelector.originalLanguageDefault": "All Languages",
"components.Layout.LanguagePicker.displaylanguage": "Display Language", "components.Layout.LanguagePicker.displaylanguage": "Display Language",
"components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV", "components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV",
"components.Layout.Sidebar.dashboard": "Discover",
"components.Layout.Sidebar.browsemovies": "Movies", "components.Layout.Sidebar.browsemovies": "Movies",
"components.Layout.Sidebar.browsetv": "Series", "components.Layout.Sidebar.browsetv": "Series",
"components.Layout.Sidebar.dashboard": "Discover",
"components.Layout.Sidebar.issues": "Issues", "components.Layout.Sidebar.issues": "Issues",
"components.Layout.Sidebar.requests": "Requests", "components.Layout.Sidebar.requests": "Requests",
"components.Layout.Sidebar.settings": "Settings", "components.Layout.Sidebar.settings": "Settings",
@@ -218,11 +217,12 @@
"components.Layout.UserWarnings.passwordRequired": "A password is required.", "components.Layout.UserWarnings.passwordRequired": "A password is required.",
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind", "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind",
"components.Layout.VersionStatus.outofdate": "Out of Date", "components.Layout.VersionStatus.outofdate": "Out of Date",
"components.Layout.VersionStatus.streamdevelop": "Jellyseerr Develop", "components.Layout.VersionStatus.streamdevelop": "Overseerr Develop",
"components.Layout.VersionStatus.streamstable": "Jellyseerr Stable", "components.Layout.VersionStatus.streamstable": "Overseerr Stable",
"components.Login.credentialerror": "The username or password is incorrect.", "components.Login.credentialerror": "The username or password is incorrect.",
"components.Login.description": "Since this is your first time logging into {applicationName}, you are required to add a valid email address.", "components.Login.description": "Since this is your first time logging into {applicationName}, you are required to add a valid email address.",
"components.Login.email": "Email Address", "components.Login.email": "Email Address",
"components.Login.emailtooltip": "Address does not need to be associated with your {mediaServerName} instance.",
"components.Login.forgotpassword": "Forgot Password?", "components.Login.forgotpassword": "Forgot Password?",
"components.Login.host": "{mediaServerName} URL", "components.Login.host": "{mediaServerName} URL",
"components.Login.initialsignin": "Connect", "components.Login.initialsignin": "Connect",
@@ -394,8 +394,8 @@
"components.PermissionEdit.viewrecentDescription": "Grant permission to view the list of recently added media.", "components.PermissionEdit.viewrecentDescription": "Grant permission to view the list of recently added media.",
"components.PermissionEdit.viewrequests": "View Requests", "components.PermissionEdit.viewrequests": "View Requests",
"components.PermissionEdit.viewrequestsDescription": "Grant permission to view media requests submitted by other users.", "components.PermissionEdit.viewrequestsDescription": "Grant permission to view media requests submitted by other users.",
"components.PermissionEdit.viewwatchlists": "View Plex Watchlists", "components.PermissionEdit.viewwatchlists": "View {mediaServerName} Watchlists",
"components.PermissionEdit.viewwatchlistsDescription": "Grant permission to view other users' Plex Watchlists.", "components.PermissionEdit.viewwatchlistsDescription": "Grant permission to view other users' {mediaServerName} Watchlists.",
"components.PersonDetails.alsoknownas": "Also Known As: {names}", "components.PersonDetails.alsoknownas": "Also Known As: {names}",
"components.PersonDetails.appearsin": "Appearances", "components.PersonDetails.appearsin": "Appearances",
"components.PersonDetails.ascharacter": "as {character}", "components.PersonDetails.ascharacter": "as {character}",
@@ -582,7 +582,7 @@
"components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token", "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token",
"components.Settings.Notifications.NotificationsPushbullet.validationTypes": "You must select at least one notification type", "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "You must select at least one notification type",
"components.Settings.Notifications.NotificationsPushover.accessToken": "Application API Token", "components.Settings.Notifications.NotificationsPushover.accessToken": "Application API Token",
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Jellyseerr", "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr",
"components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Device Default", "components.Settings.Notifications.NotificationsPushover.deviceDefault": "Device Default",
"components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.",
@@ -607,7 +607,7 @@
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL", "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an <WebhookLink>Incoming Webhook</WebhookLink> integration", "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an <WebhookLink>Incoming Webhook</WebhookLink> integration",
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Jellyseerr must be served over HTTPS.", "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Overseerr must be served over HTTPS.",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test notification sent!", "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test notification sent!",
@@ -633,7 +633,7 @@
"components.Settings.Notifications.authPass": "SMTP Password", "components.Settings.Notifications.authPass": "SMTP Password",
"components.Settings.Notifications.authUser": "SMTP Username", "components.Settings.Notifications.authUser": "SMTP Username",
"components.Settings.Notifications.botAPI": "Bot Authorization Token", "components.Settings.Notifications.botAPI": "Bot Authorization Token",
"components.Settings.Notifications.botApiTip": "<CreateBotLink>Create a bot</CreateBotLink> for use with Jellyseerr", "components.Settings.Notifications.botApiTip": "<CreateBotLink>Create a bot</CreateBotLink> for use with Overseerr",
"components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL", "components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL",
"components.Settings.Notifications.botUsername": "Bot Username", "components.Settings.Notifications.botUsername": "Bot Username",
"components.Settings.Notifications.botUsernameTip": "Allow users to also start a chat with your bot and configure their own notifications", "components.Settings.Notifications.botUsernameTip": "Allow users to also start a chat with your bot and configure their own notifications",
@@ -748,10 +748,11 @@
"components.Settings.SettingsAbout.githubdiscussions": "GitHub Discussions", "components.Settings.SettingsAbout.githubdiscussions": "GitHub Discussions",
"components.Settings.SettingsAbout.helppaycoffee": "Help Pay for Coffee", "components.Settings.SettingsAbout.helppaycoffee": "Help Pay for Coffee",
"components.Settings.SettingsAbout.outofdate": "Out of Date", "components.Settings.SettingsAbout.outofdate": "Out of Date",
"components.Settings.SettingsAbout.overseerrinformation": "About Jellyseerr", "components.Settings.SettingsAbout.overseerrinformation": "About Overseerr",
"components.Settings.SettingsAbout.preferredmethod": "Preferred", "components.Settings.SettingsAbout.preferredmethod": "Preferred",
"components.Settings.SettingsAbout.runningDevelop": "You are running the <code>develop</code> branch of Jellyseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.", "components.Settings.SettingsAbout.runningDevelop": "You are running the <code>develop</code> branch of Overseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.",
"components.Settings.SettingsAbout.supportoverseerr": "Support Jellyseerr", "components.Settings.SettingsAbout.supportoverseerr": "Support Overseerr",
"components.Settings.SettingsAbout.supportjellyseerr": "Support Jellyseerr",
"components.Settings.SettingsAbout.timezone": "Time Zone", "components.Settings.SettingsAbout.timezone": "Time Zone",
"components.Settings.SettingsAbout.totalmedia": "Total Media", "components.Settings.SettingsAbout.totalmedia": "Total Media",
"components.Settings.SettingsAbout.totalrequests": "Total Requests", "components.Settings.SettingsAbout.totalrequests": "Total Requests",
@@ -759,7 +760,7 @@
"components.Settings.SettingsAbout.version": "Version", "components.Settings.SettingsAbout.version": "Version",
"components.Settings.SettingsJobsCache.availability-sync": "Media Availability Sync", "components.Settings.SettingsJobsCache.availability-sync": "Media Availability Sync",
"components.Settings.SettingsJobsCache.cache": "Cache", "components.Settings.SettingsJobsCache.cache": "Cache",
"components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.", "components.Settings.SettingsJobsCache.cacheDescription": "Overseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.",
"components.Settings.SettingsJobsCache.cacheflushed": "{cachename} cache flushed.", "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} cache flushed.",
"components.Settings.SettingsJobsCache.cachehits": "Hits", "components.Settings.SettingsJobsCache.cachehits": "Hits",
"components.Settings.SettingsJobsCache.cachekeys": "Total Keys", "components.Settings.SettingsJobsCache.cachekeys": "Total Keys",
@@ -780,7 +781,7 @@
"components.Settings.SettingsJobsCache.flushcache": "Flush Cache", "components.Settings.SettingsJobsCache.flushcache": "Flush Cache",
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Image Cache Cleanup", "components.Settings.SettingsJobsCache.image-cache-cleanup": "Image Cache Cleanup",
"components.Settings.SettingsJobsCache.imagecache": "Image Cache", "components.Settings.SettingsJobsCache.imagecache": "Image Cache",
"components.Settings.SettingsJobsCache.imagecacheDescription": "When enabled in settings, Jellyseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.", "components.Settings.SettingsJobsCache.imagecacheDescription": "When enabled in settings, Overseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.",
"components.Settings.SettingsJobsCache.imagecachecount": "Images Cached", "components.Settings.SettingsJobsCache.imagecachecount": "Images Cached",
"components.Settings.SettingsJobsCache.imagecachesize": "Total Cache Size", "components.Settings.SettingsJobsCache.imagecachesize": "Total Cache Size",
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Jellyfin Full Library Scan", "components.Settings.SettingsJobsCache.jellyfin-full-scan": "Jellyfin Full Library Scan",
@@ -790,7 +791,7 @@
"components.Settings.SettingsJobsCache.jobcancelled": "{jobname} canceled.", "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} canceled.",
"components.Settings.SettingsJobsCache.jobname": "Job Name", "components.Settings.SettingsJobsCache.jobname": "Job Name",
"components.Settings.SettingsJobsCache.jobs": "Jobs", "components.Settings.SettingsJobsCache.jobs": "Jobs",
"components.Settings.SettingsJobsCache.jobsDescription": "Jellyseerr performs certain maintenance tasks as regularly-scheduled jobs, but they can also be manually triggered below. Manually running a job will not alter its schedule.", "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr performs certain maintenance tasks as regularly-scheduled jobs, but they can also be manually triggered below. Manually running a job will not alter its schedule.",
"components.Settings.SettingsJobsCache.jobsandcache": "Jobs & Cache", "components.Settings.SettingsJobsCache.jobsandcache": "Jobs & Cache",
"components.Settings.SettingsJobsCache.jobstarted": "{jobname} started.", "components.Settings.SettingsJobsCache.jobstarted": "{jobname} started.",
"components.Settings.SettingsJobsCache.jobtype": "Type", "components.Settings.SettingsJobsCache.jobtype": "Type",
@@ -831,7 +832,7 @@
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)", "components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsMain.general": "General", "components.Settings.SettingsMain.general": "General",
"components.Settings.SettingsMain.generalsettings": "General Settings", "components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.", "components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Overseerr.",
"components.Settings.SettingsMain.hideAvailable": "Hide Available Media", "components.Settings.SettingsMain.hideAvailable": "Hide Available Media",
"components.Settings.SettingsMain.locale": "Display Language", "components.Settings.SettingsMain.locale": "Display Language",
"components.Settings.SettingsMain.originallanguage": "Discover Language", "components.Settings.SettingsMain.originallanguage": "Discover Language",
@@ -844,14 +845,14 @@
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.", "components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
"components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!", "components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsMain.trustProxy": "Enable Proxy Support", "components.Settings.SettingsMain.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsMain.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy", "components.Settings.SettingsMain.trustProxyTip": "Allow Overseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title", "components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title",
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL", "components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", "components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions", "components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users", "components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In", "components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
"components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password, instead of Plex OAuth", "components.Settings.SettingsUsers.localLoginTip": "Allow users to sign in using their email address and password, instead of {mediaServerName} OAuth",
"components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Movie Request Limit", "components.Settings.SettingsUsers.movieRequestLimitLabel": "Global Movie Request Limit",
"components.Settings.SettingsUsers.newPlexLogin": "Enable New {mediaServerName} Sign-In", "components.Settings.SettingsUsers.newPlexLogin": "Enable New {mediaServerName} Sign-In",
"components.Settings.SettingsUsers.newPlexLoginTip": "Allow {mediaServerName} users to sign in without first being imported", "components.Settings.SettingsUsers.newPlexLoginTip": "Allow {mediaServerName} users to sign in without first being imported",
@@ -946,7 +947,7 @@
"components.Settings.jellyfinsettingsDescription": "Configure the settings for your {mediaServerName} server. {mediaServerName} scans your {mediaServerName} libraries to see what content is available.", "components.Settings.jellyfinsettingsDescription": "Configure the settings for your {mediaServerName} server. {mediaServerName} scans your {mediaServerName} libraries to see what content is available.",
"components.Settings.librariesRemaining": "Libraries Remaining: {count}", "components.Settings.librariesRemaining": "Libraries Remaining: {count}",
"components.Settings.manualscan": "Manual Library Scan", "components.Settings.manualscan": "Manual Library Scan",
"components.Settings.manualscanDescription": "Normally, this will only be run once every 24 hours. Jellyseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!", "components.Settings.manualscanDescription": "Normally, this will only be run once every 24 hours. Overseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!",
"components.Settings.manualscanDescriptionJellyfin": "Normally, this will only be run once every 24 hours. Jellyseerr will check your {mediaServerName} server's recently added more aggressively. If this is your first time configuring Jellyseerr, a one-time full manual library scan is recommended!", "components.Settings.manualscanDescriptionJellyfin": "Normally, this will only be run once every 24 hours. Jellyseerr will check your {mediaServerName} server's recently added more aggressively. If this is your first time configuring Jellyseerr, a one-time full manual library scan is recommended!",
"components.Settings.manualscanJellyfin": "Manual Library Scan", "components.Settings.manualscanJellyfin": "Manual Library Scan",
"components.Settings.mediaTypeMovie": "movie", "components.Settings.mediaTypeMovie": "movie",
@@ -969,12 +970,12 @@
"components.Settings.notrunning": "Not Running", "components.Settings.notrunning": "Not Running",
"components.Settings.plex": "Plex", "components.Settings.plex": "Plex",
"components.Settings.plexlibraries": "Plex Libraries", "components.Settings.plexlibraries": "Plex Libraries",
"components.Settings.plexlibrariesDescription": "The libraries Jellyseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.", "components.Settings.plexlibrariesDescription": "The libraries Overseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.",
"components.Settings.plexsettings": "Plex Settings", "components.Settings.plexsettings": "Plex Settings",
"components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Jellyseerr scans your Plex libraries to determine content availability.", "components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Overseerr scans your Plex libraries to determine content availability.",
"components.Settings.port": "Port", "components.Settings.port": "Port",
"components.Settings.radarrsettings": "Radarr Settings", "components.Settings.radarrsettings": "Radarr Settings",
"components.Settings.restartrequiredTooltip": "Jellyseerr must be restarted for changes to this setting to take effect", "components.Settings.restartrequiredTooltip": "Overseerr must be restarted for changes to this setting to take effect",
"components.Settings.save": "Save Changes", "components.Settings.save": "Save Changes",
"components.Settings.saving": "Saving…", "components.Settings.saving": "Saving…",
"components.Settings.scan": "Sync Libraries", "components.Settings.scan": "Sync Libraries",
@@ -996,7 +997,7 @@
"components.Settings.syncing": "Syncing", "components.Settings.syncing": "Syncing",
"components.Settings.tautulliApiKey": "API Key", "components.Settings.tautulliApiKey": "API Key",
"components.Settings.tautulliSettings": "Tautulli Settings", "components.Settings.tautulliSettings": "Tautulli Settings",
"components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.", "components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Overseerr fetches watch history data for your Plex media from Tautulli.",
"components.Settings.timeout": "Timeout", "components.Settings.timeout": "Timeout",
"components.Settings.toastPlexConnecting": "Attempting to connect to Plex…", "components.Settings.toastPlexConnecting": "Attempting to connect to Plex…",
"components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.", "components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.",
@@ -1042,10 +1043,15 @@
"components.StatusChecker.reloadApp": "Reload {applicationTitle}", "components.StatusChecker.reloadApp": "Reload {applicationTitle}",
"components.StatusChecker.restartRequired": "Server Restart Required", "components.StatusChecker.restartRequired": "Server Restart Required",
"components.StatusChecker.restartRequiredDescription": "Please restart the server to apply the updated settings.", "components.StatusChecker.restartRequiredDescription": "Please restart the server to apply the updated settings.",
"components.TitleCard.addToWatchList": "Add to watchlist",
"components.TitleCard.cleardata": "Clear Data", "components.TitleCard.cleardata": "Clear Data",
"components.TitleCard.mediaerror": "{mediaType} Not Found", "components.TitleCard.mediaerror": "{mediaType} Not Found",
"components.TitleCard.tmdbid": "TMDB ID", "components.TitleCard.tmdbid": "TMDB ID",
"components.TitleCard.tvdbid": "TheTVDB ID", "components.TitleCard.tvdbid": "TheTVDB ID",
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.TitleCard.watchlistError": "Something went wrong try again.",
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"components.TvDetails.Season.noepisodes": "Episode list unavailable.", "components.TvDetails.Season.noepisodes": "Episode list unavailable.",
"components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.", "components.TvDetails.Season.somethingwentwrong": "Something went wrong while retrieving season data.",
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast", "components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
@@ -1229,6 +1235,7 @@
"components.UserProfile.UserSettings.unauthorizedDescription": "You do not have permission to modify this user's settings.", "components.UserProfile.UserSettings.unauthorizedDescription": "You do not have permission to modify this user's settings.",
"components.UserProfile.emptywatchlist": "Media added to your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> will appear here.", "components.UserProfile.emptywatchlist": "Media added to your <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> will appear here.",
"components.UserProfile.limit": "{remaining} of {limit}", "components.UserProfile.limit": "{remaining} of {limit}",
"components.UserProfile.localWatchlist": "{username}'s Watchlist",
"components.UserProfile.movierequests": "Movie Requests", "components.UserProfile.movierequests": "Movie Requests",
"components.UserProfile.pastdays": "{type} (past {days} days)", "components.UserProfile.pastdays": "{type} (past {days} days)",
"components.UserProfile.plexwatchlist": "Plex Watchlist", "components.UserProfile.plexwatchlist": "Plex Watchlist",
@@ -1238,11 +1245,6 @@
"components.UserProfile.seriesrequest": "Series Requests", "components.UserProfile.seriesrequest": "Series Requests",
"components.UserProfile.totalrequests": "Total Requests", "components.UserProfile.totalrequests": "Total Requests",
"components.UserProfile.unlimited": "Unlimited", "components.UserProfile.unlimited": "Unlimited",
"components.TitleCard.addToWatchList": "Add to watchlist",
"components.TitleCard.watchlistCancel": "watchlist for <strong>{title}</strong> canceled.",
"components.TitleCard.watchlistDeleted": "<strong>{title}</strong> Removed from watchlist successfully!",
"components.TitleCard.watchlistError": "Something went wrong try again.",
"components.TitleCard.watchlistSuccess": "<strong>{title}</strong> added to watchlist successfully!",
"i18n.advanced": "Advanced", "i18n.advanced": "Advanced",
"i18n.all": "All", "i18n.all": "All",
"i18n.approve": "Approve", "i18n.approve": "Approve",