diff --git a/src/components/Common/Modal/index.tsx b/src/components/Common/Modal/index.tsx index 3babc9b00..835fbbce2 100644 --- a/src/components/Common/Modal/index.tsx +++ b/src/components/Common/Modal/index.tsx @@ -5,6 +5,8 @@ import { useTransition, animated } from 'react-spring'; import { useLockBodyScroll } from '../../../hooks/useLockBodyScroll'; import LoadingSpinner from '../LoadingSpinner'; import useClickOutside from '../../../hooks/useClickOutside'; +import { useIntl } from 'react-intl'; +import globalMessages from '../../../i18n/globalMessages'; interface ModalProps extends React.HTMLAttributes { title?: string; @@ -53,6 +55,7 @@ const Modal: React.FC = ({ onTertiary, ...props }) => { + const intl = useIntl(); const modalRef = useRef(null); useClickOutside(modalRef, () => { typeof onCancel === 'function' && backgroundClickable @@ -174,7 +177,9 @@ const Modal: React.FC = ({ onClick={onCancel} className="ml-3 sm:ml-0 sm:px-4" > - {cancelText ? cancelText : 'Cancel'} + {cancelText + ? cancelText + : intl.formatMessage(globalMessages.cancel)} )} diff --git a/src/components/MovieDetails/MovieRecommendations.tsx b/src/components/MovieDetails/MovieRecommendations.tsx index 4d920c23d..a9a811024 100644 --- a/src/components/MovieDetails/MovieRecommendations.tsx +++ b/src/components/MovieDetails/MovieRecommendations.tsx @@ -35,7 +35,7 @@ const MovieRecommendations: React.FC = () => { return `/api/v1/movie/${router.query.movieId}/recommendations?page=${ pageIndex + 1 - }`; + }&language=${locale}`; }, { initialSize: 3, diff --git a/src/components/MovieDetails/MovieSimilar.tsx b/src/components/MovieDetails/MovieSimilar.tsx index c485b75ee..7e6d6fd7b 100644 --- a/src/components/MovieDetails/MovieSimilar.tsx +++ b/src/components/MovieDetails/MovieSimilar.tsx @@ -35,7 +35,7 @@ const MovieSimilar: React.FC = () => { return `/api/v1/movie/${router.query.movieId}/similar?page=${ pageIndex + 1 - }`; + }&language=${locale}`; }, { initialSize: 3, diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 3b9d7a104..e8bb3f38d 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -36,6 +36,7 @@ import RTAudRotten from '../../assets/rt_aud_rotten.svg'; import type { RTRating } from '../../../server/api/rottentomatoes'; import Error from '../../pages/_error'; import Head from 'next/head'; +import globalMessages from '../../i18n/globalMessages'; const messages = defineMessages({ releasedate: 'Release Date', @@ -56,6 +57,14 @@ const messages = defineMessages({ viewrequest: 'View Request', pending: 'Pending', overviewunavailable: 'Overview unavailable', + manageModalTitle: 'Manage Movie', + manageModalRequests: 'Requests', + manageModalNoRequests: 'No Requests', + manageModalClearMedia: 'Clear All Media Data', + manageModalClearMediaWarning: + 'This will remove all media data including all requests for this item. This action is irreversible. If this item exists in your Plex library, the media information will be recreated next sync.', + approve: 'Approve', + decline: 'Decline', }); interface MovieDetailsProps { @@ -144,11 +153,13 @@ const MovieDetails: React.FC = ({ movie }) => { /> setShowManager(false)} subText={data.title} > -

Requests

+

+ {intl.formatMessage(messages.manageModalRequests)} +

    {data.mediaInfo?.requests?.map((request) => ( @@ -160,7 +171,9 @@ const MovieDetails: React.FC = ({ movie }) => { ))} {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • No requests
  • +
  • + {intl.formatMessage(messages.manageModalNoRequests)} +
  • )}
@@ -171,12 +184,10 @@ const MovieDetails: React.FC = ({ movie }) => { className="w-full text-center" onClick={() => deleteMedia()} > - Clear All Media Data + {intl.formatMessage(messages.manageModalClearMedia)}
- This will remove all media data including all requests for this - item. This action is irreversible. If this item exists in your - Plex library, the media information will be recreated next sync. + {intl.formatMessage(messages.manageModalClearMediaWarning)}
)} @@ -192,13 +203,19 @@ const MovieDetails: React.FC = ({ movie }) => {
{data.mediaInfo?.status === MediaStatus.AVAILABLE && ( - Available + + {intl.formatMessage(globalMessages.available)} + )} {data.mediaInfo?.status === MediaStatus.PROCESSING && ( - Unavailable + + {intl.formatMessage(globalMessages.unavailable)} + )} {data.mediaInfo?.status === MediaStatus.PENDING && ( - Pending + + {intl.formatMessage(globalMessages.pending)} + )}

@@ -309,7 +326,7 @@ const MovieDetails: React.FC = ({ movie }) => { clipRule="evenodd" /> - Approve + {intl.formatMessage(messages.approve)} modifyRequest('decline')} @@ -326,7 +343,7 @@ const MovieDetails: React.FC = ({ movie }) => { clipRule="evenodd" /> - Decline + {intl.formatMessage(messages.decline)} )} diff --git a/src/components/PendingRequest/index.tsx b/src/components/PendingRequest/index.tsx deleted file mode 100644 index def70ac45..000000000 --- a/src/components/PendingRequest/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useState } from 'react'; -import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; -import Button from '../Common/Button'; -import { MediaRequest } from '../../../server/entity/MediaRequest'; -import axios from 'axios'; - -const messages = defineMessages({ - pendingtitle: 'Pending Request', - pendingdescription: - 'This title was requested by {username} ({email}) on {date}', - approve: 'Approve', - decline: 'Decline', -}); - -interface PendingRequestProps { - request: MediaRequest; - onUpdate: () => void; -} - -const PendingRequest: React.FC = ({ - request, - onUpdate, -}) => { - const intl = useIntl(); - const [isLoading, setLoading] = useState(false); - - const updateStatus = async (status: 'approve' | 'decline') => { - setLoading(true); - const response = await axios.get(`/api/v1/request/${request.id}/${status}`); - - if (response.data) { - onUpdate(); - setLoading(false); - } - }; - - return ( -
-
-

- -

-
-

- -

-
-
- - - - - - -
-
-
- ); -}; - -export default PendingRequest; diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index fd90c93bf..b10909eb3 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -1,10 +1,15 @@ import React, { useState } from 'react'; import type { MediaRequest } from '../../../server/entity/MediaRequest'; -import { FormattedDate } from 'react-intl'; +import { FormattedDate, useIntl, defineMessages } from 'react-intl'; import Badge from '../Common/Badge'; import { MediaRequestStatus } from '../../../server/constants/media'; import Button from '../Common/Button'; import axios from 'axios'; +import globalMessages from '../../i18n/globalMessages'; + +const messages = defineMessages({ + seasons: 'Seasons', +}); interface RequestBlockProps { request: MediaRequest; @@ -12,6 +17,7 @@ interface RequestBlockProps { } const RequestBlock: React.FC = ({ request, onUpdate }) => { + const intl = useIntl(); const [isUpdating, setIsUpdating] = useState(false); const updateRequest = async (type: 'approve' | 'decline'): Promise => { @@ -143,16 +149,24 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {
{request.status === MediaRequestStatus.AVAILABLE && ( - Available + + {intl.formatMessage(globalMessages.available)} + )} {request.status === MediaRequestStatus.APPROVED && ( - Approved + + {intl.formatMessage(globalMessages.approved)} + )} {request.status === MediaRequestStatus.DECLINED && ( - Declined + + {intl.formatMessage(globalMessages.declined)} + )} {request.status === MediaRequestStatus.PENDING && ( - Pending + + {intl.formatMessage(globalMessages.pending)} + )}
@@ -176,7 +190,7 @@ const RequestBlock: React.FC = ({ request, onUpdate }) => {

{(request.seasons ?? []).length > 0 && (
- Seasons + {intl.formatMessage(messages.seasons)} {request.seasons.map((season) => ( {season.seasonNumber} diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index 00b3131f3..9d13948db 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -19,6 +19,16 @@ const messages = defineMessages({ 'Your request will be immediately approved. Do you wish to continue?', cancelrequest: 'This will remove your request. Are you sure you want to continue?', + requestSuccess: '{title} successfully requested!', + requestCancel: 'Request for {title} cancelled', + requesttitle: 'Request {title}', + close: 'Close', + cancel: 'Cancel Request', + cancelling: 'Cancelling...', + pendingrequest: 'Pending request for {title}', + requesting: 'Requesting...', + request: 'Request', + requestfrom: 'There is currently a pending request from {username}', }); interface RequestModalProps extends React.HTMLAttributes { @@ -62,7 +72,12 @@ const MovieRequestModal: React.FC = ({ } addToast( - {data?.title} succesfully requested! + {intl.formatMessage(messages.requestSuccess, { + title: data?.title, + strong: function strong(msg) { + return {msg}; + }, + })} , { appearance: 'success', autoDismiss: true } ); @@ -84,7 +99,12 @@ const MovieRequestModal: React.FC = ({ } addToast( - {data?.title} request cancelled! + {intl.formatMessage(messages.cancelrequest, { + title: data?.title, + strong: function strong(msg) { + return {msg}; + }, + })} , { appearance: 'success', autoDismiss: true } ); @@ -109,15 +129,22 @@ const MovieRequestModal: React.FC = ({ onCancel={onCancel} onOk={isOwner ? () => cancelRequest() : undefined} okDisabled={isUpdating} - title={`Pending request for ${data?.title}`} - okText={isUpdating ? 'Cancelling...' : 'Cancel Request'} + title={intl.formatMessage(messages.pendingrequest, { + title: data?.title, + })} + okText={ + isUpdating + ? intl.formatMessage(messages.cancelling) + : intl.formatMessage(messages.cancel) + } okButtonType={'danger'} - cancelText="Close" + cancelText={intl.formatMessage(messages.close)} iconSvg={} {...props} > - There is currently a pending request from{' '} - {activeRequest.requestedBy.username}. + {intl.formatMessage(messages.requestfrom, { + username: activeRequest.requestedBy.username, + })} ); } @@ -129,8 +156,12 @@ const MovieRequestModal: React.FC = ({ onCancel={onCancel} onOk={sendRequest} okDisabled={isUpdating} - title={`Request ${data?.title}`} - okText={isUpdating ? 'Requesting...' : 'Request'} + title={intl.formatMessage(messages.requesttitle)} + okText={ + isUpdating + ? intl.formatMessage(messages.requesting) + : intl.formatMessage(messages.request) + } okButtonType={'primary'} iconSvg={} {...props} diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index a78f852dc..f3feb0c6b 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -14,11 +14,25 @@ import { import { TvDetails, SeasonWithEpisodes } from '../../../server/models/Tv'; import type SeasonRequest from '../../../server/entity/SeasonRequest'; import Badge from '../Common/Badge'; +import globalMessages from '../../i18n/globalMessages'; const messages = defineMessages({ requestadmin: 'Your request will be immediately approved.', cancelrequest: 'This will remove your request. Are you sure you want to continue?', + requestSuccess: '{title} successfully requested!', + requestCancel: 'Request for {title} cancelled', + requesttitle: 'Request {title}', + requesting: 'Requesting...', + requestseasons: + 'Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}', + selectseason: 'Select season(s)', + season: 'Season', + numberofepisodes: '# of Episodes', + status: 'Status', + seasonnumber: 'Season {number}', + extras: 'Extras', + notrequested: 'Not Requested', }); interface RequestModalProps extends React.HTMLAttributes { @@ -61,7 +75,12 @@ const TvRequestModal: React.FC = ({ } addToast( - {data?.name} succesfully requested! + {intl.formatMessage(messages.requestSuccess, { + title: data?.name, + strong: function strong(msg) { + return {msg}; + }, + })} , { appearance: 'success', autoDismiss: true } ); @@ -177,11 +196,13 @@ const TvRequestModal: React.FC = ({ backgroundClickable onCancel={onCancel} onOk={() => sendRequest()} - title={`Request ${data?.name}`} + title={intl.formatMessage(messages.requesttitle, { title: data?.name })} okText={ selectedSeasons.length === 0 - ? 'Select a season' - : `Request ${selectedSeasons.length} seasons` + ? intl.formatMessage(messages.selectseason) + : intl.formatMessage(messages.requestseasons, { + seasonCount: selectedSeasons.length, + }) } okDisabled={selectedSeasons.length === 0} okButtonType="primary" @@ -238,13 +259,13 @@ const TvRequestModal: React.FC = ({ - Season + {intl.formatMessage(messages.season)} - # Of Episodes + {intl.formatMessage(messages.numberofepisodes)} - Status + {intl.formatMessage(messages.status)} @@ -303,39 +324,55 @@ const TvRequestModal: React.FC = ({ {season.seasonNumber === 0 - ? 'Extras' - : `Season ${season.seasonNumber}`} + ? intl.formatMessage(messages.extras) + : intl.formatMessage(messages.seasonnumber, { + number: season.seasonNumber, + })} {season.episodeCount} {!seasonRequest && !mediaSeason && ( - Not Requested + + {intl.formatMessage(messages.notrequested)} + )} {!mediaSeason && seasonRequest?.status === MediaRequestStatus.PENDING && ( - Pending + + {intl.formatMessage(globalMessages.pending)} + )} {!mediaSeason && seasonRequest?.status === MediaRequestStatus.APPROVED && ( - Unavailable + + {intl.formatMessage( + globalMessages.unavailable + )} + )} {!mediaSeason && seasonRequest?.status === MediaRequestStatus.AVAILABLE && ( - Available + + {intl.formatMessage(globalMessages.available)} + )} {mediaSeason?.status === MediaStatus.PARTIALLY_AVAILABLE && ( - Partially Available + {intl.formatMessage( + globalMessages.partiallyavailable + )} )} {mediaSeason?.status === MediaStatus.AVAILABLE && ( - Available + + {intl.formatMessage(globalMessages.available)} + )} diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 129a69b27..31121b963 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -8,6 +8,13 @@ import { import { useSWRInfinite } from 'swr'; import ListView from '../Common/ListView'; import { LanguageContext } from '../../context/LanguageContext'; +import { defineMessages, useIntl } from 'react-intl'; +import Header from '../Common/Header'; +import globalMessages from '../../i18n/globalMessages'; + +const messages = defineMessages({ + searchresults: 'Search Results', +}); interface SearchResult { page: number; @@ -17,6 +24,7 @@ interface SearchResult { } const Search: React.FC = () => { + const intl = useIntl(); const { locale } = useContext(LanguageContext); const router = useRouter(); const { data, error, size, setSize } = useSWRInfinite( @@ -58,25 +66,21 @@ const Search: React.FC = () => { return ( <> -
-
-

- Search Results -

-
+
+
{intl.formatMessage(messages.searchresults)}
diff --git a/src/components/Settings/CopyButton.tsx b/src/components/Settings/CopyButton.tsx index 34a22a9e1..717667d59 100644 --- a/src/components/Settings/CopyButton.tsx +++ b/src/components/Settings/CopyButton.tsx @@ -1,8 +1,14 @@ import React, { useEffect } from 'react'; import useClipboard from 'react-use-clipboard'; import { useToasts } from 'react-toast-notifications'; +import { defineMessages, useIntl } from 'react-intl'; + +const messages = defineMessages({ + copied: 'Copied API key to clipboard', +}); const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => { + const intl = useIntl(); const [isCopied, setCopied] = useClipboard(textToCopy, { successDuration: 1000, }); @@ -10,12 +16,12 @@ const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => { useEffect(() => { if (isCopied) { - addToast('Copied API key to clipboard', { + addToast(intl.formatMessage(messages.copied), { appearance: 'info', autoDismiss: true, }); } - }, [isCopied, addToast]); + }, [isCopied, addToast, intl]); return ( + ))} diff --git a/src/components/Settings/SettingsLayout.tsx b/src/components/Settings/SettingsLayout.tsx index 650720fa3..49c4eda78 100644 --- a/src/components/Settings/SettingsLayout.tsx +++ b/src/components/Settings/SettingsLayout.tsx @@ -1,6 +1,17 @@ import React from 'react'; import Link from 'next/link'; import { useRouter } from 'next/router'; +import { defineMessages, useIntl } from 'react-intl'; + +const messages = defineMessages({ + menuGeneralSettings: 'General Settings', + menuPlexSettings: 'Plex', + menuServices: 'Services', + menuNotifications: 'Notifications', + menuLogs: 'Logs', + menuJobs: 'Jobs', + menuAbout: 'About', +}); interface SettingsRoute { text: string; @@ -8,46 +19,48 @@ interface SettingsRoute { regex: RegExp; } -const settingsRoutes: SettingsRoute[] = [ - { - text: 'General Settings', - route: '/settings/main', - regex: /^\/settings(\/main)?$/, - }, - { - text: 'Plex', - route: '/settings/plex', - regex: /^\/settings\/plex/, - }, - { - text: 'Services', - route: '/settings/services', - regex: /^\/settings\/services/, - }, - { - text: 'Notifications', - route: '/settings/notifications/email', - regex: /^\/settings\/notifications/, - }, - { - text: 'Logs', - route: '/settings/logs', - regex: /^\/settings\/logs/, - }, - { - text: 'Jobs', - route: '/settings/jobs', - regex: /^\/settings\/jobs/, - }, - { - text: 'About', - route: '/settings/about', - regex: /^\/settings\/about/, - }, -]; - const SettingsLayout: React.FC = ({ children }) => { const router = useRouter(); + const intl = useIntl(); + + const settingsRoutes: SettingsRoute[] = [ + { + text: intl.formatMessage(messages.menuGeneralSettings), + route: '/settings/main', + regex: /^\/settings(\/main)?$/, + }, + { + text: intl.formatMessage(messages.menuPlexSettings), + route: '/settings/plex', + regex: /^\/settings\/plex/, + }, + { + text: intl.formatMessage(messages.menuServices), + route: '/settings/services', + regex: /^\/settings\/services/, + }, + { + text: intl.formatMessage(messages.menuNotifications), + route: '/settings/notifications/email', + regex: /^\/settings\/notifications/, + }, + { + text: intl.formatMessage(messages.menuLogs), + route: '/settings/logs', + regex: /^\/settings\/logs/, + }, + { + text: intl.formatMessage(messages.menuJobs), + route: '/settings/jobs', + regex: /^\/settings\/jobs/, + }, + { + text: intl.formatMessage(messages.menuAbout), + route: '/settings/about', + regex: /^\/settings\/about/, + }, + ]; + const activeLinkColor = 'border-indigo-600 text-indigo-500 focus:outline-none focus:text-indigo-500 focus:border-indigo-500'; diff --git a/src/components/Settings/SettingsMain.tsx b/src/components/Settings/SettingsMain.tsx index cf0097983..fdb6c249f 100644 --- a/src/components/Settings/SettingsMain.tsx +++ b/src/components/Settings/SettingsMain.tsx @@ -9,8 +9,13 @@ import Button from '../Common/Button'; import { defineMessages, useIntl } from 'react-intl'; const messages = defineMessages({ + generalsettings: 'General Settings', + generalsettingsDescription: + 'These are settings related to general Overseerr configuration.', save: 'Save Changes', saving: 'Saving...', + apikey: 'API Key', + applicationurl: 'Application URL', }); const SettingsMain: React.FC = () => { @@ -27,10 +32,10 @@ const SettingsMain: React.FC = () => { <>

- General Settings + {intl.formatMessage(messages.generalsettings)}

- These are settings related to general Overseerr configuration. + {intl.formatMessage(messages.generalsettingsDescription)}

@@ -59,7 +64,7 @@ const SettingsMain: React.FC = () => { htmlFor="username" className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2" > - API Key + {intl.formatMessage(messages.apikey)}
@@ -93,7 +98,7 @@ const SettingsMain: React.FC = () => { htmlFor="name" className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px sm:pt-2" > - Application URL + {intl.formatMessage(messages.applicationurl)}
diff --git a/src/components/Settings/SettingsNotifications.tsx b/src/components/Settings/SettingsNotifications.tsx index 9c54d4f60..3ae673aee 100644 --- a/src/components/Settings/SettingsNotifications.tsx +++ b/src/components/Settings/SettingsNotifications.tsx @@ -1,6 +1,13 @@ import Link from 'next/link'; import { useRouter } from 'next/router'; import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +const messages = defineMessages({ + notificationsettings: 'Notification Settings', + notificationsettingsDescription: + 'Here you can pick and choose what types of notifications to send and through what types of services.', +}); interface SettingsRoute { text: string; @@ -23,6 +30,7 @@ const settingsRoutes: SettingsRoute[] = [ const SettingsNotifications: React.FC = ({ children }) => { const router = useRouter(); + const intl = useIntl(); const activeLinkColor = 'bg-gray-700'; @@ -55,11 +63,10 @@ const SettingsNotifications: React.FC = ({ children }) => { <>

- Notification Settings + {intl.formatMessage(messages.notificationsettings)}

- Here you can pick and choose what types of notifications to send and - through what types of services. + {intl.formatMessage(messages.notificationsettingsDescription)}

diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index a6c669f87..d70f220eb 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -9,6 +9,12 @@ import Placeholder from './Placeholder'; import Link from 'next/link'; import { MediaStatus } from '../../../server/constants/media'; import RequestModal from '../RequestModal'; +import { defineMessages, useIntl } from 'react-intl'; + +const messages = defineMessages({ + movie: 'MOVIE', + tvshow: 'SERIES', +}); interface TitleCardProps { id: number; @@ -32,6 +38,7 @@ const TitleCard: React.FC = ({ mediaType, canExpand = false, }) => { + const intl = useIntl(); const [isUpdating, setIsUpdating] = useState(false); const [currentStatus, setCurrentStatus] = useState(status); const [showDetail, setShowDetail] = useState(false); @@ -87,7 +94,9 @@ const TitleCard: React.FC = ({ }`} >
- {mediaType === 'movie' ? 'MOVIE' : 'TV SHOW'} + {mediaType === 'movie' + ? intl.formatMessage(messages.movie) + : intl.formatMessage(messages.tvshow)}
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index a7902e2d9..22965a141 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -27,6 +27,7 @@ import RTAudFresh from '../../assets/rt_aud_fresh.svg'; import RTAudRotten from '../../assets/rt_aud_rotten.svg'; import type { RTRating } from '../../../server/api/rottentomatoes'; import Head from 'next/head'; +import globalMessages from '../../i18n/globalMessages'; const messages = defineMessages({ userrating: 'User Rating', @@ -47,6 +48,14 @@ const messages = defineMessages({ 'Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}', declinerequests: 'Decline {requestCount} {requestCount, plural, one {Request} other {Requests}}', + manageModalTitle: 'Manage Series', + manageModalRequests: 'Requests', + manageModalNoRequests: 'No Requests', + manageModalClearMedia: 'Clear All Media Data', + manageModalClearMediaWarning: + 'This will remove all media data including all requests for this item. This action is irreversible. If this item exists in your Plex library, the media information will be recreated next sync.', + approve: 'Approve', + decline: 'Decline', }); interface TvDetailsProps { @@ -151,7 +160,9 @@ const TvDetails: React.FC = ({ tv }) => { onClose={() => setShowManager(false)} subText={data.name} > -

Requests

+

+ {intl.formatMessage(messages.manageModalTitle)} +

    {data.mediaInfo?.requests?.map((request) => ( @@ -163,7 +174,9 @@ const TvDetails: React.FC = ({ tv }) => { ))} {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • No requests
  • +
  • + {intl.formatMessage(messages.manageModalNoRequests)} +
  • )}
@@ -174,12 +187,10 @@ const TvDetails: React.FC = ({ tv }) => { className="w-full text-center" onClick={() => deleteMedia()} > - Clear All Media Data + {intl.formatMessage(messages.manageModalClearMedia)}
- This will remove all media data including all requests for this - item. This action is irreversible. If this item exists in your - Plex library, the media information will be recreated next sync. + {intl.formatMessage(messages.manageModalClearMediaWarning)}
)} @@ -195,16 +206,24 @@ const TvDetails: React.FC = ({ tv }) => {
{data.mediaInfo?.status === MediaStatus.AVAILABLE && ( - Available + + {intl.formatMessage(globalMessages.available)} + )} {data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE && ( - Partially Available + + {intl.formatMessage(globalMessages.partiallyavailable)} + )} {data.mediaInfo?.status === MediaStatus.PROCESSING && ( - Unavailable + + {intl.formatMessage(globalMessages.unavailable)} + )} {data.mediaInfo?.status === MediaStatus.PENDING && ( - Pending + + {intl.formatMessage(globalMessages.pending)} + )}

diff --git a/src/components/UserEdit/index.tsx b/src/components/UserEdit/index.tsx index f32d9f414..36b05a696 100644 --- a/src/components/UserEdit/index.tsx +++ b/src/components/UserEdit/index.tsx @@ -145,7 +145,9 @@ const UserEdit: React.FC = () => { return ( <> -
Edit User
+
+ +
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 8dbb2705d..98a7d2522 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -3,14 +3,30 @@ import useSWR from 'swr'; import LoadingSpinner from '../Common/LoadingSpinner'; import type { User } from '../../../server/entity/User'; import Badge from '../Common/Badge'; -import { FormattedDate } from 'react-intl'; +import { FormattedDate, defineMessages, useIntl } from 'react-intl'; import Button from '../Common/Button'; import { hasPermission } from '../../../server/lib/permissions'; import { Permission } from '../../hooks/useUser'; import { useRouter } from 'next/router'; import Header from '../Common/Header'; +const messages = defineMessages({ + userlist: 'User List', + username: 'Username', + totalrequests: 'Total Requests', + usertype: 'User Type', + role: 'Role', + created: 'Created', + lastupdated: 'Last Updated', + edit: 'Edit', + delete: 'Delete', + admin: 'Admin', + user: 'User', + plexuser: 'Plex User', +}); + const UserList: React.FC = () => { + const intl = useIntl(); const router = useRouter(); const { data, error } = useSWR('/api/v1/user'); @@ -20,7 +36,7 @@ const UserList: React.FC = () => { return ( <> -
User List
+
{intl.formatMessage(messages.userlist)}
@@ -29,22 +45,22 @@ const UserList: React.FC = () => { - Name + {intl.formatMessage(messages.username)} - Total Requests + {intl.formatMessage(messages.totalrequests)} - User Type + {intl.formatMessage(messages.usertype)} - Role + {intl.formatMessage(messages.role)} - Created + {intl.formatMessage(messages.created)} - Last Updated + {intl.formatMessage(messages.lastupdated)} @@ -77,12 +93,14 @@ const UserList: React.FC = () => {
- Plex User + + {intl.formatMessage(messages.plexuser)} + {hasPermission(Permission.ADMIN, user.permissions) - ? 'Admin' - : 'User'} + ? intl.formatMessage(messages.admin) + : intl.formatMessage(messages.user)} @@ -101,9 +119,11 @@ const UserList: React.FC = () => { ) } > - Edit + {intl.formatMessage(messages.edit)} + + - ))} diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts new file mode 100644 index 000000000..f2cc0edaa --- /dev/null +++ b/src/i18n/globalMessages.ts @@ -0,0 +1,16 @@ +import { defineMessages } from 'react-intl'; + +const globalMessages = defineMessages({ + available: 'Available', + partiallyavailable: 'Partially Available', + processing: 'Processing', + unavailable: 'Unavailable', + pending: 'Pending', + declined: 'Declined', + approved: 'Approved', + movies: 'Movies', + tvshows: 'Series', + cancel: 'Cancel', +}); + +export default globalMessages; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 0bec5c86d..82d07714d 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -18,10 +18,17 @@ "components.Layout.UserDropdown.signout": "Sign Out", "components.Layout.alphawarning": "This is ALPHA software. Almost everything is bound to be nearly broken and/or unstable. Please report issues to the Overseerr Github!", "components.Login.signinplex": "Sign in to continue", + "components.MovieDetails.approve": "Approve", "components.MovieDetails.available": "Available", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cancelrequest": "Cancel Request", "components.MovieDetails.cast": "Cast", + "components.MovieDetails.decline": "Decline", + "components.MovieDetails.manageModalClearMedia": "Clear All Media Data", + "components.MovieDetails.manageModalClearMediaWarning": "This will remove all media data including all requests for this item. This action is irreversible. If this item exists in your Plex library, the media information will be recreated next sync.", + "components.MovieDetails.manageModalNoRequests": "No Requests", + "components.MovieDetails.manageModalRequests": "Requests", + "components.MovieDetails.manageModalTitle": "Manage Movie", "components.MovieDetails.originallanguage": "Original Language", "components.MovieDetails.overview": "Overview", "components.MovieDetails.overviewunavailable": "Overview unavailable", @@ -38,17 +45,46 @@ "components.MovieDetails.unavailable": "Unavailable", "components.MovieDetails.userrating": "User Rating", "components.MovieDetails.viewrequest": "View Request", - "components.PendingRequest.approve": "Approve", - "components.PendingRequest.decline": "Decline", - "components.PendingRequest.pendingdescription": "This title was requested by {username} ({email}) on {date}", - "components.PendingRequest.pendingtitle": "Pending Request", "components.PlexLoginButton.loading": "Loading...", "components.PlexLoginButton.loggingin": "Logging in...", "components.PlexLoginButton.loginwithplex": "Login with Plex", + "components.RequestBlock.seasons": "Seasons", + "components.RequestModal.cancel": "Cancel Request", + "components.RequestModal.cancelling": "Cancelling...", "components.RequestModal.cancelrequest": "This will remove your request. Are you sure you want to continue?", + "components.RequestModal.close": "Close", + "components.RequestModal.extras": "Extras", + "components.RequestModal.notrequested": "Not Requested", + "components.RequestModal.numberofepisodes": "# of Episodes", + "components.RequestModal.pendingrequest": "Pending request for {title}", + "components.RequestModal.request": "Request", + "components.RequestModal.requestCancel": "Request for {title} cancelled", + "components.RequestModal.requestSuccess": "{title} successfully requested!", "components.RequestModal.requestadmin": "Your request will be immediately approved.", + "components.RequestModal.requestfrom": "There is currently a pending request from {username}", + "components.RequestModal.requesting": "Requesting...", + "components.RequestModal.requestseasons": "Request {seasonCount} {seasonCount, plural, one {Season} other {Seasons}}", + "components.RequestModal.requesttitle": "Request {title}", + "components.RequestModal.season": "Season", + "components.RequestModal.seasonnumber": "Season {number}", + "components.RequestModal.selectseason": "Select season(s)", + "components.RequestModal.status": "Status", + "components.Search.searchresults": "Search Results", + "components.Settings.Notifications.agentenabled": "Agent Enabled", + "components.Settings.Notifications.authPass": "Auth Pass", + "components.Settings.Notifications.authUser": "Auth User", + "components.Settings.Notifications.emailsender": "Email Sender Address", + "components.Settings.Notifications.enableSsl": "Enable SSL", "components.Settings.Notifications.save": "Save Changes", "components.Settings.Notifications.saving": "Saving...", + "components.Settings.Notifications.smtpHost": "SMTP Host", + "components.Settings.Notifications.smtpPort": "SMTP Port", + "components.Settings.Notifications.validationFromRequired": "You must provide an email sender address", + "components.Settings.Notifications.validationSmtpHostRequired": "You must provide an SMTP host", + "components.Settings.Notifications.validationSmtpPortRequired": "You must provide an SMTP port", + "components.Settings.Notifications.validationWebhookUrlRequired": "You must provide a webhook URL", + "components.Settings.Notifications.webhookUrl": "Webhook URL", + "components.Settings.Notifications.webhookUrlPlaceholder": "Server Settings -> Integrations -> Webhooks", "components.Settings.RadarrModal.add": "Add Server", "components.Settings.RadarrModal.apiKey": "API Key", "components.Settings.RadarrModal.apiKeyPlaceholder": "Your Radarr API Key", @@ -114,17 +150,33 @@ "components.Settings.addradarr": "Add Radarr Server", "components.Settings.address": "Address", "components.Settings.addsonarr": "Add Sonarr Server", + "components.Settings.apikey": "API Key", + "components.Settings.applicationurl": "Application URL", "components.Settings.cancelscan": "Cancel Scan", + "components.Settings.copied": "Copied API key to clipboard", "components.Settings.currentlibrary": "Current Library: {name}", "components.Settings.default": "Default", "components.Settings.default4k": "Default 4K", "components.Settings.delete": "Delete", "components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?", "components.Settings.edit": "Edit", + "components.Settings.generalsettings": "General Settings", + "components.Settings.generalsettingsDescription": "These are settings related to general Overseerr configuration.", "components.Settings.hostname": "Hostname/IP", + "components.Settings.jobname": "Job Name", "components.Settings.librariesRemaining": "Libraries Remaining: {count}", "components.Settings.manualscan": "Manual Library Scan", "components.Settings.manualscanDescription": "Normally, this will only be run once every 6 hours. Overseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one time full manual library scan is recommended!", + "components.Settings.menuAbout": "About", + "components.Settings.menuGeneralSettings": "General Settings", + "components.Settings.menuJobs": "Jobs", + "components.Settings.menuLogs": "Logs", + "components.Settings.menuNotifications": "Notifications", + "components.Settings.menuPlexSettings": "Plex", + "components.Settings.menuServices": "Services", + "components.Settings.nextexecution": "Next Execution", + "components.Settings.notificationsettings": "Notification Settings", + "components.Settings.notificationsettingsDescription": "Here you can pick and choose what types of notifications to send and through what types of services.", "components.Settings.notrunning": "Not Running", "components.Settings.plexlibraries": "Plex Libraries", "components.Settings.plexlibrariesDescription": "These are the libraries Overseerr will scan for titles. If you see no libraries listed, you will need to run at least one sync by clicking the button below. You must first configure and save your plex connection settings before you will be able to retrieve your libraries.", @@ -133,6 +185,7 @@ "components.Settings.port": "Port", "components.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). Administrations can override a titles connection to use in the manage title screen.", "components.Settings.radarrsettings": "Radarr Settings", + "components.Settings.runnow": "Run Now", "components.Settings.save": "Save Changes", "components.Settings.saving": "Saving...", "components.Settings.servername": "Server Name (Automatically Set)", @@ -152,11 +205,20 @@ "components.Setup.signinMessage": "Get started by logging in with your Plex account", "components.Setup.welcome": "Welcome to Overseerr", "components.Slider.noresults": "No Results", + "components.TitleCard.movie": "MOVIE", + "components.TitleCard.tvshow": "SERIES", + "components.TvDetails.approve": "Approve", "components.TvDetails.approverequests": "Approve {requestCount} {requestCount, plural, one {Request} other {Requests}}", "components.TvDetails.available": "Available", "components.TvDetails.cancelrequest": "Cancel Request", "components.TvDetails.cast": "Cast", + "components.TvDetails.decline": "Decline", "components.TvDetails.declinerequests": "Decline {requestCount} {requestCount, plural, one {Request} other {Requests}}", + "components.TvDetails.manageModalClearMedia": "Clear All Media Data", + "components.TvDetails.manageModalClearMediaWarning": "This will remove all media data including all requests for this item. This action is irreversible. If this item exists in your Plex library, the media information will be recreated next sync.", + "components.TvDetails.manageModalNoRequests": "No Requests", + "components.TvDetails.manageModalRequests": "Requests", + "components.TvDetails.manageModalTitle": "Manage Series", "components.TvDetails.originallanguage": "Original Language", "components.TvDetails.overview": "Overview", "components.TvDetails.overviewunavailable": "Overview unavailable", @@ -193,6 +255,28 @@ "components.UserEdit.usersaved": "User succesfully saved", "components.UserEdit.vote": "Vote", "components.UserEdit.voteDescription": "Grants permission to vote on requests (voting not yet implemented)", + "components.UserList.admin": "Admin", + "components.UserList.created": "Created", + "components.UserList.delete": "Delete", + "components.UserList.edit": "Edit", + "components.UserList.lastupdated": "Last Updated", + "components.UserList.plexuser": "Plex User", + "components.UserList.role": "Role", + "components.UserList.totalrequests": "Total Requests", + "components.UserList.user": "User", + "components.UserList.userlist": "User List", + "components.UserList.username": "Username", + "components.UserList.usertype": "User Type", + "i18n.approved": "Approved", + "i18n.available": "Available", + "i18n.cancel": "Cancel", + "i18n.declined": "Declined", + "i18n.movies": "Movies", + "i18n.partiallyavailable": "Partially Available", + "i18n.pending": "Pending", + "i18n.processing": "Processing", + "i18n.tvshows": "Series", + "i18n.unavailable": "Unavailable", "pages.internalServerError": "{statusCode} - Internal Server Error", "pages.oops": "Oops", "pages.pageNotFound": "404 - Page Not Found", diff --git a/src/i18n/locale/fr.json b/src/i18n/locale/fr.json index 3520f9160..4af28fbab 100644 --- a/src/i18n/locale/fr.json +++ b/src/i18n/locale/fr.json @@ -18,10 +18,17 @@ "components.Layout.UserDropdown.signout": "Déconnexion", "components.Layout.alphawarning": "Ce logiciel est en version ALPHA. Presque tout est succeptible d'être buggé et/ou instalbe. Veuillez signaler tout problème sur le Github d'Overseer!", "components.Login.signinplex": "S'identifier pour continuer", + "components.MovieDetails.approve": "", "components.MovieDetails.available": "Disponible", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cancelrequest": "Annuler la Requête", "components.MovieDetails.cast": "Casting", + "components.MovieDetails.decline": "", + "components.MovieDetails.manageModalClearMedia": "", + "components.MovieDetails.manageModalClearMediaWarning": "", + "components.MovieDetails.manageModalNoRequests": "", + "components.MovieDetails.manageModalRequests": "", + "components.MovieDetails.manageModalTitle": "", "components.MovieDetails.originallanguage": "", "components.MovieDetails.overview": "", "components.MovieDetails.overviewunavailable": "", @@ -38,17 +45,46 @@ "components.MovieDetails.unavailable": "", "components.MovieDetails.userrating": "", "components.MovieDetails.viewrequest": "", - "components.PendingRequest.approve": "", - "components.PendingRequest.decline": "", - "components.PendingRequest.pendingdescription": "", - "components.PendingRequest.pendingtitle": "", "components.PlexLoginButton.loading": "", "components.PlexLoginButton.loggingin": "", "components.PlexLoginButton.loginwithplex": "", + "components.RequestBlock.seasons": "", + "components.RequestModal.cancel": "", + "components.RequestModal.cancelling": "", "components.RequestModal.cancelrequest": "", + "components.RequestModal.close": "", + "components.RequestModal.extras": "", + "components.RequestModal.notrequested": "", + "components.RequestModal.numberofepisodes": "", + "components.RequestModal.pendingrequest": "", + "components.RequestModal.request": "", + "components.RequestModal.requestCancel": "", + "components.RequestModal.requestSuccess": "", "components.RequestModal.requestadmin": "", + "components.RequestModal.requestfrom": "", + "components.RequestModal.requesting": "", + "components.RequestModal.requestseasons": "", + "components.RequestModal.requesttitle": "", + "components.RequestModal.season": "", + "components.RequestModal.seasonnumber": "", + "components.RequestModal.selectseason": "", + "components.RequestModal.status": "", + "components.Search.searchresults": "", + "components.Settings.Notifications.agentenabled": "", + "components.Settings.Notifications.authPass": "", + "components.Settings.Notifications.authUser": "", + "components.Settings.Notifications.emailsender": "", + "components.Settings.Notifications.enableSsl": "", "components.Settings.Notifications.save": "", "components.Settings.Notifications.saving": "", + "components.Settings.Notifications.smtpHost": "", + "components.Settings.Notifications.smtpPort": "", + "components.Settings.Notifications.validationFromRequired": "", + "components.Settings.Notifications.validationSmtpHostRequired": "", + "components.Settings.Notifications.validationSmtpPortRequired": "", + "components.Settings.Notifications.validationWebhookUrlRequired": "", + "components.Settings.Notifications.webhookUrl": "", + "components.Settings.Notifications.webhookUrlPlaceholder": "", "components.Settings.RadarrModal.add": "", "components.Settings.RadarrModal.apiKey": "", "components.Settings.RadarrModal.apiKeyPlaceholder": "", @@ -114,17 +150,33 @@ "components.Settings.addradarr": "", "components.Settings.address": "", "components.Settings.addsonarr": "", + "components.Settings.apikey": "", + "components.Settings.applicationurl": "", "components.Settings.cancelscan": "", + "components.Settings.copied": "", "components.Settings.currentlibrary": "", "components.Settings.default": "", "components.Settings.default4k": "", "components.Settings.delete": "", "components.Settings.deleteserverconfirm": "", "components.Settings.edit": "", + "components.Settings.generalsettings": "", + "components.Settings.generalsettingsDescription": "", "components.Settings.hostname": "", + "components.Settings.jobname": "", "components.Settings.librariesRemaining": "", "components.Settings.manualscan": "", "components.Settings.manualscanDescription": "", + "components.Settings.menuAbout": "", + "components.Settings.menuGeneralSettings": "", + "components.Settings.menuJobs": "", + "components.Settings.menuLogs": "", + "components.Settings.menuNotifications": "", + "components.Settings.menuPlexSettings": "", + "components.Settings.menuServices": "", + "components.Settings.nextexecution": "", + "components.Settings.notificationsettings": "", + "components.Settings.notificationsettingsDescription": "", "components.Settings.notrunning": "", "components.Settings.plexlibraries": "", "components.Settings.plexlibrariesDescription": "", @@ -133,6 +185,7 @@ "components.Settings.port": "", "components.Settings.radarrSettingsDescription": "", "components.Settings.radarrsettings": "", + "components.Settings.runnow": "", "components.Settings.save": "", "components.Settings.saving": "", "components.Settings.servername": "", @@ -152,11 +205,20 @@ "components.Setup.signinMessage": "", "components.Setup.welcome": "", "components.Slider.noresults": "", + "components.TitleCard.movie": "", + "components.TitleCard.tvshow": "", + "components.TvDetails.approve": "", "components.TvDetails.approverequests": "", "components.TvDetails.available": "", "components.TvDetails.cancelrequest": "", "components.TvDetails.cast": "", + "components.TvDetails.decline": "", "components.TvDetails.declinerequests": "", + "components.TvDetails.manageModalClearMedia": "", + "components.TvDetails.manageModalClearMediaWarning": "", + "components.TvDetails.manageModalNoRequests": "", + "components.TvDetails.manageModalRequests": "", + "components.TvDetails.manageModalTitle": "", "components.TvDetails.originallanguage": "", "components.TvDetails.overview": "", "components.TvDetails.overviewunavailable": "", @@ -193,6 +255,28 @@ "components.UserEdit.usersaved": "", "components.UserEdit.vote": "", "components.UserEdit.voteDescription": "", + "components.UserList.admin": "", + "components.UserList.created": "", + "components.UserList.delete": "", + "components.UserList.edit": "", + "components.UserList.lastupdated": "", + "components.UserList.plexuser": "", + "components.UserList.role": "", + "components.UserList.totalrequests": "", + "components.UserList.user": "", + "components.UserList.userlist": "", + "components.UserList.username": "", + "components.UserList.usertype": "", + "i18n.approved": "", + "i18n.available": "", + "i18n.cancel": "", + "i18n.declined": "", + "i18n.movies": "", + "i18n.partiallyavailable": "", + "i18n.pending": "", + "i18n.processing": "", + "i18n.tvshows": "", + "i18n.unavailable": "", "pages.internalServerError": "", "pages.oops": "", "pages.pageNotFound": "", diff --git a/src/i18n/locale/ja.json b/src/i18n/locale/ja.json index 79c38558a..8b7d5b629 100644 --- a/src/i18n/locale/ja.json +++ b/src/i18n/locale/ja.json @@ -18,10 +18,17 @@ "components.Layout.UserDropdown.signout": "", "components.Layout.alphawarning": "", "components.Login.signinplex": "", + "components.MovieDetails.approve": "", "components.MovieDetails.available": "", "components.MovieDetails.budget": "興行収入", "components.MovieDetails.cancelrequest": "チャンセルリクエスト", "components.MovieDetails.cast": "キャスト", + "components.MovieDetails.decline": "", + "components.MovieDetails.manageModalClearMedia": "", + "components.MovieDetails.manageModalClearMediaWarning": "", + "components.MovieDetails.manageModalNoRequests": "", + "components.MovieDetails.manageModalRequests": "", + "components.MovieDetails.manageModalTitle": "", "components.MovieDetails.originallanguage": "言語", "components.MovieDetails.overview": "ストーリー", "components.MovieDetails.overviewunavailable": "", @@ -38,17 +45,46 @@ "components.MovieDetails.unavailable": "", "components.MovieDetails.userrating": "ユーザー評価", "components.MovieDetails.viewrequest": "", - "components.PendingRequest.approve": "", - "components.PendingRequest.decline": "", - "components.PendingRequest.pendingdescription": "", - "components.PendingRequest.pendingtitle": "", "components.PlexLoginButton.loading": "", "components.PlexLoginButton.loggingin": "", "components.PlexLoginButton.loginwithplex": "", + "components.RequestBlock.seasons": "", + "components.RequestModal.cancel": "", + "components.RequestModal.cancelling": "", "components.RequestModal.cancelrequest": "このリクエストをキャンセルしてよろしいですか?", + "components.RequestModal.close": "", + "components.RequestModal.extras": "", + "components.RequestModal.notrequested": "", + "components.RequestModal.numberofepisodes": "", + "components.RequestModal.pendingrequest": "", + "components.RequestModal.request": "", + "components.RequestModal.requestCancel": "", + "components.RequestModal.requestSuccess": "", "components.RequestModal.requestadmin": "このリクエストが今すぐ承認致します。よろしいですか?", + "components.RequestModal.requestfrom": "", + "components.RequestModal.requesting": "", + "components.RequestModal.requestseasons": "", + "components.RequestModal.requesttitle": "", + "components.RequestModal.season": "", + "components.RequestModal.seasonnumber": "", + "components.RequestModal.selectseason": "", + "components.RequestModal.status": "", + "components.Search.searchresults": "", + "components.Settings.Notifications.agentenabled": "", + "components.Settings.Notifications.authPass": "", + "components.Settings.Notifications.authUser": "", + "components.Settings.Notifications.emailsender": "", + "components.Settings.Notifications.enableSsl": "", "components.Settings.Notifications.save": "", "components.Settings.Notifications.saving": "", + "components.Settings.Notifications.smtpHost": "", + "components.Settings.Notifications.smtpPort": "", + "components.Settings.Notifications.validationFromRequired": "", + "components.Settings.Notifications.validationSmtpHostRequired": "", + "components.Settings.Notifications.validationSmtpPortRequired": "", + "components.Settings.Notifications.validationWebhookUrlRequired": "", + "components.Settings.Notifications.webhookUrl": "", + "components.Settings.Notifications.webhookUrlPlaceholder": "", "components.Settings.RadarrModal.add": "", "components.Settings.RadarrModal.apiKey": "", "components.Settings.RadarrModal.apiKeyPlaceholder": "", @@ -114,17 +150,33 @@ "components.Settings.addradarr": "", "components.Settings.address": "", "components.Settings.addsonarr": "", + "components.Settings.apikey": "", + "components.Settings.applicationurl": "", "components.Settings.cancelscan": "", + "components.Settings.copied": "", "components.Settings.currentlibrary": "", "components.Settings.default": "", "components.Settings.default4k": "", "components.Settings.delete": "", "components.Settings.deleteserverconfirm": "", "components.Settings.edit": "", + "components.Settings.generalsettings": "", + "components.Settings.generalsettingsDescription": "", "components.Settings.hostname": "", + "components.Settings.jobname": "", "components.Settings.librariesRemaining": "", "components.Settings.manualscan": "", "components.Settings.manualscanDescription": "", + "components.Settings.menuAbout": "", + "components.Settings.menuGeneralSettings": "", + "components.Settings.menuJobs": "", + "components.Settings.menuLogs": "", + "components.Settings.menuNotifications": "", + "components.Settings.menuPlexSettings": "", + "components.Settings.menuServices": "", + "components.Settings.nextexecution": "", + "components.Settings.notificationsettings": "", + "components.Settings.notificationsettingsDescription": "", "components.Settings.notrunning": "", "components.Settings.plexlibraries": "", "components.Settings.plexlibrariesDescription": "", @@ -133,6 +185,7 @@ "components.Settings.port": "", "components.Settings.radarrSettingsDescription": "", "components.Settings.radarrsettings": "", + "components.Settings.runnow": "", "components.Settings.save": "", "components.Settings.saving": "", "components.Settings.servername": "", @@ -152,11 +205,20 @@ "components.Setup.signinMessage": "", "components.Setup.welcome": "", "components.Slider.noresults": "", + "components.TitleCard.movie": "", + "components.TitleCard.tvshow": "", + "components.TvDetails.approve": "", "components.TvDetails.approverequests": "", "components.TvDetails.available": "", "components.TvDetails.cancelrequest": "", "components.TvDetails.cast": "", + "components.TvDetails.decline": "", "components.TvDetails.declinerequests": "", + "components.TvDetails.manageModalClearMedia": "", + "components.TvDetails.manageModalClearMediaWarning": "", + "components.TvDetails.manageModalNoRequests": "", + "components.TvDetails.manageModalRequests": "", + "components.TvDetails.manageModalTitle": "", "components.TvDetails.originallanguage": "", "components.TvDetails.overview": "", "components.TvDetails.overviewunavailable": "", @@ -193,6 +255,28 @@ "components.UserEdit.usersaved": "", "components.UserEdit.vote": "", "components.UserEdit.voteDescription": "", + "components.UserList.admin": "", + "components.UserList.created": "", + "components.UserList.delete": "", + "components.UserList.edit": "", + "components.UserList.lastupdated": "", + "components.UserList.plexuser": "", + "components.UserList.role": "", + "components.UserList.totalrequests": "", + "components.UserList.user": "", + "components.UserList.userlist": "", + "components.UserList.username": "", + "components.UserList.usertype": "", + "i18n.approved": "", + "i18n.available": "", + "i18n.cancel": "", + "i18n.declined": "", + "i18n.movies": "", + "i18n.partiallyavailable": "", + "i18n.pending": "", + "i18n.processing": "", + "i18n.tvshows": "", + "i18n.unavailable": "", "pages.internalServerError": "", "pages.oops": "ああ", "pages.pageNotFound": "", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 8a1289656..d1878a1d7 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -13,10 +13,13 @@ import { LanguageContext, AvailableLocales } from '../context/LanguageContext'; import Head from 'next/head'; import Toast from '../components/Toast'; -const loadLocaleData = (locale: string) => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const loadLocaleData = (locale: string): Promise => { switch (locale) { case 'ja': return import('../i18n/locale/ja.json'); + case 'fr': + return import('../i18n/locale/fr.json'); default: return import('../i18n/locale/en.json'); }