fix(frontend): more issues-related fixes (#2234)

* fix(frontend): more issues-related fixes

* fix: permission VIEW_ISSUES is also sufficient for viewing issues in slideover

* fix(frontend): only display issue notif types user is eligible to receive

* fix: don't display issues block in slideover if no open issues

* fix: move year out of link in issue details header

* fix: use 'view' global string for issue block button

* fix: issue/request/user list sort options
pull/2255/head
TheCatLady 3 years ago committed by GitHub
parent 8c49309c35
commit 3ec4a9c76e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,7 +13,7 @@ const Badge: React.FC<BadgeProps> = ({
children, children,
}) => { }) => {
const badgeStyle = [ const badgeStyle = [
'px-2 inline-flex text-xs leading-5 font-semibold rounded-full', 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap',
]; ];
if (url) { if (url) {

@ -8,6 +8,7 @@ import Link from 'next/link';
import React from 'react'; import React from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import type Issue from '../../../server/entity/Issue'; import type Issue from '../../../server/entity/Issue';
import globalMessages from '../../i18n/globalMessages';
import Button from '../Common/Button'; import Button from '../Common/Button';
import { issueOptions } from '../IssueModal/constants'; import { issueOptions } from '../IssueModal/constants';
@ -56,7 +57,7 @@ const IssueBlock: React.FC<IssueBlockProps> = ({ issue }) => {
<Link href={`/issues/${issue.id}`} passHref> <Link href={`/issues/${issue.id}`} passHref>
<Button buttonType="primary" buttonSize="sm" as="a"> <Button buttonType="primary" buttonSize="sm" as="a">
<EyeIcon /> <EyeIcon />
<span>View</span> <span>{intl.formatMessage(globalMessages.view)}</span>
</Button> </Button>
</Link> </Link>
</div> </div>

@ -44,7 +44,7 @@ const messages = defineMessages({
reopenissueandcomment: 'Reopen with Comment', reopenissueandcomment: 'Reopen with Comment',
issuepagetitle: 'Issue', issuepagetitle: 'Issue',
playonplex: 'Play on Plex', playonplex: 'Play on Plex',
play4konplex: 'Play 4K on Plex', play4konplex: 'Play in 4K on Plex',
openinarr: 'Open in {arr}', openinarr: 'Open in {arr}',
openin4karr: 'Open in 4K {arr}', openin4karr: 'Open in 4K {arr}',
toasteditdescriptionsuccess: 'Issue description edited successfully!', toasteditdescriptionsuccess: 'Issue description edited successfully!',
@ -228,7 +228,7 @@ const IssueDetails: React.FC = () => {
<div className="media-title"> <div className="media-title">
<div className="media-status"> <div className="media-status">
{issueData.status === IssueStatus.OPEN && ( {issueData.status === IssueStatus.OPEN && (
<Badge badgeType="primary"> <Badge badgeType="warning">
{intl.formatMessage(globalMessages.open)} {intl.formatMessage(globalMessages.open)}
</Badge> </Badge>
)} )}
@ -244,15 +244,11 @@ const IssueDetails: React.FC = () => {
issueData.media.mediaType === MediaType.MOVIE ? 'movie' : 'tv' issueData.media.mediaType === MediaType.MOVIE ? 'movie' : 'tv'
}/${data.id}`} }/${data.id}`}
> >
<a className="hover:underline"> <a className="hover:underline">{title}</a>
{title}{' '} </Link>{' '}
{releaseYear && ( {releaseYear && (
<span className="media-year"> <span className="media-year">({releaseYear.slice(0, 4)})</span>
({releaseYear.slice(0, 4)}) )}
</span>
)}
</a>
</Link>
</h1> </h1>
<span className="media-attributes"> <span className="media-attributes">
{intl.formatMessage(messages.openedby, { {intl.formatMessage(messages.openedby, {

@ -183,7 +183,7 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
{intl.formatMessage(messages.issuestatus)} {intl.formatMessage(messages.issuestatus)}
</span> </span>
{issue.status === IssueStatus.OPEN ? ( {issue.status === IssueStatus.OPEN ? (
<Badge badgeType="primary"> <Badge badgeType="warning">
{intl.formatMessage(globalMessages.open)} {intl.formatMessage(globalMessages.open)}
</Badge> </Badge>
) : ( ) : (

@ -19,7 +19,7 @@ import IssueItem from './IssueItem';
const messages = defineMessages({ const messages = defineMessages({
issues: 'Issues', issues: 'Issues',
sortAdded: 'Request Date', sortAdded: 'Most Recent',
sortModified: 'Last Modified', sortModified: 'Last Modified',
showallissues: 'Show All Issues', showallissues: 'Show All Issues',
}); });

@ -9,9 +9,12 @@ import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications'; import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { MediaStatus } from '../../../../server/constants/media';
import type Issue from '../../../../server/entity/Issue'; import type Issue from '../../../../server/entity/Issue';
import { MovieDetails } from '../../../../server/models/Movie'; import { MovieDetails } from '../../../../server/models/Movie';
import { TvDetails } from '../../../../server/models/Tv'; import { TvDetails } from '../../../../server/models/Tv';
import useSettings from '../../../hooks/useSettings';
import { Permission, useUser } from '../../../hooks/useUser';
import globalMessages from '../../../i18n/globalMessages'; import globalMessages from '../../../i18n/globalMessages';
import Button from '../../Common/Button'; import Button from '../../Common/Button';
import Modal from '../../Common/Modal'; import Modal from '../../Common/Modal';
@ -21,7 +24,9 @@ const messages = defineMessages({
validationMessageRequired: 'You must provide a description', validationMessageRequired: 'You must provide a description',
issomethingwrong: 'Is there a problem with {title}?', issomethingwrong: 'Is there a problem with {title}?',
whatswrong: "What's wrong?", whatswrong: "What's wrong?",
providedetail: 'Provide a detailed explanation of the issue.', providedetail:
'Please provide a detailed explanation of the issue you encountered.',
extras: 'Extras',
season: 'Season {seasonNumber}', season: 'Season {seasonNumber}',
episode: 'Episode {episodeNumber}', episode: 'Episode {episodeNumber}',
allseasons: 'All Seasons', allseasons: 'All Seasons',
@ -56,6 +61,8 @@ const CreateIssueModal: React.FC<CreateIssueModalProps> = ({
tmdbId, tmdbId,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const settings = useSettings();
const { hasPermission } = useUser();
const { addToast } = useToasts(); const { addToast } = useToasts();
const { data, error } = useSWR<MovieDetails | TvDetails>( const { data, error } = useSWR<MovieDetails | TvDetails>(
tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null
@ -65,6 +72,20 @@ const CreateIssueModal: React.FC<CreateIssueModalProps> = ({
return null; return null;
} }
const availableSeasons = (data?.mediaInfo?.seasons ?? [])
.filter(
(season) =>
season.status === MediaStatus.AVAILABLE ||
season.status === MediaStatus.PARTIALLY_AVAILABLE ||
(settings.currentSettings.series4kEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
type: 'or',
}) &&
(season.status4k === MediaStatus.AVAILABLE ||
season.status4k === MediaStatus.PARTIALLY_AVAILABLE))
)
.map((season) => season.seasonNumber);
const CreateIssueModalSchema = Yup.object().shape({ const CreateIssueModalSchema = Yup.object().shape({
message: Yup.string().required( message: Yup.string().required(
intl.formatMessage(messages.validationMessageRequired) intl.formatMessage(messages.validationMessageRequired)
@ -76,7 +97,7 @@ const CreateIssueModal: React.FC<CreateIssueModalProps> = ({
initialValues={{ initialValues={{
selectedIssue: issueOptions[0], selectedIssue: issueOptions[0],
message: '', message: '',
problemSeason: 0, problemSeason: availableSeasons.length === 1 ? availableSeasons[0] : 0,
problemEpisode: 0, problemEpisode: 0,
}} }}
validationSchema={CreateIssueModalSchema} validationSchema={CreateIssueModalSchema}
@ -162,18 +183,23 @@ const CreateIssueModal: React.FC<CreateIssueModalProps> = ({
as="select" as="select"
id="problemSeason" id="problemSeason"
name="problemSeason" name="problemSeason"
disabled={availableSeasons.length === 1}
> >
<option value={0}> {availableSeasons.length > 1 && (
{intl.formatMessage(messages.allseasons)} <option value={0}>
</option> {intl.formatMessage(messages.allseasons)}
{data.seasons.map((season) => ( </option>
)}
{availableSeasons.map((season) => (
<option <option
value={season.seasonNumber} value={season}
key={`problem-season-${season.seasonNumber}`} key={`problem-season-${season}`}
> >
{intl.formatMessage(messages.season, { {season === 0
seasonNumber: season.seasonNumber, ? intl.formatMessage(messages.extras)
})} : intl.formatMessage(messages.season, {
seasonNumber: season,
})}
</option> </option>
))} ))}
</Field> </Field>

@ -19,6 +19,7 @@ import RequestBlock from '../RequestBlock';
const messages = defineMessages({ const messages = defineMessages({
manageModalTitle: 'Manage {mediaType}', manageModalTitle: 'Manage {mediaType}',
manageModalIssues: 'Open Issues',
manageModalRequests: 'Requests', manageModalRequests: 'Requests',
manageModalNoRequests: 'No requests.', manageModalNoRequests: 'No requests.',
manageModalClearMedia: 'Clear Media Data', manageModalClearMedia: 'Clear Media Data',
@ -77,6 +78,11 @@ const ManageSlideOver: React.FC<
revalidate(); revalidate();
}; };
const openIssues =
data.mediaInfo?.issues?.filter(
(issue) => issue.status === IssueStatus.OPEN
) ?? [];
return ( return (
<SlideOver <SlideOver
show={show} show={show}
@ -155,14 +161,17 @@ const ManageSlideOver: React.FC<
)} )}
</div> </div>
)} )}
{(data.mediaInfo?.issues ?? []).length > 0 && ( {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], {
<> type: 'or',
<h3 className="mb-2 text-xl">Open Issues</h3> }) &&
<div className="mb-4 overflow-hidden bg-gray-600 rounded-md shadow"> openIssues.length > 0 && (
<ul> <>
{data.mediaInfo?.issues <h3 className="mb-2 text-xl">
?.filter((issue) => issue.status === IssueStatus.OPEN) {intl.formatMessage(messages.manageModalIssues)}
.map((issue) => ( </h3>
<div className="mb-4 overflow-hidden bg-gray-600 rounded-md shadow">
<ul>
{openIssues.map((issue) => (
<li <li
key={`manage-issue-${issue.id}`} key={`manage-issue-${issue.id}`}
className="border-b border-gray-700 last:border-b-0" className="border-b border-gray-700 last:border-b-0"
@ -170,10 +179,10 @@ const ManageSlideOver: React.FC<
<IssueBlock issue={issue} /> <IssueBlock issue={issue} />
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</> </>
)} )}
<h3 className="mb-2 text-xl"> <h3 className="mb-2 text-xl">
{intl.formatMessage(messages.manageModalRequests)} {intl.formatMessage(messages.manageModalRequests)}
</h3> </h3>

@ -330,7 +330,14 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
onUpdate={() => revalidate()} onUpdate={() => revalidate()}
/> />
{(data.mediaInfo?.status === MediaStatus.AVAILABLE || {(data.mediaInfo?.status === MediaStatus.AVAILABLE ||
data.mediaInfo?.status4k === MediaStatus.AVAILABLE) && (settings.currentSettings.movie4kEnabled &&
hasPermission(
[Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE],
{
type: 'or',
}
) &&
data.mediaInfo?.status4k === MediaStatus.AVAILABLE)) &&
hasPermission( hasPermission(
[Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES],
{ {
@ -338,7 +345,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
} }
) && ( ) && (
<Button <Button
buttonType="danger" buttonType="warning"
className="ml-2 first:ml-0" className="ml-2 first:ml-0"
onClick={() => setShowIssueModal(true)} onClick={() => setShowIssueModal(true)}
> >
@ -348,20 +355,26 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
{hasPermission(Permission.MANAGE_REQUESTS) && ( {hasPermission(Permission.MANAGE_REQUESTS) && (
<Button <Button
buttonType="default" buttonType="default"
className="ml-2 first:ml-0" className="relative ml-2 first:ml-0"
onClick={() => setShowManager(true)} onClick={() => setShowManager(true)}
> >
<CogIcon className="!mr-0" /> <CogIcon className="!mr-0" />
{( {hasPermission(
data.mediaInfo?.issues.filter( [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
(issue) => issue.status === IssueStatus.OPEN {
) ?? [] type: 'or',
).length > 0 && ( }
<> ) &&
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1" /> (
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1 animate-ping" /> data.mediaInfo?.issues.filter(
</> (issue) => issue.status === IssueStatus.OPEN
)} ) ?? []
).length > 0 && (
<>
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1" />
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1 animate-ping" />
</>
)}
</Button> </Button>
)} )}
</div> </div>

@ -274,6 +274,11 @@ const NotificationTypeSelector: React.FC<NotificationTypeSelectorProps> = ({
: messages.issuecommentDescription : messages.issuecommentDescription
), ),
value: Notification.ISSUE_COMMENT, value: Notification.ISSUE_COMMENT,
hidden:
user &&
!hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], {
type: 'or',
}),
hasNotifyUser: hasNotifyUser:
!user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true,
}, },
@ -286,6 +291,11 @@ const NotificationTypeSelector: React.FC<NotificationTypeSelectorProps> = ({
: messages.issueresolvedDescription : messages.issueresolvedDescription
), ),
value: Notification.ISSUE_RESOLVED, value: Notification.ISSUE_RESOLVED,
hidden:
user &&
!hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], {
type: 'or',
}),
hasNotifyUser: true, hasNotifyUser: true,
}, },
]; ];

@ -22,7 +22,7 @@ import RequestItem from './RequestItem';
const messages = defineMessages({ const messages = defineMessages({
requests: 'Requests', requests: 'Requests',
showallrequests: 'Show All Requests', showallrequests: 'Show All Requests',
sortAdded: 'Request Date', sortAdded: 'Most Recent',
sortModified: 'Last Modified', sortModified: 'Last Modified',
}); });

@ -331,9 +331,14 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
is4kShowComplete={is4kComplete} is4kShowComplete={is4kComplete}
/> />
{(data.mediaInfo?.status === MediaStatus.AVAILABLE || {(data.mediaInfo?.status === MediaStatus.AVAILABLE ||
data.mediaInfo?.status4k === MediaStatus.AVAILABLE ||
data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE || data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE ||
data?.mediaInfo?.status4k === MediaStatus.PARTIALLY_AVAILABLE) && (settings.currentSettings.series4kEnabled &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
type: 'or',
}) &&
(data.mediaInfo?.status4k === MediaStatus.AVAILABLE ||
data?.mediaInfo?.status4k ===
MediaStatus.PARTIALLY_AVAILABLE))) &&
hasPermission( hasPermission(
[Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES],
{ {
@ -341,7 +346,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
} }
) && ( ) && (
<Button <Button
buttonType="danger" buttonType="warning"
className="ml-2 first:ml-0" className="ml-2 first:ml-0"
onClick={() => setShowIssueModal(true)} onClick={() => setShowIssueModal(true)}
> >
@ -351,20 +356,26 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
{hasPermission(Permission.MANAGE_REQUESTS) && ( {hasPermission(Permission.MANAGE_REQUESTS) && (
<Button <Button
buttonType="default" buttonType="default"
className="ml-2 first:ml-0" className="relative ml-2 first:ml-0"
onClick={() => setShowManager(true)} onClick={() => setShowManager(true)}
> >
<CogIcon className="!mr-0" /> <CogIcon className="!mr-0" />
{( {hasPermission(
data.mediaInfo?.issues.filter( [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
(issue) => issue.status === IssueStatus.OPEN {
) ?? [] type: 'or',
).length > 0 && ( }
<> ) &&
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1" /> (
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1 animate-ping" /> data.mediaInfo?.issues.filter(
</> (issue) => issue.status === IssueStatus.OPEN
)} ) ?? []
).length > 0 && (
<>
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1" />
<div className="absolute w-3 h-3 bg-red-600 rounded-full -right-1 -top-1 animate-ping" />
</>
)}
</Button> </Button>
)} )}
</div> </div>

@ -46,8 +46,7 @@ const messages = defineMessages({
totalrequests: 'Requests', totalrequests: 'Requests',
accounttype: 'Type', accounttype: 'Type',
role: 'Role', role: 'Role',
created: 'Created', created: 'Joined',
lastupdated: 'Updated',
bulkedit: 'Bulk Edit', bulkedit: 'Bulk Edit',
owner: 'Owner', owner: 'Owner',
admin: 'Admin', admin: 'Admin',
@ -75,8 +74,7 @@ const messages = defineMessages({
autogeneratepassword: 'Automatically Generate Password', autogeneratepassword: 'Automatically Generate Password',
autogeneratepasswordTip: 'Email a server-generated password to the user', autogeneratepasswordTip: 'Email a server-generated password to the user',
validationEmail: 'You must provide a valid email address', validationEmail: 'You must provide a valid email address',
sortCreated: 'Creation Date', sortCreated: 'Join Date',
sortUpdated: 'Last Updated',
sortDisplayName: 'Display Name', sortDisplayName: 'Display Name',
sortRequests: 'Request Count', sortRequests: 'Request Count',
localLoginDisabled: localLoginDisabled:
@ -91,7 +89,7 @@ const UserList: React.FC = () => {
const settings = useSettings(); const settings = useSettings();
const { addToast } = useToasts(); const { addToast } = useToasts();
const { user: currentUser, hasPermission: currentHasPermission } = useUser(); const { user: currentUser, hasPermission: currentHasPermission } = useUser();
const [currentSort, setCurrentSort] = useState<Sort>('created'); const [currentSort, setCurrentSort] = useState<Sort>('displayname');
const [currentPageSize, setCurrentPageSize] = useState<number>(10); const [currentPageSize, setCurrentPageSize] = useState<number>(10);
const page = router.query.page ? Number(router.query.page) : 1; const page = router.query.page ? Number(router.query.page) : 1;
@ -522,9 +520,6 @@ const UserList: React.FC = () => {
<option value="created"> <option value="created">
{intl.formatMessage(messages.sortCreated)} {intl.formatMessage(messages.sortCreated)}
</option> </option>
<option value="updated">
{intl.formatMessage(messages.sortUpdated)}
</option>
<option value="requests"> <option value="requests">
{intl.formatMessage(messages.sortRequests)} {intl.formatMessage(messages.sortRequests)}
</option> </option>
@ -556,7 +551,6 @@ const UserList: React.FC = () => {
<Table.TH>{intl.formatMessage(messages.accounttype)}</Table.TH> <Table.TH>{intl.formatMessage(messages.accounttype)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.role)}</Table.TH> <Table.TH>{intl.formatMessage(messages.role)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.created)}</Table.TH> <Table.TH>{intl.formatMessage(messages.created)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.lastupdated)}</Table.TH>
<Table.TH className="text-right"> <Table.TH className="text-right">
{(data.results ?? []).length > 1 && ( {(data.results ?? []).length > 1 && (
<Button <Button
@ -652,13 +646,6 @@ const UserList: React.FC = () => {
day: 'numeric', day: 'numeric',
})} })}
</Table.TD> </Table.TD>
<Table.TD>
{intl.formatDate(user.updatedAt, {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</Table.TD>
<Table.TD alignText="right"> <Table.TD alignText="right">
<Button <Button
buttonType="warning" buttonType="warning"

@ -57,7 +57,7 @@
"components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by {username}", "components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by {username}",
"components.IssueDetails.openin4karr": "Open in 4K {arr}", "components.IssueDetails.openin4karr": "Open in 4K {arr}",
"components.IssueDetails.openinarr": "Open in {arr}", "components.IssueDetails.openinarr": "Open in {arr}",
"components.IssueDetails.play4konplex": "Play 4K on Plex", "components.IssueDetails.play4konplex": "Play in 4K on Plex",
"components.IssueDetails.playonplex": "Play on Plex", "components.IssueDetails.playonplex": "Play on Plex",
"components.IssueDetails.problemepisode": "Affected Episode", "components.IssueDetails.problemepisode": "Affected Episode",
"components.IssueDetails.problemseason": "Affected Season", "components.IssueDetails.problemseason": "Affected Season",
@ -82,15 +82,16 @@
"components.IssueList.IssueItem.viewissue": "View Issue", "components.IssueList.IssueItem.viewissue": "View Issue",
"components.IssueList.issues": "Issues", "components.IssueList.issues": "Issues",
"components.IssueList.showallissues": "Show All Issues", "components.IssueList.showallissues": "Show All Issues",
"components.IssueList.sortAdded": "Request Date", "components.IssueList.sortAdded": "Most Recent",
"components.IssueList.sortModified": "Last Modified", "components.IssueList.sortModified": "Last Modified",
"components.IssueModal.CreateIssueModal.allepisodes": "All Episodes", "components.IssueModal.CreateIssueModal.allepisodes": "All Episodes",
"components.IssueModal.CreateIssueModal.allseasons": "All Seasons", "components.IssueModal.CreateIssueModal.allseasons": "All Seasons",
"components.IssueModal.CreateIssueModal.episode": "Episode {episodeNumber}", "components.IssueModal.CreateIssueModal.episode": "Episode {episodeNumber}",
"components.IssueModal.CreateIssueModal.extras": "Extras",
"components.IssueModal.CreateIssueModal.issomethingwrong": "Is there a problem with {title}?", "components.IssueModal.CreateIssueModal.issomethingwrong": "Is there a problem with {title}?",
"components.IssueModal.CreateIssueModal.problemepisode": "Affected Episode", "components.IssueModal.CreateIssueModal.problemepisode": "Affected Episode",
"components.IssueModal.CreateIssueModal.problemseason": "Affected Season", "components.IssueModal.CreateIssueModal.problemseason": "Affected Season",
"components.IssueModal.CreateIssueModal.providedetail": "Provide a detailed explanation of the issue.", "components.IssueModal.CreateIssueModal.providedetail": "Please provide a detailed explanation of the issue you encountered.",
"components.IssueModal.CreateIssueModal.reportissue": "Report an Issue", "components.IssueModal.CreateIssueModal.reportissue": "Report an Issue",
"components.IssueModal.CreateIssueModal.season": "Season {seasonNumber}", "components.IssueModal.CreateIssueModal.season": "Season {seasonNumber}",
"components.IssueModal.CreateIssueModal.submitissue": "Submit Issue", "components.IssueModal.CreateIssueModal.submitissue": "Submit Issue",
@ -134,6 +135,7 @@
"components.ManageSlideOver.downloadstatus": "Download Status", "components.ManageSlideOver.downloadstatus": "Download Status",
"components.ManageSlideOver.manageModalClearMedia": "Clear Media Data", "components.ManageSlideOver.manageModalClearMedia": "Clear Media Data",
"components.ManageSlideOver.manageModalClearMediaWarning": "* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", "components.ManageSlideOver.manageModalClearMediaWarning": "* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.",
"components.ManageSlideOver.manageModalIssues": "Open Issues",
"components.ManageSlideOver.manageModalNoRequests": "No requests.", "components.ManageSlideOver.manageModalNoRequests": "No requests.",
"components.ManageSlideOver.manageModalRequests": "Requests", "components.ManageSlideOver.manageModalRequests": "Requests",
"components.ManageSlideOver.manageModalTitle": "Manage {mediaType}", "components.ManageSlideOver.manageModalTitle": "Manage {mediaType}",
@ -287,7 +289,7 @@
"components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}", "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestList.requests": "Requests", "components.RequestList.requests": "Requests",
"components.RequestList.showallrequests": "Show All Requests", "components.RequestList.showallrequests": "Show All Requests",
"components.RequestList.sortAdded": "Request Date", "components.RequestList.sortAdded": "Most Recent",
"components.RequestList.sortModified": "Last Modified", "components.RequestList.sortModified": "Last Modified",
"components.RequestModal.AdvancedRequester.advancedoptions": "Advanced", "components.RequestModal.AdvancedRequester.advancedoptions": "Advanced",
"components.RequestModal.AdvancedRequester.animenote": "* This series is an anime.", "components.RequestModal.AdvancedRequester.animenote": "* This series is an anime.",
@ -799,7 +801,7 @@
"components.UserList.autogeneratepasswordTip": "Email a server-generated password to the user", "components.UserList.autogeneratepasswordTip": "Email a server-generated password to the user",
"components.UserList.bulkedit": "Bulk Edit", "components.UserList.bulkedit": "Bulk Edit",
"components.UserList.create": "Create", "components.UserList.create": "Create",
"components.UserList.created": "Created", "components.UserList.created": "Joined",
"components.UserList.createlocaluser": "Create Local User", "components.UserList.createlocaluser": "Create Local User",
"components.UserList.creating": "Creating…", "components.UserList.creating": "Creating…",
"components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.", "components.UserList.deleteconfirm": "Are you sure you want to delete this user? All of their request data will be permanently removed.",
@ -810,7 +812,6 @@
"components.UserList.importedfromplex": "{userCount, plural, one {# new user} other {# new users}} imported from Plex successfully!", "components.UserList.importedfromplex": "{userCount, plural, one {# new user} other {# new users}} imported from Plex successfully!",
"components.UserList.importfromplex": "Import Users from Plex", "components.UserList.importfromplex": "Import Users from Plex",
"components.UserList.importfromplexerror": "Something went wrong while importing users from Plex.", "components.UserList.importfromplexerror": "Something went wrong while importing users from Plex.",
"components.UserList.lastupdated": "Updated",
"components.UserList.localLoginDisabled": "The <strong>Enable Local Sign-In</strong> setting is currently disabled.", "components.UserList.localLoginDisabled": "The <strong>Enable Local Sign-In</strong> setting is currently disabled.",
"components.UserList.localuser": "Local User", "components.UserList.localuser": "Local User",
"components.UserList.nouserstoimport": "No new users to import from Plex.", "components.UserList.nouserstoimport": "No new users to import from Plex.",
@ -819,10 +820,9 @@
"components.UserList.passwordinfodescription": "Configure an application URL and enable email notifications to allow automatic password generation.", "components.UserList.passwordinfodescription": "Configure an application URL and enable email notifications to allow automatic password generation.",
"components.UserList.plexuser": "Plex User", "components.UserList.plexuser": "Plex User",
"components.UserList.role": "Role", "components.UserList.role": "Role",
"components.UserList.sortCreated": "Creation Date", "components.UserList.sortCreated": "Join Date",
"components.UserList.sortDisplayName": "Display Name", "components.UserList.sortDisplayName": "Display Name",
"components.UserList.sortRequests": "Request Count", "components.UserList.sortRequests": "Request Count",
"components.UserList.sortUpdated": "Last Updated",
"components.UserList.totalrequests": "Requests", "components.UserList.totalrequests": "Requests",
"components.UserList.user": "User", "components.UserList.user": "User",
"components.UserList.usercreatedfailed": "Something went wrong while creating the user.", "components.UserList.usercreatedfailed": "Something went wrong while creating the user.",

Loading…
Cancel
Save