mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-01 20:28:40 -05:00
fix(lang): UI string edits, round 2 (#1202)
This commit is contained in:
@@ -36,7 +36,7 @@ const messages = defineMessages({
|
||||
requestcollection4k: 'Request Collection in 4K',
|
||||
requestswillbecreated4k:
|
||||
'The following titles will have 4K requests created for them:',
|
||||
requestSuccess: '<strong>{title}</strong> successfully requested!',
|
||||
requestSuccess: '<strong>{title}</strong> requested successfully!',
|
||||
});
|
||||
|
||||
interface CollectionDetailsProps {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
LanguageContext,
|
||||
AvailableLocales,
|
||||
} from '../../../context/LanguageContext';
|
||||
import { FormattedMessage, defineMessages } from 'react-intl';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
changelanguage: 'Change Language',
|
||||
@@ -80,6 +80,7 @@ const availableLanguages: AvailableLanguageObject = {
|
||||
};
|
||||
|
||||
const LanguagePicker: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const { locale, setLocale } = useContext(LanguageContext);
|
||||
const [isDropdownOpen, setDropdownOpen] = useState(false);
|
||||
@@ -128,7 +129,7 @@ const LanguagePicker: React.FC = () => {
|
||||
htmlFor="language"
|
||||
className="block pb-2 text-sm font-medium leading-5 text-gray-300"
|
||||
>
|
||||
<FormattedMessage {...messages.changelanguage} />
|
||||
{intl.formatMessage(messages.changelanguage)}
|
||||
</label>
|
||||
<select
|
||||
id="language"
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { ReactNode, useRef } from 'react';
|
||||
import Transition from '../../Transition';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useUser, Permission } from '../../../hooks/useUser';
|
||||
import useClickOutside from '../../../hooks/useClickOutside';
|
||||
|
||||
@@ -119,6 +119,7 @@ const SidebarLinks: SidebarLinkProps[] = [
|
||||
const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
const navRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
useClickOutside(navRef, () => setClosed());
|
||||
return (
|
||||
@@ -212,9 +213,9 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
`}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
<FormattedMessage
|
||||
{...messages[sidebarLink.messagesKey]}
|
||||
/>
|
||||
{intl.formatMessage(
|
||||
messages[sidebarLink.messagesKey]
|
||||
)}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
@@ -266,9 +267,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
|
||||
`}
|
||||
>
|
||||
{sidebarLink.svgIcon}
|
||||
<FormattedMessage
|
||||
{...messages[sidebarLink.messagesKey]}
|
||||
/>
|
||||
{intl.formatMessage(messages[sidebarLink.messagesKey])}
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,12 @@ import UserDropdown from './UserDropdown';
|
||||
import Sidebar from './Sidebar';
|
||||
import LanguagePicker from './LanguagePicker';
|
||||
import { useRouter } from 'next/router';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { Permission, useUser } from '../../hooks/useUser';
|
||||
|
||||
const messages = defineMessages({
|
||||
alphawarning:
|
||||
'This is ALPHA software. Features may be broken and/or unstable. Please report issues on GitHub!',
|
||||
'This is ALPHA software. Features may be broken and/or unstable. Please report any issues on GitHub!',
|
||||
});
|
||||
|
||||
const Layout: React.FC = ({ children }) => {
|
||||
@@ -17,6 +17,7 @@ const Layout: React.FC = ({ children }) => {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const { hasPermission } = useUser();
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
|
||||
useEffect(() => {
|
||||
const updateScrolled = () => {
|
||||
@@ -101,7 +102,7 @@ const Layout: React.FC = ({ children }) => {
|
||||
</div>
|
||||
<div className="flex-1 ml-3 md:flex md:justify-between">
|
||||
<p className="text-sm leading-5 text-white">
|
||||
<FormattedMessage {...messages.alphawarning} />
|
||||
{intl.formatMessage(messages.alphawarning)}
|
||||
</p>
|
||||
<p className="mt-3 text-sm leading-5 md:mt-0 md:ml-6">
|
||||
<a
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useUser } from '../../hooks/useUser';
|
||||
import axios from 'axios';
|
||||
import { useRouter } from 'next/dist/client/router';
|
||||
import ImageFader from '../Common/ImageFader';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import Transition from '../Transition';
|
||||
import LanguagePicker from '../Layout/LanguagePicker';
|
||||
import LocalLogin from './LocalLogin';
|
||||
@@ -77,9 +77,9 @@ const Login: React.FC = () => {
|
||||
<LanguagePicker />
|
||||
</div>
|
||||
<div className="relative z-40 px-4 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img src="/logo.png" className="w-auto mx-auto max-h-32" alt="Logo" />
|
||||
<img src="/logo.png" className="max-w-full" alt="Logo" />
|
||||
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
|
||||
<FormattedMessage {...messages.signinheader} />
|
||||
{intl.formatMessage(messages.signinheader)}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
|
||||
@@ -6,21 +6,21 @@ import { useRouter } from 'next/router';
|
||||
import Header from '../Common/Header';
|
||||
import type { MovieDetails } from '../../../server/models/Movie';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import useDiscover from '../../hooks/useDiscover';
|
||||
import Error from '../../pages/_error';
|
||||
import Link from 'next/link';
|
||||
|
||||
const messages = defineMessages({
|
||||
recommendations: 'Recommendations',
|
||||
recommendationssubtext: 'If you liked {title}, you might also like…',
|
||||
});
|
||||
|
||||
const MovieRecommendations: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const router = useRouter();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data: movieData, error: movieError } = useSWR<MovieDetails>(
|
||||
const { data: movieData } = useSWR<MovieDetails>(
|
||||
`/api/v1/movie/${router.query.movieId}?language=${locale}`
|
||||
);
|
||||
const {
|
||||
@@ -47,14 +47,12 @@ const MovieRecommendations: React.FC = () => {
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
movieData && !movieError
|
||||
? intl.formatMessage(messages.recommendationssubtext, {
|
||||
title: movieData.title,
|
||||
})
|
||||
: ''
|
||||
<Link href={`/movie/${movieData?.id}`}>
|
||||
<a className="hover:underline">{movieData?.title}</a>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<FormattedMessage {...messages.recommendations} />
|
||||
{intl.formatMessage(messages.recommendations)}
|
||||
</Header>
|
||||
</div>
|
||||
<ListView
|
||||
|
||||
@@ -6,21 +6,21 @@ import { useRouter } from 'next/router';
|
||||
import Header from '../Common/Header';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import type { MovieDetails } from '../../../server/models/Movie';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import useDiscover from '../../hooks/useDiscover';
|
||||
import Error from '../../pages/_error';
|
||||
import Link from 'next/link';
|
||||
|
||||
const messages = defineMessages({
|
||||
similar: 'Similar Titles',
|
||||
similarsubtext: 'Other movies similar to {title}',
|
||||
});
|
||||
|
||||
const MovieSimilar: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data: movieData, error: movieError } = useSWR<MovieDetails>(
|
||||
const { data: movieData } = useSWR<MovieDetails>(
|
||||
`/api/v1/movie/${router.query.movieId}?language=${locale}`
|
||||
);
|
||||
const {
|
||||
@@ -45,14 +45,12 @@ const MovieSimilar: React.FC = () => {
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
movieData && !movieError
|
||||
? intl.formatMessage(messages.similarsubtext, {
|
||||
title: movieData.title,
|
||||
})
|
||||
: undefined
|
||||
<Link href={`/movie/${movieData?.id}`}>
|
||||
<a className="hover:underline">{movieData?.title}</a>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<FormattedMessage {...messages.similar} />
|
||||
{intl.formatMessage(messages.similar)}
|
||||
</Header>
|
||||
</div>
|
||||
<ListView
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useContext, useMemo } from 'react';
|
||||
import { defineMessages, FormattedNumber, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import type { MovieDetails as MovieDetailsType } from '../../../server/models/Movie';
|
||||
import useSWR from 'swr';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -595,11 +595,10 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
<div className="media-fact">
|
||||
<span>{intl.formatMessage(messages.revenue)}</span>
|
||||
<span className="media-fact-value">
|
||||
<FormattedNumber
|
||||
currency="USD"
|
||||
style="currency"
|
||||
value={data.revenue}
|
||||
/>
|
||||
{intl.formatNumber(data.revenue, {
|
||||
currency: 'USD',
|
||||
style: 'currency',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -607,11 +606,10 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
||||
<div className="media-fact">
|
||||
<span>{intl.formatMessage(messages.budget)}</span>
|
||||
<span className="media-fact-value">
|
||||
<FormattedNumber
|
||||
currency="USD"
|
||||
style="currency"
|
||||
value={data.budget}
|
||||
/>
|
||||
{intl.formatNumber(data.budget, {
|
||||
currency: 'USD',
|
||||
style: 'currency',
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -18,11 +18,10 @@ import AdvancedRequester, { RequestOverrides } from './AdvancedRequester';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
requestadmin:
|
||||
'Your request will be immediately approved. Do you wish to continue?',
|
||||
requestadmin: 'Your request will be immediately approved.',
|
||||
cancelrequest:
|
||||
'This will remove your request. Are you sure you want to continue?',
|
||||
requestSuccess: '<strong>{title}</strong> successfully requested!',
|
||||
requestSuccess: '<strong>{title}</strong> requested successfully!',
|
||||
requestCancel: 'Request for <strong>{title}</strong> canceled',
|
||||
requesttitle: 'Request {title}',
|
||||
request4ktitle: 'Request {title} in 4K',
|
||||
|
||||
@@ -10,7 +10,8 @@ const messages = defineMessages({
|
||||
next: 'Next',
|
||||
notvdbid: 'Manual Match Required',
|
||||
notvdbiddescription:
|
||||
"We couldn't automatically match your request. Please select the correct match from the list below:",
|
||||
"We couldn't automatically match your request.\
|
||||
Please select the correct match from the list below.",
|
||||
nosummary: 'No summary for this title was found.',
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const messages = defineMessages({
|
||||
requestadmin: 'Your request will be immediately approved.',
|
||||
cancelrequest:
|
||||
'This will remove your request. Are you sure you want to continue?',
|
||||
requestSuccess: '<strong>{title}</strong> successfully requested!',
|
||||
requestSuccess: '<strong>{title}</strong> requested successfully!',
|
||||
requesttitle: 'Request {title}',
|
||||
request4ktitle: 'Request {title} in 4K',
|
||||
requesting: 'Requesting…',
|
||||
@@ -45,7 +45,6 @@ const messages = defineMessages({
|
||||
requestcancelled: 'Request canceled.',
|
||||
autoapproval: 'Automatic Approval',
|
||||
requesterror: 'Something went wrong while submitting the request.',
|
||||
next: 'Next',
|
||||
backbutton: 'Back',
|
||||
});
|
||||
|
||||
|
||||
@@ -7,13 +7,15 @@ import { Field, Form, Formik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
forgotpassword: 'Forgot Your Password?',
|
||||
emailresetlink: 'Email Me a Recovery Link',
|
||||
passwordreset: 'Password Reset',
|
||||
resetpassword: 'Reset your password',
|
||||
emailresetlink: 'Email a Recovery Link',
|
||||
email: 'Email',
|
||||
validationemailrequired: 'You must provide a valid email address',
|
||||
gobacklogin: 'Go Back to Sign-In Page',
|
||||
gobacklogin: 'Return to Sign-In Page',
|
||||
requestresetlinksuccessmessage:
|
||||
'A password reset link will be sent to the provided email address if it is associated with a valid user.',
|
||||
});
|
||||
@@ -30,6 +32,7 @@ const ResetPassword: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col min-h-screen bg-gray-900 py-14">
|
||||
<PageTitle title={intl.formatMessage(messages.passwordreset)} />
|
||||
<ImageFader
|
||||
backgroundImages={[
|
||||
'/images/rotate1.jpg',
|
||||
@@ -44,13 +47,9 @@ const ResetPassword: React.FC = () => {
|
||||
<LanguagePicker />
|
||||
</div>
|
||||
<div className="relative z-40 px-4 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="w-auto mx-auto max-h-32"
|
||||
alt="Overseerr Logo"
|
||||
/>
|
||||
<img src="/logo.png" className="max-w-full" alt="Logo" />
|
||||
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
{intl.formatMessage(messages.resetpassword)}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
|
||||
@@ -10,16 +10,16 @@ import { useRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
|
||||
const messages = defineMessages({
|
||||
resetpassword: 'Reset Password',
|
||||
passwordreset: 'Password Reset',
|
||||
resetpassword: 'Reset your password',
|
||||
password: 'Password',
|
||||
confirmpassword: 'Confirm Password',
|
||||
validationpasswordrequired: 'You must provide a password',
|
||||
validationpasswordmatch: 'Password must match',
|
||||
validationpasswordmatch: 'Passwords must match',
|
||||
validationpasswordminchars:
|
||||
'Password is too short; should be a minimum of 8 characters',
|
||||
gobacklogin: 'Go Back to Sign-In Page',
|
||||
resetpasswordsuccessmessage:
|
||||
'If the link is valid and is connected to a user then the password has been reset.',
|
||||
gobacklogin: 'Return to Sign-In Page',
|
||||
resetpasswordsuccessmessage: 'Password reset successfully!',
|
||||
});
|
||||
|
||||
const ResetPassword: React.FC = () => {
|
||||
@@ -60,11 +60,7 @@ const ResetPassword: React.FC = () => {
|
||||
<LanguagePicker />
|
||||
</div>
|
||||
<div className="relative z-40 px-4 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="w-auto mx-auto max-h-32"
|
||||
alt="Overseerr Logo"
|
||||
/>
|
||||
<img src="/logo.png" className="max-w-full" alt="Logo" />
|
||||
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
|
||||
{intl.formatMessage(messages.resetpassword)}
|
||||
</h2>
|
||||
|
||||
@@ -15,8 +15,8 @@ import globalMessages from '../../../i18n/globalMessages';
|
||||
const messages = defineMessages({
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
validationSmtpHostRequired: 'You must provide an SMTP host',
|
||||
validationSmtpPortRequired: 'You must provide an SMTP port',
|
||||
validationSmtpHostRequired: 'You must provide a hostname or IP address',
|
||||
validationSmtpPortRequired: 'You must provide a valid port number',
|
||||
agentenabled: 'Enable Agent',
|
||||
emailsender: 'Sender Address',
|
||||
smtpHost: 'SMTP Host',
|
||||
@@ -80,9 +80,9 @@ const NotificationsEmail: React.FC = () => {
|
||||
/^(([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])$/i,
|
||||
intl.formatMessage(messages.validationSmtpHostRequired)
|
||||
),
|
||||
smtpPort: Yup.number().required(
|
||||
intl.formatMessage(messages.validationSmtpPortRequired)
|
||||
),
|
||||
smtpPort: Yup.number()
|
||||
.typeError(intl.formatMessage(messages.validationSmtpPortRequired))
|
||||
.required(intl.formatMessage(messages.validationSmtpPortRequired)),
|
||||
});
|
||||
|
||||
if (!data && !error) {
|
||||
|
||||
@@ -12,13 +12,13 @@ const messages = defineMessages({
|
||||
createradarr: 'Add New Radarr Server',
|
||||
editradarr: 'Edit Radarr Server',
|
||||
validationNameRequired: 'You must provide a server name',
|
||||
validationHostnameRequired: 'You must provide a hostname/IP',
|
||||
validationPortRequired: 'You must provide a port',
|
||||
validationHostnameRequired: 'You must provide a hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
validationApiKeyRequired: 'You must provide an API key',
|
||||
validationRootFolderRequired: 'You must select a root folder',
|
||||
validationProfileRequired: 'You must select a profile',
|
||||
validationMinimumAvailabilityRequired: 'You must select minimum availability',
|
||||
toastRadarrTestSuccess: 'Radarr connection established!',
|
||||
toastRadarrTestSuccess: 'Radarr connection established successfully!',
|
||||
toastRadarrTestFailure: 'Failed to connect to Radarr.',
|
||||
saving: 'Saving…',
|
||||
save: 'Save Changes',
|
||||
@@ -28,7 +28,7 @@ const messages = defineMessages({
|
||||
defaultserver: 'Default Server',
|
||||
servername: 'Server Name',
|
||||
servernamePlaceholder: 'A Radarr Server',
|
||||
hostname: 'Hostname',
|
||||
hostname: 'Hostname or IP Address',
|
||||
port: 'Port',
|
||||
ssl: 'SSL',
|
||||
apiKey: 'API Key',
|
||||
@@ -98,9 +98,9 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
|
||||
/^(([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])$/i,
|
||||
intl.formatMessage(messages.validationHostnameRequired)
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
port: Yup.number()
|
||||
.typeError(intl.formatMessage(messages.validationPortRequired))
|
||||
.required(intl.formatMessage(messages.validationPortRequired)),
|
||||
apiKey: Yup.string().required(
|
||||
intl.formatMessage(messages.validationApiKeyRequired)
|
||||
),
|
||||
|
||||
@@ -18,9 +18,10 @@ const messages = defineMessages({
|
||||
latestversion: 'Latest',
|
||||
currentversion: 'Current Version',
|
||||
viewchangelog: 'View Changelog',
|
||||
runningDevelop: 'You are running a develop version of Overseerr!',
|
||||
runningDevelop: 'Development Version',
|
||||
runningDevelopMessage:
|
||||
'The changes in your version will not be available below. Please see the <GithubLink>GitHub repository</GithubLink> for latest updates.',
|
||||
'The latest changes to the <code>develop</code> branch of Overseerr are not shown below.\
|
||||
Please see the commit history for this branch on <GithubLink>GitHub</GithubLink> for details.',
|
||||
});
|
||||
|
||||
const REPO_RELEASE_API =
|
||||
@@ -161,6 +162,9 @@ const Releases: React.FC<ReleasesProps> = ({ currentVersion }) => {
|
||||
{currentVersion.startsWith('develop-') && (
|
||||
<Alert title={intl.formatMessage(messages.runningDevelop)}>
|
||||
{intl.formatMessage(messages.runningDevelopMessage, {
|
||||
code: function code(msg) {
|
||||
return <code className="bg-opacity-50">{msg}</code>;
|
||||
},
|
||||
GithubLink: function GithubLink(msg) {
|
||||
return (
|
||||
<a
|
||||
|
||||
@@ -4,19 +4,21 @@ import Error from '../../../pages/_error';
|
||||
import List from '../../Common/List';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import { SettingsAboutResponse } from '../../../../server/interfaces/api/settingsInterfaces';
|
||||
import { defineMessages, FormattedNumber, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import Releases from './Releases';
|
||||
import Badge from '../../Common/Badge';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
about: 'About',
|
||||
overseerrinformation: 'Overseerr Information',
|
||||
version: 'Version',
|
||||
totalmedia: 'Total Media',
|
||||
totalrequests: 'Total Requests',
|
||||
gettingsupport: 'Getting Support',
|
||||
githubdiscussions: 'GitHub Discussions',
|
||||
clickheretojoindiscord: 'Click here to join our Discord server.',
|
||||
timezone: 'Timezone',
|
||||
timezone: 'Time Zone',
|
||||
supportoverseerr: 'Support Overseerr',
|
||||
helppaycoffee: 'Help Pay for Coffee',
|
||||
documentation: 'Documentation',
|
||||
@@ -39,20 +41,26 @@ const SettingsAbout: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.about),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="section">
|
||||
<List title={intl.formatMessage(messages.overseerrinformation)}>
|
||||
<List.Item title={intl.formatMessage(messages.version)}>
|
||||
{data.version}
|
||||
<code>{data.version}</code>
|
||||
</List.Item>
|
||||
<List.Item title={intl.formatMessage(messages.totalmedia)}>
|
||||
<FormattedNumber value={data.totalMediaItems} />
|
||||
{intl.formatNumber(data.totalMediaItems)}
|
||||
</List.Item>
|
||||
<List.Item title={intl.formatMessage(messages.totalrequests)}>
|
||||
<FormattedNumber value={data.totalRequests} />
|
||||
{intl.formatNumber(data.totalRequests)}
|
||||
</List.Item>
|
||||
{data.tz && (
|
||||
<List.Item title={intl.formatMessage(messages.timezone)}>
|
||||
{data.tz}
|
||||
<code>{data.tz}</code>
|
||||
</List.Item>
|
||||
)}
|
||||
</List>
|
||||
@@ -86,7 +94,7 @@ const SettingsAbout: React.FC = () => {
|
||||
rel="noreferrer"
|
||||
className="text-indigo-500 hover:underline"
|
||||
>
|
||||
{intl.formatMessage(messages.clickheretojoindiscord)}
|
||||
https://discord.gg/PkCWJSeCk7
|
||||
</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
|
||||
@@ -15,8 +15,11 @@ import { useToasts } from 'react-toast-notifications';
|
||||
import Badge from '../../Common/Badge';
|
||||
import { CacheItem } from '../../../../server/interfaces/api/settingsInterfaces';
|
||||
import { formatBytes } from '../../../utils/numberHelpers';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
|
||||
const messages: { [messageName: string]: MessageDescriptor } = defineMessages({
|
||||
jobsandcache: 'Jobs & Cache',
|
||||
jobs: 'Jobs',
|
||||
jobsDescription:
|
||||
'Overseerr performs certain maintenance tasks as regularly-scheduled jobs,\
|
||||
@@ -118,6 +121,12 @@ const SettingsJobs: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.jobsandcache),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{intl.formatMessage(messages.jobs)}</h3>
|
||||
<p className="description">
|
||||
|
||||
@@ -3,10 +3,10 @@ import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
settings: 'Settings',
|
||||
menuGeneralSettings: 'General Settings',
|
||||
menuGeneralSettings: 'General',
|
||||
menuUsers: 'Users',
|
||||
menuPlexSettings: 'Plex',
|
||||
menuServices: 'Services',
|
||||
@@ -99,7 +99,7 @@ const SettingsLayout: React.FC = ({ children }) => {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.settings)} />
|
||||
<PageTitle title={intl.formatMessage(globalMessages.settings)} />
|
||||
<div className="mt-6">
|
||||
<div className="sm:hidden">
|
||||
<select
|
||||
|
||||
@@ -9,7 +9,9 @@ import Error from '../../../pages/_error';
|
||||
import Badge from '../../Common/Badge';
|
||||
import Button from '../../Common/Button';
|
||||
import LoadingSpinner from '../../Common/LoadingSpinner';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
import Table from '../../Common/Table';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
logs: 'Logs',
|
||||
@@ -93,6 +95,12 @@ const SettingsLogs: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.logs),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-2">
|
||||
<h3 className="heading">{intl.formatMessage(messages.logs)}</h3>
|
||||
<p className="description">
|
||||
|
||||
@@ -13,8 +13,10 @@ import Badge from '../Common/Badge';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
import * as Yup from 'yup';
|
||||
import RegionSelector from '../RegionSelector';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
general: 'General',
|
||||
generalsettings: 'General Settings',
|
||||
generalsettingsDescription:
|
||||
'Configure global and default settings for Overseerr.',
|
||||
@@ -24,21 +26,19 @@ const messages = defineMessages({
|
||||
applicationTitle: 'Application Title',
|
||||
applicationurl: 'Application URL',
|
||||
region: 'Discover Region',
|
||||
regionTip:
|
||||
'Filter content by region (only applies to the "Popular" and "Upcoming" categories)',
|
||||
regionTip: 'Filter content by regional availability',
|
||||
originallanguage: 'Discover Language',
|
||||
originallanguageTip:
|
||||
'Filter content by original language (only applies to the "Popular" and "Upcoming" categories)',
|
||||
toastApiKeySuccess: 'New API key generated!',
|
||||
originallanguageTip: 'Filter content by original language',
|
||||
toastApiKeySuccess: 'New API key generated successfully!',
|
||||
toastApiKeyFailure: 'Something went wrong while generating a new API key.',
|
||||
toastSettingsSuccess: 'Settings successfully saved!',
|
||||
toastSettingsSuccess: 'Settings saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
hideAvailable: 'Hide Available Media',
|
||||
csrfProtection: 'Enable CSRF Protection',
|
||||
csrfProtectionTip:
|
||||
'Sets external API access to read-only (requires HTTPS and Overseerr must be reloaded for changes to take effect)',
|
||||
'Sets external API access to read-only (requires HTTPS, and Overseerr must be reloaded for changes to take effect)',
|
||||
csrfProtectionHoverTip:
|
||||
'Do NOT enable this unless you understand what you are doing!',
|
||||
'Do NOT enable this setting unless you understand what you are doing!',
|
||||
trustProxy: 'Enable Proxy Support',
|
||||
trustProxyTip:
|
||||
'Allows Overseerr to correctly register client IP addresses behind a proxy (Overseerr must be reloaded for changes to take effect)',
|
||||
@@ -46,7 +46,7 @@ const messages = defineMessages({
|
||||
validationApplicationUrl: 'You must provide a valid URL',
|
||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
originalLanguageDefault: 'All Languages',
|
||||
partialRequestsEnabled: 'Enable Partial Series Requests',
|
||||
partialRequestsEnabled: 'Allow Partial Series Requests',
|
||||
});
|
||||
|
||||
const SettingsMain: React.FC = () => {
|
||||
@@ -119,6 +119,12 @@ const SettingsMain: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.general),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(messages.generalsettings)}
|
||||
|
||||
@@ -15,8 +15,11 @@ import LoadingSpinner from '../Common/LoadingSpinner';
|
||||
import axios from 'axios';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import Button from '../Common/Button';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
notifications: 'Notifications',
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
notificationsettings: 'Notification Settings',
|
||||
@@ -174,6 +177,12 @@ const SettingsNotifications: React.FC = ({ children }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.notifications),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(messages.notificationsettings)}
|
||||
|
||||
@@ -9,12 +9,15 @@ import Button from '../Common/Button';
|
||||
import axios from 'axios';
|
||||
import LibraryItem from './LibraryItem';
|
||||
import Badge from '../Common/Badge';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import * as Yup from 'yup';
|
||||
import Alert from '../Common/Alert';
|
||||
import Spinner from '../../assets/spinner.svg';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
plex: 'Plex',
|
||||
plexsettings: 'Plex Settings',
|
||||
plexsettingsDescription:
|
||||
'Configure the settings for your Plex server. Overseerr scans your Plex libraries to see what content is available.',
|
||||
@@ -30,17 +33,17 @@ const messages = defineMessages({
|
||||
serverpresetRefreshing: 'Retrieving servers…',
|
||||
serverpresetLoad: 'Press the button to load available servers',
|
||||
toastPlexRefresh: 'Retrieving server list from Plex',
|
||||
toastPlexRefreshSuccess: 'Retrieved server list from Plex',
|
||||
toastPlexRefreshFailure: 'Unable to retrieve server list from Plex',
|
||||
toastPlexConnecting: 'Attempting to connect to Plex server',
|
||||
toastPlexConnectingSuccess: 'Connected to Plex server',
|
||||
toastPlexConnectingFailure: 'Unable to connect to Plex server',
|
||||
toastPlexRefreshSuccess: 'Plex server list retrieved successfully!',
|
||||
toastPlexRefreshFailure: 'Failed to retrieve Plex server list.',
|
||||
toastPlexConnecting: 'Attempting to connect to Plex…',
|
||||
toastPlexConnectingSuccess: 'Plex connection established successfully!',
|
||||
toastPlexConnectingFailure: 'Failed to connect to Plex.',
|
||||
settingUpPlex: 'Setting Up Plex',
|
||||
settingUpPlexDescription:
|
||||
'To set up Plex, you can either enter your details manually \
|
||||
or select a server retrieved from <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>.\
|
||||
Press the button to the right of the dropdown to check connectivity and retrieve available servers.',
|
||||
hostname: 'Hostname/IP',
|
||||
hostname: 'Hostname or IP Address',
|
||||
port: 'Port',
|
||||
ssl: 'SSL',
|
||||
timeout: 'Timeout',
|
||||
@@ -59,8 +62,8 @@ const messages = defineMessages({
|
||||
librariesRemaining: 'Libraries Remaining: {count}',
|
||||
startscan: 'Start Scan',
|
||||
cancelscan: 'Cancel Scan',
|
||||
validationHostnameRequired: 'You must provide a hostname/IP',
|
||||
validationPortRequired: 'You must provide a port',
|
||||
validationHostnameRequired: 'You must provide a hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
});
|
||||
|
||||
interface Library {
|
||||
@@ -120,9 +123,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
/^(([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])$/i,
|
||||
intl.formatMessage(messages.validationHostnameRequired)
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
port: Yup.number()
|
||||
.typeError(intl.formatMessage(messages.validationPortRequired))
|
||||
.required(intl.formatMessage(messages.validationPortRequired)),
|
||||
});
|
||||
|
||||
const activeLibraries =
|
||||
@@ -259,12 +262,16 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.plex),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
<FormattedMessage {...messages.plexsettings} />
|
||||
</h3>
|
||||
<h3 className="heading">{intl.formatMessage(messages.plexsettings)}</h3>
|
||||
<p className="description">
|
||||
<FormattedMessage {...messages.plexsettingsDescription} />
|
||||
{intl.formatMessage(messages.plexsettingsDescription)}
|
||||
</p>
|
||||
<div className="section">
|
||||
<Alert title={intl.formatMessage(messages.settingUpPlex)} type="info">
|
||||
@@ -350,11 +357,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
<div className="form-row">
|
||||
<label htmlFor="name" className="text-label">
|
||||
<div className="flex flex-col">
|
||||
<span>
|
||||
<FormattedMessage {...messages.servername} />
|
||||
</span>
|
||||
<span>{intl.formatMessage(messages.servername)}</span>
|
||||
<span className="text-gray-500">
|
||||
<FormattedMessage {...messages.servernameTip} />
|
||||
{intl.formatMessage(messages.servernameTip)}
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
@@ -376,7 +381,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="preset" className="text-label">
|
||||
<FormattedMessage {...messages.serverpreset} />
|
||||
{intl.formatMessage(messages.serverpreset)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="form-input-field input-group">
|
||||
@@ -460,7 +465,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="hostname" className="text-label">
|
||||
<FormattedMessage {...messages.hostname} />
|
||||
{intl.formatMessage(messages.hostname)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="form-input-field">
|
||||
@@ -482,7 +487,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="port" className="text-label">
|
||||
<FormattedMessage {...messages.port} />
|
||||
{intl.formatMessage(messages.port)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<Field
|
||||
@@ -545,10 +550,10 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
</Formik>
|
||||
<div className="mt-10 mb-6">
|
||||
<h3 className="heading">
|
||||
<FormattedMessage {...messages.plexlibraries} />
|
||||
{intl.formatMessage(messages.plexlibraries)}
|
||||
</h3>
|
||||
<p className="description">
|
||||
<FormattedMessage {...messages.plexlibrariesDescription} />
|
||||
{intl.formatMessage(messages.plexlibrariesDescription)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="section">
|
||||
@@ -581,11 +586,9 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-10 mb-6">
|
||||
<h3 className="heading">
|
||||
<FormattedMessage {...messages.manualscan} />
|
||||
</h3>
|
||||
<h3 className="heading">{intl.formatMessage(messages.manualscan)}</h3>
|
||||
<p className="description">
|
||||
<FormattedMessage {...messages.manualscanDescription} />
|
||||
{intl.formatMessage(messages.manualscanDescription)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="section">
|
||||
@@ -615,28 +618,24 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
{dataSync.currentLibrary && (
|
||||
<div className="flex items-center mb-2 mr-0 sm:mb-0 sm:mr-2">
|
||||
<Badge>
|
||||
<FormattedMessage
|
||||
{...messages.currentlibrary}
|
||||
values={{ name: dataSync.currentLibrary.name }}
|
||||
/>
|
||||
{intl.formatMessage(messages.currentlibrary, {
|
||||
name: dataSync.currentLibrary.name,
|
||||
})}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Badge badgeType="warning">
|
||||
<FormattedMessage
|
||||
{...messages.librariesRemaining}
|
||||
values={{
|
||||
count: dataSync.currentLibrary
|
||||
? dataSync.libraries.slice(
|
||||
dataSync.libraries.findIndex(
|
||||
(library) =>
|
||||
library.id === dataSync.currentLibrary?.id
|
||||
) + 1
|
||||
).length
|
||||
: 0,
|
||||
}}
|
||||
/>
|
||||
{intl.formatMessage(messages.librariesRemaining, {
|
||||
count: dataSync.currentLibrary
|
||||
? dataSync.libraries.slice(
|
||||
dataSync.libraries.findIndex(
|
||||
(library) =>
|
||||
library.id === dataSync.currentLibrary?.id
|
||||
) + 1
|
||||
).length
|
||||
: 0,
|
||||
})}
|
||||
</Badge>
|
||||
</div>
|
||||
</>
|
||||
@@ -658,7 +657,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedMessage {...messages.startscan} />
|
||||
{intl.formatMessage(messages.startscan)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -678,7 +677,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedMessage {...messages.cancelscan} />
|
||||
{intl.formatMessage(messages.cancelscan)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,11 @@ import Transition from '../Transition';
|
||||
import axios from 'axios';
|
||||
import SonarrModal from './SonarrModal';
|
||||
import Alert from '../Common/Alert';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import globalMessages from '../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
services: 'Services',
|
||||
radarrsettings: 'Radarr Settings',
|
||||
radarrSettingsDescription:
|
||||
'Configure your Radarr connection below. You can have multiple Radarr configurations, but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server which is used for new requests.',
|
||||
@@ -214,6 +217,12 @@ const SettingsServices: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.services),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(messages.radarrsettings)}
|
||||
|
||||
@@ -8,13 +8,16 @@ import Button from '../../Common/Button';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import PermissionEdit from '../../PermissionEdit';
|
||||
import PageTitle from '../../Common/PageTitle';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
userSettings: 'Users',
|
||||
users: 'Users',
|
||||
userSettings: 'User Settings',
|
||||
userSettingsDescription: 'Configure global and default user settings.',
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
toastSettingsSuccess: 'Settings successfully saved!',
|
||||
toastSettingsSuccess: 'User settings saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
localLogin: 'Enable Local User Sign-In',
|
||||
defaultPermissions: 'Default User Permissions',
|
||||
@@ -33,6 +36,12 @@ const SettingsUsers: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.users),
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{intl.formatMessage(messages.userSettings)}</h3>
|
||||
<p className="description">
|
||||
|
||||
@@ -12,13 +12,13 @@ const messages = defineMessages({
|
||||
createsonarr: 'Add New Sonarr Server',
|
||||
editsonarr: 'Edit Sonarr Server',
|
||||
validationNameRequired: 'You must provide a server name',
|
||||
validationHostnameRequired: 'You must provide a hostname/IP',
|
||||
validationPortRequired: 'You must provide a port',
|
||||
validationHostnameRequired: 'You must provide a hostname or IP address',
|
||||
validationPortRequired: 'You must provide a valid port number',
|
||||
validationApiKeyRequired: 'You must provide an API key',
|
||||
validationRootFolderRequired: 'You must select a root folder',
|
||||
validationProfileRequired: 'You must select a quality profile',
|
||||
validationLanguageProfileRequired: 'You must select a language profile',
|
||||
toastSonarrTestSuccess: 'Sonarr connection established!',
|
||||
toastSonarrTestSuccess: 'Sonarr connection established successfully!',
|
||||
toastSonarrTestFailure: 'Failed to connect to Sonarr.',
|
||||
saving: 'Saving…',
|
||||
save: 'Save Changes',
|
||||
@@ -28,7 +28,7 @@ const messages = defineMessages({
|
||||
defaultserver: 'Default Server',
|
||||
servername: 'Server Name',
|
||||
servernamePlaceholder: 'A Sonarr Server',
|
||||
hostname: 'Hostname',
|
||||
hostname: 'Hostname or IP Address',
|
||||
port: 'Port',
|
||||
ssl: 'SSL',
|
||||
apiKey: 'API Key',
|
||||
@@ -109,9 +109,9 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
/^(([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])$/i,
|
||||
intl.formatMessage(messages.validationHostnameRequired)
|
||||
),
|
||||
port: Yup.number().required(
|
||||
intl.formatMessage(messages.validationPortRequired)
|
||||
),
|
||||
port: Yup.number()
|
||||
.typeError(intl.formatMessage(messages.validationPortRequired))
|
||||
.required(intl.formatMessage(messages.validationPortRequired)),
|
||||
apiKey: Yup.string().required(
|
||||
intl.formatMessage(messages.validationApiKeyRequired)
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useUser } from '../../hooks/useUser';
|
||||
import PlexLoginButton from '../PlexLoginButton';
|
||||
import axios from 'axios';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
welcome: 'Welcome to Overseerr',
|
||||
@@ -14,6 +14,7 @@ interface LoginWithPlexProps {
|
||||
}
|
||||
|
||||
const LoginWithPlex: React.FC<LoginWithPlexProps> = ({ onComplete }) => {
|
||||
const intl = useIntl();
|
||||
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
|
||||
const { user, revalidate } = useUser();
|
||||
|
||||
@@ -45,10 +46,10 @@ const LoginWithPlex: React.FC<LoginWithPlexProps> = ({ onComplete }) => {
|
||||
return (
|
||||
<form>
|
||||
<div className="flex justify-center mb-2 text-xl font-bold">
|
||||
<FormattedMessage {...messages.welcome} />
|
||||
{intl.formatMessage(messages.welcome)}
|
||||
</div>
|
||||
<div className="flex justify-center pb-6 mb-2 text-sm">
|
||||
<FormattedMessage {...messages.signinMessage} />
|
||||
{intl.formatMessage(messages.signinMessage)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<PlexLoginButton onAuthToken={(authToken) => setAuthToken(authToken)} />
|
||||
|
||||
@@ -7,7 +7,7 @@ import SettingsServices from '../Settings/SettingsServices';
|
||||
import LoginWithPlex from './LoginWithPlex';
|
||||
import SetupSteps from './SetupSteps';
|
||||
import axios from 'axios';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import Badge from '../Common/Badge';
|
||||
import LanguagePicker from '../Layout/LanguagePicker';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
@@ -63,11 +63,7 @@ const Setup: React.FC = () => {
|
||||
<LanguagePicker />
|
||||
</div>
|
||||
<div className="relative z-40 px-4 sm:mx-auto sm:w-full sm:max-w-4xl">
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="w-auto mx-auto mb-10 max-h-32"
|
||||
alt="Logo"
|
||||
/>
|
||||
<img src="/logo.png" className="max-w-full sm:max-w-md" alt="Logo" />
|
||||
<AppDataWarning />
|
||||
<nav className="relative z-50">
|
||||
<ul
|
||||
@@ -115,7 +111,7 @@ const Setup: React.FC = () => {
|
||||
disabled={!plexSettingsComplete}
|
||||
onClick={() => setCurrentStep(3)}
|
||||
>
|
||||
<FormattedMessage {...messages.continue} />
|
||||
{intl.formatMessage(messages.continue)}
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -133,11 +129,9 @@ const Setup: React.FC = () => {
|
||||
onClick={() => finishSetup()}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{isUpdating ? (
|
||||
<FormattedMessage {...messages.finishing} />
|
||||
) : (
|
||||
<FormattedMessage {...messages.finish} />
|
||||
)}
|
||||
{isUpdating
|
||||
? intl.formatMessage(messages.finishing)
|
||||
: intl.formatMessage(messages.finish)}
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
} from 'react';
|
||||
import { useSpring } from 'react-spring';
|
||||
import TitleCard from '../TitleCard';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
noresults: 'No results.',
|
||||
@@ -36,6 +36,7 @@ const Slider: React.FC<SliderProps> = ({
|
||||
emptyMessage,
|
||||
placeholder = <TitleCard.Placeholder />,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [scrollPos, setScrollPos] = useState({ isStart: true, isEnd: false });
|
||||
|
||||
@@ -230,11 +231,9 @@ const Slider: React.FC<SliderProps> = ({
|
||||
))}
|
||||
{isEmpty && (
|
||||
<div className="mt-16 mb-16 text-center text-white">
|
||||
{emptyMessage ? (
|
||||
emptyMessage
|
||||
) : (
|
||||
<FormattedMessage {...messages.noresults} />
|
||||
)}
|
||||
{emptyMessage
|
||||
? emptyMessage
|
||||
: intl.formatMessage(messages.noresults)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,10 +5,10 @@ import Modal from '../Common/Modal';
|
||||
import Transition from '../Transition';
|
||||
|
||||
const messages = defineMessages({
|
||||
newversionavailable: 'New Version Available',
|
||||
newversionavailable: 'Application Update',
|
||||
newversionDescription:
|
||||
'An update is now available. Click the button below to reload the application.',
|
||||
reloadOverseerr: 'Reload Overseerr',
|
||||
'Overseerr has been updated! Please click the button below to reload the page.',
|
||||
reloadOverseerr: 'Reload',
|
||||
});
|
||||
|
||||
const StatusChecker: React.FC = () => {
|
||||
|
||||
@@ -5,22 +5,22 @@ import ListView from '../Common/ListView';
|
||||
import { useRouter } from 'next/router';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import Header from '../Common/Header';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { TvDetails } from '../../../server/models/Tv';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import Error from '../../pages/_error';
|
||||
import useDiscover from '../../hooks/useDiscover';
|
||||
import Link from 'next/link';
|
||||
|
||||
const messages = defineMessages({
|
||||
recommendations: 'Recommendations',
|
||||
recommendationssubtext: 'If you liked {title}, you might also like…',
|
||||
});
|
||||
|
||||
const TvRecommendations: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data: tvData, error: tvError } = useSWR<TvDetails>(
|
||||
const { data: tvData } = useSWR<TvDetails>(
|
||||
`/api/v1/tv/${router.query.tvId}?language=${locale}`
|
||||
);
|
||||
const {
|
||||
@@ -45,14 +45,12 @@ const TvRecommendations: React.FC = () => {
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
tvData && !tvError
|
||||
? intl.formatMessage(messages.recommendationssubtext, {
|
||||
title: tvData.name,
|
||||
})
|
||||
: ''
|
||||
<Link href={`/tv/${tvData?.id}`}>
|
||||
<a className="hover:underline">{tvData?.name}</a>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<FormattedMessage {...messages.recommendations} />
|
||||
{intl.formatMessage(messages.recommendations)}
|
||||
</Header>
|
||||
</div>
|
||||
<ListView
|
||||
|
||||
@@ -4,23 +4,23 @@ import type { TvResult } from '../../../server/models/Search';
|
||||
import ListView from '../Common/ListView';
|
||||
import { useRouter } from 'next/router';
|
||||
import { LanguageContext } from '../../context/LanguageContext';
|
||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import type { TvDetails } from '../../../server/models/Tv';
|
||||
import Header from '../Common/Header';
|
||||
import PageTitle from '../Common/PageTitle';
|
||||
import useDiscover from '../../hooks/useDiscover';
|
||||
import Error from '../../pages/_error';
|
||||
import Link from 'next/link';
|
||||
|
||||
const messages = defineMessages({
|
||||
similar: 'Similar Series',
|
||||
similarsubtext: 'Other series similar to {title}',
|
||||
});
|
||||
|
||||
const TvSimilar: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const intl = useIntl();
|
||||
const { locale } = useContext(LanguageContext);
|
||||
const { data: tvData, error: tvError } = useSWR<TvDetails>(
|
||||
const { data: tvData } = useSWR<TvDetails>(
|
||||
`/api/v1/tv/${router.query.tvId}?language=${locale}`
|
||||
);
|
||||
const {
|
||||
@@ -43,14 +43,12 @@ const TvSimilar: React.FC = () => {
|
||||
<div className="mt-1 mb-5">
|
||||
<Header
|
||||
subtext={
|
||||
tvData && !tvError
|
||||
? intl.formatMessage(messages.similarsubtext, {
|
||||
title: tvData.name,
|
||||
})
|
||||
: undefined
|
||||
<Link href={`/tv/${tvData?.id}`}>
|
||||
<a className="hover:underline">{tvData?.name}</a>
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<FormattedMessage {...messages.similar} />
|
||||
{intl.formatMessage(messages.similar)}
|
||||
</Header>
|
||||
</div>
|
||||
<ListView
|
||||
|
||||
@@ -15,10 +15,10 @@ interface BulkEditProps {
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
userssaved: 'Users saved',
|
||||
userssaved: 'User permissions saved successfully!',
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
userfail: 'Something went wrong while saving the user.',
|
||||
userfail: 'Something went wrong while saving user permissions.',
|
||||
edituser: 'Edit User Permissions',
|
||||
});
|
||||
|
||||
|
||||
@@ -13,8 +13,11 @@ import Badge from '../../../Common/Badge';
|
||||
import Button from '../../../Common/Button';
|
||||
import LoadingSpinner from '../../../Common/LoadingSpinner';
|
||||
import RegionSelector from '../../../RegionSelector';
|
||||
import globalMessages from '../../../../i18n/globalMessages';
|
||||
import PageTitle from '../../../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
general: 'General',
|
||||
generalsettings: 'General Settings',
|
||||
displayName: 'Display Name',
|
||||
save: 'Save Changes',
|
||||
@@ -26,14 +29,12 @@ const messages = defineMessages({
|
||||
owner: 'Owner',
|
||||
admin: 'Admin',
|
||||
user: 'User',
|
||||
toastSettingsSuccess: 'Settings successfully saved!',
|
||||
toastSettingsSuccess: 'Settings saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
region: 'Discover Region',
|
||||
regionTip:
|
||||
'Filter content by region (only applies to the "Popular" and "Upcoming" categories)',
|
||||
regionTip: 'Filter content by regional availability',
|
||||
originallanguage: 'Discover Language',
|
||||
originallanguageTip:
|
||||
'Filter content by original language (only applies to the "Popular" and "Upcoming" categories)',
|
||||
originallanguageTip: 'Filter content by original language',
|
||||
originalLanguageDefault: 'All Languages',
|
||||
languageServerDefault: 'Default ({language})',
|
||||
});
|
||||
@@ -94,6 +95,12 @@ const UserGeneralSettings: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.general),
|
||||
intl.formatMessage(globalMessages.usersettings),
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(messages.generalsettings)}
|
||||
|
||||
@@ -14,8 +14,10 @@ import * as Yup from 'yup';
|
||||
import Badge from '../../../Common/Badge';
|
||||
import globalMessages from '../../../../i18n/globalMessages';
|
||||
import { PgpLink } from '../../../Settings/Notifications/NotificationsEmail';
|
||||
import PageTitle from '../../../Common/PageTitle';
|
||||
|
||||
const messages = defineMessages({
|
||||
notifications: 'Notifications',
|
||||
notificationsettings: 'Notification Settings',
|
||||
enableNotifications: 'Enable Notifications',
|
||||
discordId: 'Discord User ID',
|
||||
@@ -33,7 +35,7 @@ const messages = defineMessages({
|
||||
saving: 'Saving…',
|
||||
plexuser: 'Plex User',
|
||||
localuser: 'Local User',
|
||||
toastSettingsSuccess: 'Settings successfully saved!',
|
||||
toastSettingsSuccess: 'Notification settings saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
pgpKey: '<PgpLink>PGP</PgpLink> Public Key',
|
||||
pgpKeyTip: 'Encrypt email messages',
|
||||
@@ -70,6 +72,13 @@ const UserNotificationSettings: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.notifications),
|
||||
intl.formatMessage(globalMessages.usersettings),
|
||||
user?.displayName,
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">
|
||||
{intl.formatMessage(messages.notificationsettings)}
|
||||
|
||||
@@ -12,6 +12,8 @@ import Button from '../../../Common/Button';
|
||||
import LoadingSpinner from '../../../Common/LoadingSpinner';
|
||||
import * as Yup from 'yup';
|
||||
import useSettings from '../../../../hooks/useSettings';
|
||||
import PageTitle from '../../../Common/PageTitle';
|
||||
import globalMessages from '../../../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
password: 'Password',
|
||||
@@ -20,19 +22,23 @@ const messages = defineMessages({
|
||||
confirmpassword: 'Confirm Password',
|
||||
save: 'Save Changes',
|
||||
saving: 'Saving…',
|
||||
toastSettingsSuccess: 'Password changed!',
|
||||
toastSettingsFailure:
|
||||
'Something went wrong while changing the password. Is your current password correct?',
|
||||
toastSettingsSuccess: 'Password saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving the password.',
|
||||
toastSettingsFailureVerifyCurrent:
|
||||
'Something went wrong while saving the password. Was your current password entered correctly?',
|
||||
validationCurrentPassword: 'You must provide your current password',
|
||||
validationNewPassword: 'You must provide a new password',
|
||||
validationNewPasswordLength:
|
||||
'Password is too short; should be a minimum of 8 characters',
|
||||
validationConfirmPassword: 'You must confirm your new password',
|
||||
validationConfirmPasswordSame: 'Password must match',
|
||||
validationConfirmPassword: 'You must confirm the new password',
|
||||
validationConfirmPasswordSame: 'Passwords must match',
|
||||
nopasswordset: 'No Password Set',
|
||||
nopasswordsetDescription:
|
||||
'This user account currently does not have a password specifically for {applicationTitle}.\
|
||||
Configure a password below to enable this account to sign in as a "local user."',
|
||||
nopasswordsetDescriptionOwnAccount:
|
||||
'Your account currently does not have a password specifically for {applicationTitle}.\
|
||||
Configure a password below to enable sign in as a "local user" using your email address.',
|
||||
nopermission: 'Unauthorized',
|
||||
nopermissionDescription:
|
||||
"You do not have permission to modify this user's password.",
|
||||
@@ -95,6 +101,13 @@ const UserPasswordChange: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.password),
|
||||
intl.formatMessage(globalMessages.usersettings),
|
||||
user?.displayName,
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{intl.formatMessage(messages.password)}</h3>
|
||||
</div>
|
||||
@@ -119,10 +132,17 @@ const UserPasswordChange: React.FC = () => {
|
||||
appearance: 'success',
|
||||
});
|
||||
} catch (e) {
|
||||
addToast(intl.formatMessage(messages.toastSettingsFailure), {
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
});
|
||||
addToast(
|
||||
intl.formatMessage(
|
||||
data.hasPassword && user?.id === currentUser?.id
|
||||
? messages.toastSettingsFailureVerifyCurrent
|
||||
: messages.toastSettingsFailure
|
||||
),
|
||||
{
|
||||
autoDismiss: true,
|
||||
appearance: 'error',
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
revalidate();
|
||||
resetForm();
|
||||
@@ -137,9 +157,15 @@ const UserPasswordChange: React.FC = () => {
|
||||
type="warning"
|
||||
title={intl.formatMessage(messages.nopasswordset)}
|
||||
>
|
||||
{intl.formatMessage(messages.nopasswordsetDescription, {
|
||||
applicationTitle: settings.currentSettings.applicationTitle,
|
||||
})}
|
||||
{intl.formatMessage(
|
||||
user?.id === currentUser?.id
|
||||
? messages.nopasswordsetDescriptionOwnAccount
|
||||
: messages.nopasswordsetDescription,
|
||||
{
|
||||
applicationTitle:
|
||||
settings.currentSettings.applicationTitle,
|
||||
}
|
||||
)}
|
||||
</Alert>
|
||||
)}
|
||||
{data.hasPassword && user?.id === currentUser?.id && (
|
||||
|
||||
@@ -11,6 +11,8 @@ import Button from '../../../Common/Button';
|
||||
import LoadingSpinner from '../../../Common/LoadingSpinner';
|
||||
import PermissionEdit from '../../../PermissionEdit';
|
||||
import Alert from '../../../Common/Alert';
|
||||
import PageTitle from '../../../Common/PageTitle';
|
||||
import globalMessages from '../../../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
displayName: 'Display Name',
|
||||
@@ -18,7 +20,7 @@ const messages = defineMessages({
|
||||
saving: 'Saving…',
|
||||
plexuser: 'Plex User',
|
||||
localuser: 'Local User',
|
||||
toastSettingsSuccess: 'Settings successfully saved!',
|
||||
toastSettingsSuccess: 'Permissions saved successfully!',
|
||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||
permissions: 'Permissions',
|
||||
unauthorized: 'Unauthorized',
|
||||
@@ -60,6 +62,13 @@ const UserPermissions: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(messages.permissions),
|
||||
intl.formatMessage(globalMessages.usersettings),
|
||||
user?.displayName,
|
||||
]}
|
||||
/>
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{intl.formatMessage(messages.permissions)}</h3>
|
||||
</div>
|
||||
|
||||
@@ -10,10 +10,10 @@ import PageTitle from '../../Common/PageTitle';
|
||||
import ProfileHeader from '../ProfileHeader';
|
||||
import useSettings from '../../../hooks/useSettings';
|
||||
import Alert from '../../Common/Alert';
|
||||
import globalMessages from '../../../i18n/globalMessages';
|
||||
|
||||
const messages = defineMessages({
|
||||
settings: 'User Settings',
|
||||
menuGeneralSettings: 'General Settings',
|
||||
menuGeneralSettings: 'General',
|
||||
menuChangePass: 'Password',
|
||||
menuNotifications: 'Notifications',
|
||||
menuPermissions: 'Permissions',
|
||||
@@ -115,7 +115,12 @@ const UserSettings: React.FC = ({ children }) => {
|
||||
if (currentUser?.id !== 1 && user.id === 1) {
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.settings)} />
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(globalMessages.usersettings),
|
||||
user.displayName,
|
||||
]}
|
||||
/>
|
||||
<ProfileHeader user={user} isSettingsPage />
|
||||
<div className="mt-6">
|
||||
<Alert title={intl.formatMessage(messages.unauthorized)} type="error">
|
||||
@@ -136,7 +141,12 @@ const UserSettings: React.FC = ({ children }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={intl.formatMessage(messages.settings)} />
|
||||
<PageTitle
|
||||
title={[
|
||||
intl.formatMessage(globalMessages.usersettings),
|
||||
user.displayName,
|
||||
]}
|
||||
/>
|
||||
<ProfileHeader user={user} isSettingsPage />
|
||||
<div className="mt-6">
|
||||
<div className="sm:hidden">
|
||||
|
||||
Reference in New Issue
Block a user