mirror of
https://github.com/sct/overseerr.git
synced 2025-09-29 13:33:26 +02:00
feat(requests): add language profile support (#860)
This commit is contained in:
@@ -21,12 +21,15 @@ const messages = defineMessages({
|
||||
loadingprofiles: 'Loading profiles…',
|
||||
loadingfolders: 'Loading folders…',
|
||||
requestas: 'Request As',
|
||||
languageprofile: 'Language Profile',
|
||||
loadinglanguages: 'Loading languages…',
|
||||
});
|
||||
|
||||
export type RequestOverrides = {
|
||||
server?: number;
|
||||
profile?: number;
|
||||
folder?: string;
|
||||
language?: number;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
@@ -69,6 +72,11 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
const [selectedFolder, setSelectedFolder] = useState<string>(
|
||||
defaultOverrides?.folder ?? ''
|
||||
);
|
||||
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<number>(
|
||||
defaultOverrides?.language ?? -1
|
||||
);
|
||||
|
||||
const {
|
||||
data: serverData,
|
||||
isValidating,
|
||||
@@ -135,6 +143,13 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
? serverData.server.activeAnimeDirectory
|
||||
: serverData.server.activeDirectory)
|
||||
);
|
||||
const defaultLanguage = serverData.languageProfiles?.find(
|
||||
(language) =>
|
||||
language.id ===
|
||||
(isAnime
|
||||
? serverData.server.activeAnimeLanguageProfileId
|
||||
: serverData.server.activeLanguageProfileId)
|
||||
);
|
||||
|
||||
if (
|
||||
defaultProfile &&
|
||||
@@ -149,7 +164,15 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
defaultFolder.path !== selectedFolder &&
|
||||
(!defaultOverrides || defaultOverrides.folder === null)
|
||||
) {
|
||||
setSelectedFolder(defaultFolder?.path ?? '');
|
||||
setSelectedFolder(defaultFolder.path ?? '');
|
||||
}
|
||||
|
||||
if (
|
||||
defaultLanguage &&
|
||||
defaultLanguage.id !== selectedLanguage &&
|
||||
(!defaultOverrides || defaultOverrides.language === null)
|
||||
) {
|
||||
setSelectedLanguage(defaultLanguage.id);
|
||||
}
|
||||
}
|
||||
}, [serverData]);
|
||||
@@ -178,10 +201,19 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
) {
|
||||
setSelectedFolder(defaultOverrides.folder);
|
||||
}
|
||||
|
||||
if (
|
||||
defaultOverrides &&
|
||||
defaultOverrides.language !== null &&
|
||||
defaultOverrides.language !== undefined
|
||||
) {
|
||||
setSelectedLanguage(defaultOverrides.language);
|
||||
}
|
||||
}, [
|
||||
defaultOverrides?.server,
|
||||
defaultOverrides?.folder,
|
||||
defaultOverrides?.profile,
|
||||
defaultOverrides?.language,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,9 +223,16 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
profile: selectedProfile !== -1 ? selectedProfile : undefined,
|
||||
server: selectedServer ?? undefined,
|
||||
user: selectedUser ?? undefined,
|
||||
language: selectedLanguage ?? undefined,
|
||||
});
|
||||
}
|
||||
}, [selectedFolder, selectedServer, selectedProfile, selectedUser]);
|
||||
}, [
|
||||
selectedFolder,
|
||||
selectedServer,
|
||||
selectedProfile,
|
||||
selectedUser,
|
||||
selectedLanguage,
|
||||
]);
|
||||
|
||||
if (!data && !error) {
|
||||
return (
|
||||
@@ -225,7 +264,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
{!!data && selectedServer !== null && (
|
||||
<>
|
||||
<div className="flex flex-col items-center justify-between md:flex-row">
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:pr-4 md:mb-0">
|
||||
<label htmlFor="server" className="text-label">
|
||||
{intl.formatMessage(messages.destinationserver)}
|
||||
</label>
|
||||
@@ -247,8 +286,8 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
|
||||
<label htmlFor="server" className="text-label">
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:pr-4 md:mb-0">
|
||||
<label htmlFor="profile" className="text-label">
|
||||
{intl.formatMessage(messages.qualityprofile)}
|
||||
</label>
|
||||
<select
|
||||
@@ -283,8 +322,12 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:mb-0">
|
||||
<label htmlFor="server" className="text-label">
|
||||
<div
|
||||
className={`flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:mb-0 ${
|
||||
type === 'tv' ? 'md:pr-4' : ''
|
||||
}`}
|
||||
>
|
||||
<label htmlFor="folder" className="text-label">
|
||||
{intl.formatMessage(messages.rootfolder)}
|
||||
</label>
|
||||
<select
|
||||
@@ -319,6 +362,50 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{type === 'tv' && (
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:mb-0">
|
||||
<label htmlFor="language" className="text-label">
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
</label>
|
||||
<select
|
||||
id="language"
|
||||
name="language"
|
||||
value={selectedLanguage}
|
||||
onChange={(e) =>
|
||||
setSelectedLanguage(parseInt(e.target.value))
|
||||
}
|
||||
onBlur={(e) =>
|
||||
setSelectedLanguage(parseInt(e.target.value))
|
||||
}
|
||||
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5"
|
||||
>
|
||||
{isValidating && (
|
||||
<option value="">
|
||||
{intl.formatMessage(messages.loadinglanguages)}
|
||||
</option>
|
||||
)}
|
||||
{!isValidating &&
|
||||
serverData &&
|
||||
serverData.languageProfiles?.map((language) => (
|
||||
<option
|
||||
key={`folder-list${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
{isAnime &&
|
||||
serverData.server.activeAnimeLanguageProfileId ===
|
||||
language.id
|
||||
? ` ${intl.formatMessage(messages.default)}`
|
||||
: !isAnime &&
|
||||
serverData.server.activeLanguageProfileId ===
|
||||
language.id
|
||||
? ` ${intl.formatMessage(messages.default)}`
|
||||
: ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
@@ -103,6 +103,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
serverId: requestOverrides?.server,
|
||||
profileId: requestOverrides?.profile,
|
||||
rootFolder: requestOverrides?.folder,
|
||||
languageProfileId: requestOverrides?.language,
|
||||
userId: requestOverrides?.user?.id,
|
||||
seasons: selectedSeasons,
|
||||
});
|
||||
@@ -151,6 +152,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
serverId: requestOverrides.server,
|
||||
profileId: requestOverrides.profile,
|
||||
rootFolder: requestOverrides.folder,
|
||||
languageProfileId: requestOverrides.language,
|
||||
userId: requestOverrides?.user?.id,
|
||||
};
|
||||
}
|
||||
@@ -569,6 +571,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
folder: editRequest.rootFolder,
|
||||
profile: editRequest.profileId,
|
||||
server: editRequest.serverId,
|
||||
language: editRequest.languageProfileId,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@ const messages = defineMessages({
|
||||
validationPortRequired: 'You must provide a port',
|
||||
validationApiKeyRequired: 'You must provide an API key',
|
||||
validationRootFolderRequired: 'You must select a root folder',
|
||||
validationProfileRequired: 'You must select a profile',
|
||||
validationProfileRequired: 'You must select a quality profile',
|
||||
validationLanguageProfileRequired: 'You must select a language profile',
|
||||
toastSonarrTestSuccess: 'Sonarr connection established!',
|
||||
toastSonarrTestFailure: 'Failed to connect to Sonarr.',
|
||||
saving: 'Saving…',
|
||||
@@ -35,17 +36,22 @@ const messages = defineMessages({
|
||||
baseUrl: 'Base URL',
|
||||
baseUrlPlaceholder: 'Example: /sonarr',
|
||||
qualityprofile: 'Quality Profile',
|
||||
languageprofile: 'Language Profile',
|
||||
rootfolder: 'Root Folder',
|
||||
animequalityprofile: 'Anime Quality Profile',
|
||||
animelanguageprofile: 'Anime Language Profile',
|
||||
animerootfolder: 'Anime Root Folder',
|
||||
seasonfolders: 'Season Folders',
|
||||
server4k: '4K Server',
|
||||
selectQualityProfile: 'Select quality profile',
|
||||
selectRootFolder: 'Select root folder',
|
||||
selectLanguageProfile: 'Select language profile',
|
||||
loadingprofiles: 'Loading quality profiles…',
|
||||
testFirstQualityProfiles: 'Test connection to load quality profiles',
|
||||
loadingrootfolders: 'Loading root folders…',
|
||||
testFirstRootFolders: 'Test connection to load root folders',
|
||||
loadinglanguageprofiles: 'Loading language profiles…',
|
||||
testFirstLanguageProfiles: 'Test connection to load language profiles',
|
||||
syncEnabled: 'Enable Sync',
|
||||
externalUrl: 'External URL',
|
||||
externalUrlPlaceholder: 'External URL pointing to your Sonarr server',
|
||||
@@ -65,6 +71,10 @@ interface TestResponse {
|
||||
id: number;
|
||||
path: string;
|
||||
}[];
|
||||
languageProfiles: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface SonarrModalProps {
|
||||
@@ -86,6 +96,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
const [testResponse, setTestResponse] = useState<TestResponse>({
|
||||
profiles: [],
|
||||
rootFolders: [],
|
||||
languageProfiles: [],
|
||||
});
|
||||
const SonarrSettingsSchema = Yup.object().shape({
|
||||
name: Yup.string().required(
|
||||
@@ -106,6 +117,9 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
activeProfileId: Yup.string().required(
|
||||
intl.formatMessage(messages.validationProfileRequired)
|
||||
),
|
||||
activeLanguageProfileId: Yup.number().required(
|
||||
intl.formatMessage(messages.validationLanguageProfileRequired)
|
||||
),
|
||||
externalUrl: Yup.string()
|
||||
.url(intl.formatMessage(messages.validationApplicationUrl))
|
||||
.test(
|
||||
@@ -224,8 +238,10 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
apiKey: sonarr?.apiKey,
|
||||
baseUrl: sonarr?.baseUrl,
|
||||
activeProfileId: sonarr?.activeProfileId,
|
||||
activeLanguageProfileId: sonarr?.activeLanguageProfileId,
|
||||
rootFolder: sonarr?.activeDirectory,
|
||||
activeAnimeProfileId: sonarr?.activeAnimeProfileId,
|
||||
activeAnimeLanguageProfileId: sonarr?.activeAnimeLanguageProfileId,
|
||||
activeAnimeRootFolder: sonarr?.activeAnimeDirectory,
|
||||
isDefault: sonarr?.isDefault ?? false,
|
||||
is4k: sonarr?.is4k ?? false,
|
||||
@@ -252,11 +268,17 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
useSsl: values.ssl,
|
||||
baseUrl: values.baseUrl,
|
||||
activeProfileId: Number(values.activeProfileId),
|
||||
activeLanguageProfileId: values.activeLanguageProfileId
|
||||
? Number(values.activeLanguageProfileId)
|
||||
: undefined,
|
||||
activeProfileName: profileName,
|
||||
activeDirectory: values.rootFolder,
|
||||
activeAnimeProfileId: values.activeAnimeProfileId
|
||||
? Number(values.activeAnimeProfileId)
|
||||
: undefined,
|
||||
activeAnimeLanguageProfileId: values.activeAnimeLanguageProfileId
|
||||
? Number(values.activeAnimeLanguageProfileId)
|
||||
: undefined,
|
||||
activeAnimeProfileName: animeProfileName ?? undefined,
|
||||
activeAnimeDirectory: values.activeAnimeRootFolder,
|
||||
is4k: values.is4k,
|
||||
@@ -559,6 +581,54 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
<span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeLanguageProfileId"
|
||||
name="activeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{errors.activeLanguageProfileId &&
|
||||
touched.activeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="activeAnimeProfileId" className="text-label">
|
||||
{intl.formatMessage(messages.animequalityprofile)}
|
||||
@@ -635,6 +705,53 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeAnimeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.animerootfolder)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeAnimeLanguageProfileId"
|
||||
name="activeAnimeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{errors.activeAnimeLanguageProfileId &&
|
||||
touched.activeAnimeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeAnimeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="enableSeasonFolders"
|
||||
|
Reference in New Issue
Block a user