Add localization support to admin status and error messages (#4631)
* Initial plan * Add localization support to admin form status and error messages Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Format updated files with prettier Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Replace t() with Translation component in admin page JSX Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * update package-lock.json * Update web/i18n/en/translation.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gabek <414923+gabek@users.noreply.github.com> Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
gabek
Copilot
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Gabe Kangas
parent
fd89c6e8f2
commit
383b80851b
@@ -1,5 +1,6 @@
|
|||||||
import { Popconfirm, Select, Typography } from 'antd';
|
import { Popconfirm, Select, Typography } from 'antd';
|
||||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { AlertMessageContext } from '../../utils/alert-message-context';
|
import { AlertMessageContext } from '../../utils/alert-message-context';
|
||||||
import {
|
import {
|
||||||
API_VIDEO_CODEC,
|
API_VIDEO_CODEC,
|
||||||
@@ -13,11 +14,13 @@ import {
|
|||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
import { FormStatusIndicator } from './FormStatusIndicator';
|
import { FormStatusIndicator } from './FormStatusIndicator';
|
||||||
|
|
||||||
export type CodecSelectorProps = {};
|
export type CodecSelectorProps = {};
|
||||||
|
|
||||||
export const CodecSelector: FC<CodecSelectorProps> = () => {
|
export const CodecSelector: FC<CodecSelectorProps> = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
const { videoCodec, supportedCodecs } = serverConfig || {};
|
const { videoCodec, supportedCodecs } = serverConfig || {};
|
||||||
@@ -60,7 +63,9 @@ export const CodecSelector: FC<CodecSelectorProps> = () => {
|
|||||||
value: pendingSaveCodec,
|
value: pendingSaveCodec,
|
||||||
path: 'videoSettings',
|
path: 'videoSettings',
|
||||||
});
|
});
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Video codec updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.videoCodecUpdated)),
|
||||||
|
);
|
||||||
|
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
if (serverStatusData.online) {
|
if (serverStatusData.online) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import React, { FC, useContext, useState } from 'react';
|
|||||||
import { Typography, Table, Modal, Button, Alert } from 'antd';
|
import { Typography, Table, Modal, Button, Alert } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
import { AlertMessageContext } from '../../utils/alert-message-context';
|
import { AlertMessageContext } from '../../utils/alert-message-context';
|
||||||
import { UpdateArgs, VideoVariant } from '../../types/config-section';
|
import { UpdateArgs, VideoVariant } from '../../types/config-section';
|
||||||
@@ -24,6 +25,7 @@ import {
|
|||||||
STATUS_PROCESSING,
|
STATUS_PROCESSING,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
import { FormStatusIndicator } from './FormStatusIndicator';
|
import { FormStatusIndicator } from './FormStatusIndicator';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
@@ -36,6 +38,7 @@ const DeleteOutlined = dynamic(() => import('@ant-design/icons/DeleteOutlined'),
|
|||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export const CurrentVariantsTable: FC = () => {
|
export const CurrentVariantsTable: FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [displayModal, setDisplayModal] = useState(false);
|
const [displayModal, setDisplayModal] = useState(false);
|
||||||
const [modalProcessing, setModalProcessing] = useState(false);
|
const [modalProcessing, setModalProcessing] = useState(false);
|
||||||
const [editId, setEditId] = useState(0);
|
const [editId, setEditId] = useState(0);
|
||||||
@@ -87,7 +90,9 @@ export const CurrentVariantsTable: FC = () => {
|
|||||||
setModalProcessing(false);
|
setModalProcessing(false);
|
||||||
handleModalCancel();
|
handleModalCancel();
|
||||||
|
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Variants updated'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.variantsUpdated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
|
||||||
if (serverStatusData.online) {
|
if (serverStatusData.online) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Button, Upload } from 'antd';
|
|||||||
import { RcFile } from 'antd/lib/upload/interface';
|
import { RcFile } from 'antd/lib/upload/interface';
|
||||||
import React, { useState, useContext, FC } from 'react';
|
import React, { useState, useContext, FC } from 'react';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { FormStatusIndicator } from './FormStatusIndicator';
|
import { FormStatusIndicator } from './FormStatusIndicator';
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +18,7 @@ import {
|
|||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
import { NEXT_PUBLIC_API_HOST } from '../../utils/apis';
|
import { NEXT_PUBLIC_API_HOST } from '../../utils/apis';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ACCEPTED_IMAGE_TYPES,
|
ACCEPTED_IMAGE_TYPES,
|
||||||
@@ -37,6 +39,7 @@ const UploadOutlined = dynamic(() => import('@ant-design/icons/UploadOutlined'),
|
|||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export const EditLogo: FC = () => {
|
export const EditLogo: FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [logoUrl, setlogoUrl] = useState(null);
|
const [logoUrl, setlogoUrl] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [logoCachedbuster, setLogoCacheBuster] = useState(0);
|
const [logoCachedbuster, setLogoCacheBuster] = useState(0);
|
||||||
@@ -64,16 +67,28 @@ export const EditLogo: FC = () => {
|
|||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
if (file.size > MAX_IMAGE_FILESIZE) {
|
if (file.size > MAX_IMAGE_FILESIZE) {
|
||||||
const msg = `File size is too big: ${readableBytes(file.size)}`;
|
const msg = t(Localization.Admin.StatusMessages.fileSizeTooBig, {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${msg}`));
|
size: readableBytes(file.size),
|
||||||
|
});
|
||||||
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_ERROR,
|
||||||
|
t(Localization.Admin.StatusMessages.thereWasAnError, { message: msg }),
|
||||||
|
),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
// eslint-disable-next-line no-promise-executor-return
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
return rej();
|
return rej();
|
||||||
}
|
}
|
||||||
if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) {
|
if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) {
|
||||||
const msg = `File type is not supported: ${file.type}`;
|
const msg = t(Localization.Admin.StatusMessages.fileTypeNotSupported, { type: file.type });
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${msg}`));
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_ERROR,
|
||||||
|
t(Localization.Admin.StatusMessages.thereWasAnError, { message: msg }),
|
||||||
|
),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
// eslint-disable-next-line no-promise-executor-return
|
// eslint-disable-next-line no-promise-executor-return
|
||||||
@@ -101,7 +116,12 @@ export const EditLogo: FC = () => {
|
|||||||
setLogoCacheBuster(Math.floor(Math.random() * 100)); // Force logo to re-load
|
setLogoCacheBuster(Math.floor(Math.random() * 100)); // Force logo to re-load
|
||||||
},
|
},
|
||||||
onError: (msg: string) => {
|
onError: (msg: string) => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${msg}`));
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_ERROR,
|
||||||
|
t(Localization.Admin.StatusMessages.thereWasAnError, { message: msg }),
|
||||||
|
),
|
||||||
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Popconfirm, Button, Typography } from 'antd';
|
import { Popconfirm, Button, Typography } from 'antd';
|
||||||
import { FC, useContext, useState } from 'react';
|
import { FC, useContext, useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { AlertMessageContext } from '../../utils/alert-message-context';
|
import { AlertMessageContext } from '../../utils/alert-message-context';
|
||||||
|
|
||||||
import { API_YP_RESET, fetchData } from '../../utils/apis';
|
import { API_YP_RESET, fetchData } from '../../utils/apis';
|
||||||
@@ -10,10 +11,12 @@ import {
|
|||||||
STATUS_PROCESSING,
|
STATUS_PROCESSING,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
import { FormStatusIndicator } from './FormStatusIndicator';
|
import { FormStatusIndicator } from './FormStatusIndicator';
|
||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export const ResetYP: FC = () => {
|
export const ResetYP: FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { setMessage } = useContext(AlertMessageContext);
|
const { setMessage } = useContext(AlertMessageContext);
|
||||||
|
|
||||||
const [submitStatus, setSubmitStatus] = useState(null);
|
const [submitStatus, setSubmitStatus] = useState(null);
|
||||||
@@ -32,7 +35,12 @@ export const ResetYP: FC = () => {
|
|||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${error}`));
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_ERROR,
|
||||||
|
t(Localization.Admin.StatusMessages.thereWasAnError, { message: `${error}` }),
|
||||||
|
),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import React, { useState, useContext, FC } from 'react';
|
import React, { useState, useContext, FC } from 'react';
|
||||||
import { Switch } from 'antd';
|
import { Switch } from 'antd';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import {
|
import {
|
||||||
createInputStatus,
|
createInputStatus,
|
||||||
StatusState,
|
StatusState,
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
STATUS_PROCESSING,
|
STATUS_PROCESSING,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
import { FormStatusIndicator } from './FormStatusIndicator';
|
import { FormStatusIndicator } from './FormStatusIndicator';
|
||||||
|
|
||||||
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
|
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
|
||||||
@@ -42,6 +44,7 @@ export const ToggleSwitch: FC<ToggleSwitchProps> = ({
|
|||||||
useSubmit,
|
useSubmit,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
let resetTimer = null;
|
let resetTimer = null;
|
||||||
@@ -68,7 +71,12 @@ export const ToggleSwitch: FC<ToggleSwitchProps> = ({
|
|||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_ERROR,
|
||||||
|
t(Localization.Admin.StatusMessages.thereWasAnError, { message }),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useContext, useState, useEffect, FC } from 'react';
|
import React, { useContext, useState, useEffect, FC } from 'react';
|
||||||
import { Typography, Slider } from 'antd';
|
import { Typography, Slider } from 'antd';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
import { AlertMessageContext } from '../../utils/alert-message-context';
|
import { AlertMessageContext } from '../../utils/alert-message-context';
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
STATUS_PROCESSING,
|
STATUS_PROCESSING,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
import { FormStatusIndicator } from './FormStatusIndicator';
|
import { FormStatusIndicator } from './FormStatusIndicator';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
@@ -36,6 +38,7 @@ const SLIDER_COMMENTS = {
|
|||||||
|
|
||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export const VideoLatency: FC = () => {
|
export const VideoLatency: FC = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
const [selectedOption, setSelectedOption] = useState(null);
|
const [selectedOption, setSelectedOption] = useState(null);
|
||||||
|
|
||||||
@@ -73,7 +76,12 @@ export const VideoLatency: FC = () => {
|
|||||||
value: postValue,
|
value: postValue,
|
||||||
path: 'videoSettings',
|
path: 'videoSettings',
|
||||||
});
|
});
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Latency buffer level updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_SUCCESS,
|
||||||
|
t(Localization.Admin.StatusMessages.latencyBufferUpdated),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
if (serverStatusData.online) {
|
if (serverStatusData.online) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { FC, useContext, useCallback, useEffect, useState } from 'react';
|
|||||||
import { Button, Col, Collapse, Row, Slider, Space } from 'antd';
|
import { Button, Col, Collapse, Row, Slider, Space } from 'antd';
|
||||||
import Paragraph from 'antd/lib/typography/Paragraph';
|
import Paragraph from 'antd/lib/typography/Paragraph';
|
||||||
import Title from 'antd/lib/typography/Title';
|
import Title from 'antd/lib/typography/Title';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { EditCustomStyles } from '../../EditCustomStyles';
|
import { EditCustomStyles } from '../../EditCustomStyles';
|
||||||
import s from './appearance.module.scss';
|
import s from './appearance.module.scss';
|
||||||
import { postConfigUpdateToAPI, RESET_TIMEOUT } from '../../../../utils/config-constants';
|
import { postConfigUpdateToAPI, RESET_TIMEOUT } from '../../../../utils/config-constants';
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
STATUS_ERROR,
|
STATUS_ERROR,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../../../utils/input-statuses';
|
} from '../../../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../../../types/localization';
|
||||||
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
||||||
import { FormStatusIndicator } from '../../FormStatusIndicator';
|
import { FormStatusIndicator } from '../../FormStatusIndicator';
|
||||||
|
|
||||||
@@ -122,6 +124,7 @@ const ColorCollection: FC<ColorCollectionProps> = ({ variables, updateColor }) =
|
|||||||
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
// eslint-disable-next-line react/function-component-definition
|
||||||
export default function Appearance() {
|
export default function Appearance() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData;
|
const { serverConfig, setFieldInConfigState } = serverStatusData;
|
||||||
const { instanceDetails } = serverConfig;
|
const { instanceDetails } = serverConfig;
|
||||||
@@ -179,7 +182,9 @@ export default function Appearance() {
|
|||||||
apiPath: ENDPOINT,
|
apiPath: ENDPOINT,
|
||||||
data: { value: {} },
|
data: { value: {} },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.updated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
setCustomValues({});
|
setCustomValues({});
|
||||||
},
|
},
|
||||||
@@ -200,7 +205,9 @@ export default function Appearance() {
|
|||||||
apiPath: ENDPOINT,
|
apiPath: ENDPOINT,
|
||||||
data: { value: c },
|
data: { value: c },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.updated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
|
||||||
setFieldInConfigState({
|
setFieldInConfigState({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* eslint-disable react/no-array-index-key */
|
/* eslint-disable react/no-array-index-key */
|
||||||
import React, { useContext, useState, useEffect } from 'react';
|
import React, { useContext, useState, useEffect } from 'react';
|
||||||
import { Typography, Tag } from 'antd';
|
import { Typography, Tag } from 'antd';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
||||||
import {
|
import {
|
||||||
FIELD_PROPS_TAGS,
|
FIELD_PROPS_TAGS,
|
||||||
@@ -17,12 +18,14 @@ import {
|
|||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
STATUS_WARNING,
|
STATUS_WARNING,
|
||||||
} from '../../../../utils/input-statuses';
|
} from '../../../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../../../types/localization';
|
||||||
import { TAG_COLOR } from '../../EditValueArray';
|
import { TAG_COLOR } from '../../EditValueArray';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
// eslint-disable-next-line react/function-component-definition
|
||||||
export default function EditInstanceTags() {
|
export default function EditInstanceTags() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [newTagInput, setNewTagInput] = useState<string>('');
|
const [newTagInput, setNewTagInput] = useState<string>('');
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
@@ -58,7 +61,9 @@ export default function EditInstanceTags() {
|
|||||||
data: { value: postValue },
|
data: { value: postValue },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath });
|
setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath });
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.tagsUpdated)),
|
||||||
|
);
|
||||||
setNewTagInput('');
|
setNewTagInput('');
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
@@ -81,11 +86,15 @@ export default function EditInstanceTags() {
|
|||||||
resetStates();
|
resetStates();
|
||||||
const newTag = newTagInput.trim();
|
const newTag = newTagInput.trim();
|
||||||
if (newTag === '') {
|
if (newTag === '') {
|
||||||
setSubmitStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_WARNING, t(Localization.Admin.StatusMessages.pleaseEnterTag)),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) {
|
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) {
|
||||||
setSubmitStatus(createInputStatus(STATUS_WARNING, 'This tag is already used!'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_WARNING, t(Localization.Admin.StatusMessages.tagAlreadyUsed)),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Collapse } from 'antd';
|
import { Button, Collapse } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useContext, useState, useEffect } from 'react';
|
import React, { useContext, useState, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { UpdateArgs } from '../../../../types/config-section';
|
import { UpdateArgs } from '../../../../types/config-section';
|
||||||
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
import { ServerStatusContext } from '../../../../utils/server-status-context';
|
||||||
import { AlertMessageContext } from '../../../../utils/alert-message-context';
|
import { AlertMessageContext } from '../../../../utils/alert-message-context';
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
STATUS_PROCESSING,
|
STATUS_PROCESSING,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../../../utils/input-statuses';
|
} from '../../../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../../../types/localization';
|
||||||
import { TextField } from '../../TextField';
|
import { TextField } from '../../TextField';
|
||||||
import { FormStatusIndicator } from '../../FormStatusIndicator';
|
import { FormStatusIndicator } from '../../FormStatusIndicator';
|
||||||
import { isValidUrl } from '../../../../utils/validators';
|
import { isValidUrl } from '../../../../utils/validators';
|
||||||
@@ -56,6 +58,7 @@ function checkSaveable(formValues: any, currentValues: any) {
|
|||||||
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
// eslint-disable-next-line react/function-component-definition
|
||||||
export default function EditStorage() {
|
export default function EditStorage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [formDataValues, setFormDataValues] = useState(null);
|
const [formDataValues, setFormDataValues] = useState(null);
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
@@ -125,7 +128,9 @@ export default function EditStorage() {
|
|||||||
data: { value: postValue },
|
data: { value: postValue },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
|
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.updated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
setAlertMessage(
|
setAlertMessage(
|
||||||
'Changing your storage configuration will take place the next time you start a new stream.',
|
'Changing your storage configuration will take place the next time you start a new stream.',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Button, Typography } from 'antd';
|
import { Button, Typography } from 'antd';
|
||||||
import React, { useState, useContext, useEffect } from 'react';
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
import { TextField, TEXTFIELD_TYPE_TEXTAREA } from '../TextField';
|
import { TextField, TEXTFIELD_TYPE_TEXTAREA } from '../TextField';
|
||||||
import {
|
import {
|
||||||
@@ -14,12 +15,14 @@ import {
|
|||||||
STATUS_ERROR,
|
STATUS_ERROR,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../../utils/input-statuses';
|
} from '../../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../../types/localization';
|
||||||
import { UpdateArgs } from '../../../types/config-section';
|
import { UpdateArgs } from '../../../types/config-section';
|
||||||
import { FormStatusIndicator } from '../FormStatusIndicator';
|
import { FormStatusIndicator } from '../FormStatusIndicator';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export const BrowserNotify = () => {
|
export const BrowserNotify = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
const { notifications } = serverConfig || {};
|
const { notifications } = serverConfig || {};
|
||||||
@@ -77,7 +80,9 @@ export const BrowserNotify = () => {
|
|||||||
value: postValue,
|
value: postValue,
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
});
|
});
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.updated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Button, Typography } from 'antd';
|
import { Button, Typography } from 'antd';
|
||||||
import React, { useState, useContext, useEffect } from 'react';
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
import { TextField } from '../TextField';
|
import { TextField } from '../TextField';
|
||||||
import { FormStatusIndicator } from '../FormStatusIndicator';
|
import { FormStatusIndicator } from '../FormStatusIndicator';
|
||||||
@@ -15,11 +16,13 @@ import {
|
|||||||
STATUS_ERROR,
|
STATUS_ERROR,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../../../utils/input-statuses';
|
} from '../../../utils/input-statuses';
|
||||||
|
import { Localization } from '../../../types/localization';
|
||||||
import { UpdateArgs } from '../../../types/config-section';
|
import { UpdateArgs } from '../../../types/config-section';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export const DiscordNotify = () => {
|
export const DiscordNotify = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
const { notifications } = serverConfig || {};
|
const { notifications } = serverConfig || {};
|
||||||
@@ -77,7 +80,9 @@ export const DiscordNotify = () => {
|
|||||||
value: postValue,
|
value: postValue,
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
});
|
});
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.updated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
|
|||||||
+219
-168
@@ -1,170 +1,221 @@
|
|||||||
{
|
{
|
||||||
"Admin": {
|
"Admin": {
|
||||||
"EditInstanceDetails": {
|
"EditInstanceDetails": {
|
||||||
"directoryDescription": "Increase your audience by appearing in the <a href=\"https://owncast.directory\" target=\"_blank\" rel=\"noreferrer\"><strong>Owncast Directory</strong></a>. This is an external service run by the Owncast project. <a href=\"https://owncast.online/docs/directory/?source=admin\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more</a>.",
|
"directoryDescription": "Increase your audience by appearing in the <a href=\"https://owncast.directory\" target=\"_blank\" rel=\"noreferrer\"><strong>Owncast Directory</strong></a>. This is an external service run by the Owncast project. <a href=\"https://owncast.online/docs/directory/?source=admin\" target=\"_blank\" rel=\"noopener noreferrer\">Learn more</a>.",
|
||||||
"offlineMessageDescription": "The offline message is displayed to your page visitors when you're not streaming. Markdown is supported.",
|
"offlineMessageDescription": "The offline message is displayed to your page visitors when you're not streaming. Markdown is supported.",
|
||||||
"serverUrlRequiredForDirectory": "You must set your <strong>Server URL</strong> above to enable the directory."
|
"serverUrlRequiredForDirectory": "You must set your <strong>Server URL</strong> above to enable the directory."
|
||||||
},
|
},
|
||||||
"HardwareInfo": {
|
"HardwareInfo": {
|
||||||
"title": "Hardware Info",
|
"title": "Hardware Info",
|
||||||
"pleaseWait": "Please wait",
|
"pleaseWait": "Please wait",
|
||||||
"noDetails": "No hardware details have been collected yet.",
|
"noDetails": "No hardware details have been collected yet.",
|
||||||
"cpu": "CPU",
|
"cpu": "CPU",
|
||||||
"memory": "Memory",
|
"memory": "Memory",
|
||||||
"disk": "Disk",
|
"disk": "Disk",
|
||||||
"used": "used"
|
"used": "used"
|
||||||
},
|
},
|
||||||
"Help": {
|
"Help": {
|
||||||
"title": "How can we help you?",
|
"title": "How can we help you?",
|
||||||
"configureInstance": "I want to configure my owncast instance",
|
"configureInstance": "I want to configure my owncast instance",
|
||||||
"learnMore": "Learn more",
|
"learnMore": "Learn more",
|
||||||
"configureBroadcasting": "Help configuring my broadcasting software",
|
"configureBroadcasting": "Help configuring my broadcasting software",
|
||||||
"embedStream": "I want to embed my stream into another site",
|
"embedStream": "I want to embed my stream into another site",
|
||||||
"customizeWebsite": "I want to customize my website",
|
"customizeWebsite": "I want to customize my website",
|
||||||
"tweakVideo": "I want to tweak my video output",
|
"tweakVideo": "I want to tweak my video output",
|
||||||
"useStorage": "I want to use an external storage provider",
|
"useStorage": "I want to use an external storage provider",
|
||||||
"foundBug": "I found a bug",
|
"foundBug": "I found a bug",
|
||||||
"bugPlease": "If you found a bug, then please",
|
"bugPlease": "If you found a bug, then please",
|
||||||
"letUsKnow": "let us know",
|
"letUsKnow": "let us know",
|
||||||
"generalQuestion": "I have a general question",
|
"generalQuestion": "I have a general question",
|
||||||
"generalAnswered": "Most general questions are answered in our",
|
"generalAnswered": "Most general questions are answered in our",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"orExist": "or exist in our",
|
"orExist": "or exist in our",
|
||||||
"discussions": "discussions",
|
"discussions": "discussions",
|
||||||
"buildAddons": "I want to build add-ons for Owncast",
|
"buildAddons": "I want to build add-ons for Owncast",
|
||||||
"buildTools": "You can build your own bots, overlays, tools and add-ons with our",
|
"buildTools": "You can build your own bots, overlays, tools and add-ons with our",
|
||||||
"developerApis": "developer APIs.",
|
"developerApis": "developer APIs.",
|
||||||
"troubleshooting": "Troubleshooting",
|
"troubleshooting": "Troubleshooting",
|
||||||
"fixProblems": "Fix your problems",
|
"fixProblems": "Fix your problems",
|
||||||
"documentation": "Documentation",
|
"documentation": "Documentation",
|
||||||
"readDocs": "Read the Docs",
|
"readDocs": "Read the Docs",
|
||||||
"commonTasks": "Common tasks",
|
"commonTasks": "Common tasks",
|
||||||
"other": "Other"
|
"other": "Other"
|
||||||
},
|
},
|
||||||
"LogTable": {
|
"LogTable": {
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"info": "Info",
|
"info": "Info",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"timestamp": "Timestamp",
|
"timestamp": "Timestamp",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"logs": "Logs"
|
"logs": "Logs"
|
||||||
},
|
},
|
||||||
"NewsFeed": {
|
"NewsFeed": {
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
"noNews": "No news.",
|
"noNews": "No news.",
|
||||||
"title": "News & Updates from Owncast"
|
"title": "News & Updates from Owncast"
|
||||||
},
|
},
|
||||||
"ViewerInfo": {
|
"ViewerInfo": {
|
||||||
"title": "Viewer Info",
|
"title": "Viewer Info",
|
||||||
"currentStream": "Current stream",
|
"currentStream": "Current stream",
|
||||||
"last12Hours": "Last 12 hours",
|
"last12Hours": "Last 12 hours",
|
||||||
"last24Hours": "Last 24 hours",
|
"last24Hours": "Last 24 hours",
|
||||||
"last7Days": "Last 7 days",
|
"last7Days": "Last 7 days",
|
||||||
"last30Days": "Last 30 days",
|
"last30Days": "Last 30 days",
|
||||||
"last3Months": "Last 3 months",
|
"last3Months": "Last 3 months",
|
||||||
"last6Months": "Last 6 months",
|
"last6Months": "Last 6 months",
|
||||||
"currentViewers": "Current viewers",
|
"currentViewers": "Current viewers",
|
||||||
"maxViewersThisStream": "Max viewers this stream",
|
"maxViewersThisStream": "Max viewers this stream",
|
||||||
"maxViewersLastStream": "Max viewers last stream",
|
"maxViewersLastStream": "Max viewers last stream",
|
||||||
"maxViewers": "max viewers",
|
"maxViewers": "max viewers",
|
||||||
"pleaseWait": "Please wait",
|
"pleaseWait": "Please wait",
|
||||||
"noData": "No viewer data has been collected yet.",
|
"noData": "No viewer data has been collected yet.",
|
||||||
"viewers": "Viewers"
|
"viewers": "Viewers"
|
||||||
},
|
},
|
||||||
"VideoVariantForm": {
|
"VideoVariantForm": {
|
||||||
"bitrateDisabledPassthrough": "Bitrate selection is disabled when Video Passthrough is enabled.",
|
"bitrateDisabledPassthrough": "Bitrate selection is disabled when Video Passthrough is enabled.",
|
||||||
"bitrateGoodForHigh": "Good for high speed & high bitrate viewers.",
|
"bitrateGoodForHigh": "Good for high speed & high bitrate viewers.",
|
||||||
"bitrateGoodForMost": "Good for most viewers, bandwidths and resolutions.",
|
"bitrateGoodForMost": "Good for most viewers, bandwidths and resolutions.",
|
||||||
"bitrateGoodForSlow": "Good for slow, mobile and low bitrate viewers.",
|
"bitrateGoodForSlow": "Good for slow, mobile and low bitrate viewers.",
|
||||||
"bitrateValueKbps": "{{bitrate}} kbps"
|
"bitrateValueKbps": "{{bitrate}} kbps"
|
||||||
},
|
},
|
||||||
"deleteEmoji": "Delete emoji",
|
"deleteEmoji": "Delete emoji",
|
||||||
"emojiPageDescription": "Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the filename without extension will be used as emoji name. Additionally, emoji names are case-insensitive. For best results, ensure all emoji have unique names.",
|
"emojiPageDescription": "Here you can upload new custom emojis for usage in the chat. When uploading a new emoji, the filename without extension will be used as emoji name. Additionally, emoji names are case-insensitive. For best results, ensure all emoji have unique names.",
|
||||||
"emojiUploadBulkGuide": "Want to upload custom emojis in bulk? Check out our <a href=\"https://owncast.online/docs/chat/emoji\" rel=\"noopener noreferrer\" target=\"_blank\">Emoji guide</a>.",
|
"emojiUploadBulkGuide": "Want to upload custom emojis in bulk? Check out our <a href=\"https://owncast.online/docs/chat/emoji\" rel=\"noopener noreferrer\" target=\"_blank\">Emoji guide</a>.",
|
||||||
"emojis": "Emojis",
|
"emojis": "Emojis",
|
||||||
"uploadNewEmoji": "Upload new emoji"
|
"uploadNewEmoji": "Upload new emoji",
|
||||||
},
|
"StatusMessages": {
|
||||||
"Common": {
|
"updated": "Updated.",
|
||||||
"poweredByOwncastVersion": "Powered by <a href='https://owncast.online'>Owncast v{{versionNumber}}</a>"
|
"tagsUpdated": "Tags updated.",
|
||||||
},
|
"variantsUpdated": "Variants updated",
|
||||||
"Frontend": {
|
"videoCodecUpdated": "Video codec updated.",
|
||||||
"BrowserNotifyModal": {
|
"latencyBufferUpdated": "Latency buffer level updated.",
|
||||||
"allowButton": "Allow",
|
"deletingEmoji": "Deleting emoji...",
|
||||||
"blockButton": "Block",
|
"emojiDeleted": "Emoji deleted",
|
||||||
"deniedDescription": "To enable push notifications from {{hostname}} access your browser permissions for this site and turn on notifications. Then reload this page to apply your updated settings on this site. <a href='https://owncast.online/docs/notifications'>Learn more.</a>",
|
"convertingEmoji": "Converting emoji...",
|
||||||
"deniedTitle": "Notifications are blocked on your device",
|
"uploadingEmoji": "Uploading emoji...",
|
||||||
"enabledDescription": "To disable push notifications from {{hostname}} access your browser permissions for this site and turn off notifications. <a href='https://owncast.online/docs/notifications'>Learn more.</a>",
|
"emojiUploadedSuccessfully": "Emoji uploaded successfully!",
|
||||||
"enabledTitle": "Notifications are enabled",
|
"thereWasAnError": "There was an error: {{message}}",
|
||||||
"errorMessage": "Error registering for live notifications: {{message}}. Make sure you're not inside a private browser environment or have previously disabled notifications for this stream.",
|
"fileSizeTooBig": "File size is too big: {{size}}",
|
||||||
"errorTitle": "Browser Notification Error",
|
"fileTypeNotSupported": "File type is not supported: {{type}}",
|
||||||
"iosAddButton": "Add",
|
"pleaseEnterTag": "Please enter a tag",
|
||||||
"iosAddToHomeScreen": "Add to Home Screen",
|
"tagAlreadyUsed": "This tag is already used!"
|
||||||
"iosAllowPrompt": "Allow",
|
},
|
||||||
"iosComeBack": "Come back to this screen and enable notifications.",
|
"Actions": {
|
||||||
"iosDescription": "It takes a couple extra steps to make sure you get notified when your favorite streams go live.",
|
"title": "External Actions",
|
||||||
"iosNameAndTap": "Give this link a name and tap the new icon on your home screen",
|
"description": "External action URLs are 3rd party UI you can display, embedded, into your Owncast page when a user clicks on a button to launch your action.",
|
||||||
"iosShareButton": "share",
|
"readMoreLink": "Read more about how to use actions, with examples, at",
|
||||||
"iosTitle": "Get notified on iOS",
|
"createNewAction": "Create New Action",
|
||||||
"learnMore": "Learn more",
|
"createNewActionTitle": "Create New Action",
|
||||||
"mainDescription": "Get notified right in the browser each time this stream goes live.",
|
"editActionTitle": "Edit Action",
|
||||||
"permissionWantsTo": "{{hostname}} wants to",
|
"modalDescription": "Add the URL for the external action you want to present.",
|
||||||
"showNotifications": "Show notifications",
|
"onlyHttpsSupported": "Only HTTPS URLs and embeds are supported.",
|
||||||
"unsupported": "Browser notifications are not supported in your browser.",
|
"readMoreAboutActions": "Read more about external actions.",
|
||||||
"unsupportedLocal": "Browser notifications are not supported for local servers."
|
"selectActionType": "Select an action type",
|
||||||
},
|
"linkOrEmbedUrl": "Link or embed an URL",
|
||||||
"Footer": {
|
"customHtml": "Custom HTML",
|
||||||
"documentation": "Documentation",
|
"htmlEmbedPlaceholder": "HTML embed code (required)",
|
||||||
"contribute": "Contribute",
|
"urlPlaceholder": "https://myserver.com/action (required)",
|
||||||
"source": "Source"
|
"titlePlaceholder": "Your action title (required)",
|
||||||
},
|
"descriptionPlaceholder": "Optional description",
|
||||||
"Chat": {
|
"iconPlaceholder": "https://myserver.com/action/icon.png (optional)",
|
||||||
"userJoined": "joined the chat.",
|
"optionalBackgroundColor": "Optional background color of the action button.",
|
||||||
"userLeft": "left the chat.",
|
"openExternally": "Open in a new tab instead of within your page."
|
||||||
"nameChangeText": "is now known as",
|
},
|
||||||
"moderatorNotification": "You are now a moderator."
|
"Webhooks": {
|
||||||
},
|
"createNewWebhook": "Create New Webhook",
|
||||||
"FollowModal": {
|
"webhookUrlPlaceholder": "https://myserver.com/webhook",
|
||||||
"description": "By following this stream you'll get notified on the Fediverse when it goes live. Now is a great time to",
|
"selectEvents": "Select the events that will be sent to this webhook.",
|
||||||
"learnFediverse": "learn about the Fediverse",
|
"selectAll": "Select all"
|
||||||
"newToYou": "if it's new to you.",
|
},
|
||||||
"instructions": "Enter your username @server to follow",
|
"AccessTokens": {
|
||||||
"placeholder": "Your fediverse account @account@server",
|
"createNewAccessToken": "Create New Access token",
|
||||||
"redirectMessage": "You'll be redirected to your Fediverse server and asked to confirm the action.",
|
"nameDescription": "The name will be displayed as the chat user when sending messages with this access token.",
|
||||||
"joinFediverse": "Join the Fediverse",
|
"namePlaceholder": "Name of bot, service, or integration",
|
||||||
"followButton": "Follow",
|
"selectPermissions": "Select the permissions this access token will have. It cannot be edited after it's created.",
|
||||||
"followError": "Follow Error",
|
"selectAll": "Select all"
|
||||||
"unableToFollow": "Unable to follow."
|
}
|
||||||
},
|
},
|
||||||
"Header": {
|
"Common": {
|
||||||
"skipToPlayer": "Skip to player",
|
"poweredByOwncastVersion": "Powered by <a href='https://owncast.online'>Owncast v{{versionNumber}}</a>"
|
||||||
"skipToOfflineMessage": "Skip to offline message",
|
},
|
||||||
"skipToContent": "Skip to page content",
|
"Frontend": {
|
||||||
"skipToFooter": "Skip to footer",
|
"BrowserNotifyModal": {
|
||||||
"chatWillBeAvailable": "Chat will be available when the stream is live.",
|
"allowButton": "Allow",
|
||||||
"chatOffline": "Chat is offline"
|
"blockButton": "Block",
|
||||||
},
|
"deniedDescription": "To enable push notifications from {{hostname}} access your browser permissions for this site and turn on notifications. Then reload this page to apply your updated settings on this site. <a href='https://owncast.online/docs/notifications'>Learn more.</a>",
|
||||||
"NameChangeModal": {
|
"deniedTitle": "Notifications are blocked on your device",
|
||||||
"authInfo": "You can also authenticate an IndieAuth or Fediverse account via the \"Authenticate\" menu.",
|
"enabledDescription": "To disable push notifications from {{hostname}} access your browser permissions for this site and turn off notifications. <a href='https://owncast.online/docs/notifications'>Learn more.</a>",
|
||||||
"buttonText": "Change name",
|
"enabledTitle": "Notifications are enabled",
|
||||||
"colorLabel": "Your Color",
|
"errorMessage": "Error registering for live notifications: {{message}}. Make sure you're not inside a private browser environment or have previously disabled notifications for this stream.",
|
||||||
"description": "Your chat display name is what people see when you send chat messages.",
|
"errorTitle": "Browser Notification Error",
|
||||||
"overLimit": "Over limit",
|
"iosAddButton": "Add",
|
||||||
"placeholder": "Your chat display name"
|
"iosAddToHomeScreen": "Add to Home Screen",
|
||||||
},
|
"iosAllowPrompt": "Allow",
|
||||||
"chatOffline": "Chat is offline",
|
"iosComeBack": "Come back to this screen and enable notifications.",
|
||||||
"componentError": "Error: {{message}}",
|
"iosDescription": "It takes a couple extra steps to make sure you get notified when your favorite streams go live.",
|
||||||
"helloWorld": "Hello world",
|
"iosNameAndTap": "Give this link a name and tap the new icon on your home screen",
|
||||||
"offlineBasic": "This stream is offline. Check back soon!",
|
"iosShareButton": "share",
|
||||||
"offlineFediverseOnly": "This stream is offline. <span class='follow-link'>Follow</span> {{fediverseAccount}} on the Fediverse to see the next time {{streamer}} goes live.",
|
"iosTitle": "Get notified on iOS",
|
||||||
"offlineNotifyAndFediverse": "This stream is offline. You can <span class='notify-link'>be notified</span> the next time {{streamer}} goes live or <span class='follow-link'>follow</span> {{fediverseAccount}} on the Fediverse.",
|
"learnMore": "Learn more",
|
||||||
"offlineNotifyOnly": "This stream is offline. <span class='notify-link'>Be notified</span> the next time {{streamer}} goes live.",
|
"mainDescription": "Get notified right in the browser each time this stream goes live.",
|
||||||
"lastLiveAgo": "Last live {{timeAgo}} ago"
|
"permissionWantsTo": "{{hostname}} wants to",
|
||||||
},
|
"showNotifications": "Show notifications",
|
||||||
"Testing": {
|
"unsupported": "Browser notifications are not supported in your browser.",
|
||||||
"itemCount": "You have {{count}} items",
|
"unsupportedLocal": "Browser notifications are not supported for local servers."
|
||||||
"messageCount": "You have {{count}} messages from {{sender}}",
|
},
|
||||||
"noPluralKey": "This key has no plural variants - {{count}} things",
|
"Footer": {
|
||||||
"simpleKey": "Simple translation text"
|
"documentation": "Documentation",
|
||||||
}
|
"contribute": "Contribute",
|
||||||
|
"source": "Source"
|
||||||
|
},
|
||||||
|
"Chat": {
|
||||||
|
"userJoined": "joined the chat.",
|
||||||
|
"userLeft": "left the chat.",
|
||||||
|
"nameChangeText": "is now known as",
|
||||||
|
"moderatorNotification": "You are now a moderator."
|
||||||
|
},
|
||||||
|
"FollowModal": {
|
||||||
|
"description": "By following this stream you'll get notified on the Fediverse when it goes live. Now is a great time to",
|
||||||
|
"learnFediverse": "learn about the Fediverse",
|
||||||
|
"newToYou": "if it's new to you.",
|
||||||
|
"instructions": "Enter your username @server to follow",
|
||||||
|
"placeholder": "Your fediverse account @account@server",
|
||||||
|
"redirectMessage": "You'll be redirected to your Fediverse server and asked to confirm the action.",
|
||||||
|
"joinFediverse": "Join the Fediverse",
|
||||||
|
"followButton": "Follow",
|
||||||
|
"followError": "Follow Error",
|
||||||
|
"unableToFollow": "Unable to follow."
|
||||||
|
},
|
||||||
|
"Header": {
|
||||||
|
"skipToPlayer": "Skip to player",
|
||||||
|
"skipToOfflineMessage": "Skip to offline message",
|
||||||
|
"skipToContent": "Skip to page content",
|
||||||
|
"skipToFooter": "Skip to footer",
|
||||||
|
"chatWillBeAvailable": "Chat will be available when the stream is live.",
|
||||||
|
"chatOffline": "Chat is offline"
|
||||||
|
},
|
||||||
|
"NameChangeModal": {
|
||||||
|
"authInfo": "You can also authenticate an IndieAuth or Fediverse account via the \"Authenticate\" menu.",
|
||||||
|
"buttonText": "Change name",
|
||||||
|
"colorLabel": "Your Color",
|
||||||
|
"description": "Your chat display name is what people see when you send chat messages.",
|
||||||
|
"overLimit": "Over limit",
|
||||||
|
"placeholder": "Your chat display name"
|
||||||
|
},
|
||||||
|
"chatOffline": "Chat is offline",
|
||||||
|
"componentError": "Error: {{message}}",
|
||||||
|
"helloWorld": "Hello world",
|
||||||
|
"offlineBasic": "This stream is offline. Check back soon!",
|
||||||
|
"offlineFediverseOnly": "This stream is offline. <span class='follow-link'>Follow</span> {{fediverseAccount}} on the Fediverse to see the next time {{streamer}} goes live.",
|
||||||
|
"offlineNotifyAndFediverse": "This stream is offline. You can <span class='notify-link'>be notified</span> the next time {{streamer}} goes live or <span class='follow-link'>follow</span> {{fediverseAccount}} on the Fediverse.",
|
||||||
|
"offlineNotifyOnly": "This stream is offline. <span class='notify-link'>Be notified</span> the next time {{streamer}} goes live.",
|
||||||
|
"lastLiveAgo": "Last live {{timeAgo}} ago"
|
||||||
|
},
|
||||||
|
"Testing": {
|
||||||
|
"itemCount": "You have {{count}} items",
|
||||||
|
"messageCount": "You have {{count}} messages from {{sender}}",
|
||||||
|
"noPluralKey": "This key has no plural variants - {{count}} things",
|
||||||
|
"simpleKey": "Simple translation text"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+249
-76
File diff suppressed because it is too large
Load Diff
@@ -16,12 +16,15 @@ import {
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import {
|
import {
|
||||||
fetchData,
|
fetchData,
|
||||||
ACCESS_TOKENS,
|
ACCESS_TOKENS,
|
||||||
DELETE_ACCESS_TOKEN,
|
DELETE_ACCESS_TOKEN,
|
||||||
CREATE_ACCESS_TOKEN,
|
CREATE_ACCESS_TOKEN,
|
||||||
} from '../../utils/apis';
|
} from '../../utils/apis';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
|
import { Translation } from '../../components/ui/Translation/Translation';
|
||||||
|
|
||||||
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
||||||
|
|
||||||
@@ -72,6 +75,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
const NewTokenModal = (props: Props) => {
|
const NewTokenModal = (props: Props) => {
|
||||||
const { onOk, onCancel, open } = props;
|
const { onOk, onCancel, open } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
const [selectedScopes, setSelectedScopes] = useState([]);
|
const [selectedScopes, setSelectedScopes] = useState([]);
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
@@ -107,7 +111,7 @@ const NewTokenModal = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Create New Access token"
|
title={t(Localization.Admin.AccessTokens.createNewAccessToken)}
|
||||||
open={open}
|
open={open}
|
||||||
onOk={saveToken}
|
onOk={saveToken}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
@@ -115,18 +119,17 @@ const NewTokenModal = (props: Props) => {
|
|||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<p>
|
<p>
|
||||||
The name will be displayed as the chat user when sending messages with this access token.
|
<Translation translationKey={Localization.Admin.AccessTokens.nameDescription} />
|
||||||
</p>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="Name of bot, service, or integration"
|
placeholder={t(Localization.Admin.AccessTokens.namePlaceholder)}
|
||||||
onChange={input => setName(input.currentTarget.value)}
|
onChange={input => setName(input.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Select the permissions this access token will have. It cannot be edited after it's
|
<Translation translationKey={Localization.Admin.AccessTokens.selectPermissions} />
|
||||||
created.
|
|
||||||
</p>
|
</p>
|
||||||
<Checkbox.Group style={{ width: '100%' }} value={selectedScopes} onChange={onChange}>
|
<Checkbox.Group style={{ width: '100%' }} value={selectedScopes} onChange={onChange}>
|
||||||
<Row>{checkboxes}</Row>
|
<Row>{checkboxes}</Row>
|
||||||
@@ -134,7 +137,7 @@ const NewTokenModal = (props: Props) => {
|
|||||||
|
|
||||||
<p>
|
<p>
|
||||||
<Button type="primary" onClick={selectAll}>
|
<Button type="primary" onClick={selectAll}>
|
||||||
Select all
|
<Translation translationKey={Localization.Admin.AccessTokens.selectAll} />
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
+34
-20
@@ -4,6 +4,7 @@ import { bbedit } from '@uiw/codemirror-theme-bbedit';
|
|||||||
import { html as codeMirrorHTML } from '@codemirror/lang-html';
|
import { html as codeMirrorHTML } from '@codemirror/lang-html';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import React, { ReactElement, useContext, useEffect, useState } from 'react';
|
import React, { ReactElement, useContext, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { FormStatusIndicator } from '../../components/admin/FormStatusIndicator';
|
import { FormStatusIndicator } from '../../components/admin/FormStatusIndicator';
|
||||||
import { ExternalAction } from '../../interfaces/external-action';
|
import { ExternalAction } from '../../interfaces/external-action';
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +15,8 @@ import {
|
|||||||
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
|
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators';
|
import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
|
import { Translation } from '../../components/ui/Translation/Translation';
|
||||||
|
|
||||||
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ type ActionType = 'url' | 'html';
|
|||||||
|
|
||||||
const ActionModal = (props: Props) => {
|
const ActionModal = (props: Props) => {
|
||||||
const { onOk, onCancel, open, action } = props;
|
const { onOk, onCancel, open, action } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [actionType, setActionType] = useState<ActionType>('url');
|
const [actionType, setActionType] = useState<ActionType>('url');
|
||||||
|
|
||||||
@@ -120,32 +124,38 @@ const ActionModal = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
title={action == null ? 'Create New Action' : 'Edit Action'}
|
title={
|
||||||
|
action == null
|
||||||
|
? t(Localization.Admin.Actions.createNewActionTitle)
|
||||||
|
: t(Localization.Admin.Actions.editActionTitle)
|
||||||
|
}
|
||||||
open={open}
|
open={open}
|
||||||
onOk={save}
|
onOk={save}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
okButtonProps={okButtonProps}
|
okButtonProps={okButtonProps}
|
||||||
>
|
>
|
||||||
<Form initialValues={action}>
|
<Form initialValues={action}>
|
||||||
Add the URL for the external action you want to present.{' '}
|
<Translation translationKey={Localization.Admin.Actions.modalDescription} />{' '}
|
||||||
<strong>Only HTTPS URLs and embeds are supported.</strong>
|
<strong>
|
||||||
|
<Translation translationKey={Localization.Admin.Actions.onlyHttpsSupported} />
|
||||||
|
</strong>
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
href="https://owncast.online/thirdparty/actions/"
|
href="https://owncast.online/thirdparty/actions/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
Read more about external actions.
|
<Translation translationKey={Localization.Admin.Actions.readMoreAboutActions} />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Select
|
<Select
|
||||||
value={actionType}
|
value={actionType}
|
||||||
onChange={setActionType}
|
onChange={setActionType}
|
||||||
placeholder="Select an action type"
|
placeholder={t(Localization.Admin.Actions.selectActionType)}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Link or embed an URL', value: 'url' },
|
{ label: t(Localization.Admin.Actions.linkOrEmbedUrl), value: 'url' },
|
||||||
{ label: 'Custom HTML', value: 'html' },
|
{ label: t(Localization.Admin.Actions.customHtml), value: 'html' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -153,7 +163,7 @@ const ActionModal = (props: Props) => {
|
|||||||
<Form.Item name="html">
|
<Form.Item name="html">
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value={actionHTML}
|
value={actionHTML}
|
||||||
placeholder="HTML embed code (required)"
|
placeholder={t(Localization.Admin.Actions.htmlEmbedPlaceholder)}
|
||||||
theme={bbedit}
|
theme={bbedit}
|
||||||
height="200px"
|
height="200px"
|
||||||
extensions={[codeMirrorHTML()]}
|
extensions={[codeMirrorHTML()]}
|
||||||
@@ -164,7 +174,7 @@ const ActionModal = (props: Props) => {
|
|||||||
<Form.Item name="url">
|
<Form.Item name="url">
|
||||||
<Input
|
<Input
|
||||||
required
|
required
|
||||||
placeholder="https://myserver.com/action (required)"
|
placeholder={t(Localization.Admin.Actions.urlPlaceholder)}
|
||||||
onChange={input => setActionUrl(input.currentTarget.value.trim())}
|
onChange={input => setActionUrl(input.currentTarget.value.trim())}
|
||||||
type="url"
|
type="url"
|
||||||
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
||||||
@@ -175,21 +185,21 @@ const ActionModal = (props: Props) => {
|
|||||||
<Input
|
<Input
|
||||||
value={actionTitle}
|
value={actionTitle}
|
||||||
required
|
required
|
||||||
placeholder="Your action title (required)"
|
placeholder={t(Localization.Admin.Actions.titlePlaceholder)}
|
||||||
onChange={input => setActionTitle(input.currentTarget.value)}
|
onChange={input => setActionTitle(input.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="description">
|
<Form.Item name="description">
|
||||||
<Input
|
<Input
|
||||||
value={actionDescription}
|
value={actionDescription}
|
||||||
placeholder="Optional description"
|
placeholder={t(Localization.Admin.Actions.descriptionPlaceholder)}
|
||||||
onChange={input => setActionDescription(input.currentTarget.value)}
|
onChange={input => setActionDescription(input.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="icon">
|
<Form.Item name="icon">
|
||||||
<Input
|
<Input
|
||||||
value={actionIcon}
|
value={actionIcon}
|
||||||
placeholder="https://myserver.com/action/icon.png (optional)"
|
placeholder={t(Localization.Admin.Actions.iconPlaceholder)}
|
||||||
onChange={input => setActionIcon(input.currentTarget.value)}
|
onChange={input => setActionIcon(input.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -201,7 +211,7 @@ const ActionModal = (props: Props) => {
|
|||||||
onChange={input => setActionColor(input.currentTarget.value)}
|
onChange={input => setActionColor(input.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
Optional background color of the action button.
|
<Translation translationKey={Localization.Admin.Actions.optionalBackgroundColor} />
|
||||||
</div>
|
</div>
|
||||||
{actionType === 'html' ? null : (
|
{actionType === 'html' ? null : (
|
||||||
<Form.Item name="openExternally">
|
<Form.Item name="openExternally">
|
||||||
@@ -210,7 +220,7 @@ const ActionModal = (props: Props) => {
|
|||||||
defaultChecked={openExternally}
|
defaultChecked={openExternally}
|
||||||
onChange={onOpenExternallyChanged}
|
onChange={onOpenExternallyChanged}
|
||||||
>
|
>
|
||||||
Open in a new tab instead of within your page.
|
<Translation translationKey={Localization.Admin.Actions.openExternally} />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
@@ -220,6 +230,7 @@ const ActionModal = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Actions = () => {
|
const Actions = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
const { externalActions } = serverConfig;
|
const { externalActions } = serverConfig;
|
||||||
@@ -245,7 +256,9 @@ const Actions = () => {
|
|||||||
data: { value: actionsData },
|
data: { value: actionsData },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setFieldInConfigState({ fieldName: 'externalActions', value: actionsData, path: '' });
|
setFieldInConfigState({ fieldName: 'externalActions', value: actionsData, path: '' });
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.updated)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
@@ -401,13 +414,14 @@ const Actions = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Title>External Actions</Title>
|
<Title>
|
||||||
|
<Translation translationKey={Localization.Admin.Actions.title} />
|
||||||
|
</Title>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
External action URLs are 3rd party UI you can display, embedded, into your Owncast page when
|
<Translation translationKey={Localization.Admin.Actions.description} />
|
||||||
a user clicks on a button to launch your action.
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Read more about how to use actions, with examples, at{' '}
|
<Translation translationKey={Localization.Admin.Actions.readMoreLink} />{' '}
|
||||||
<a
|
<a
|
||||||
href="https://owncast.online/thirdparty/?source=admin"
|
href="https://owncast.online/thirdparty/?source=admin"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -426,7 +440,7 @@ const Actions = () => {
|
|||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Button type="primary" onClick={showCreateModal}>
|
<Button type="primary" onClick={showCreateModal}>
|
||||||
Create New Action
|
<Translation translationKey={Localization.Admin.Actions.createNewAction} />
|
||||||
</Button>
|
</Button>
|
||||||
<FormStatusIndicator status={submitStatus} />
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ const Emoji = () => {
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING, 'Deleting emoji...'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_PROCESSING, t(Localization.Admin.StatusMessages.deletingEmoji)),
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchData(DELETE_EMOJI, {
|
const response = await fetchData(DELETE_EMOJI, {
|
||||||
@@ -79,7 +81,9 @@ const Emoji = () => {
|
|||||||
throw response;
|
throw response;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Emoji deleted'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_SUCCESS, t(Localization.Admin.StatusMessages.emojiDeleted)),
|
||||||
|
);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `${error}`));
|
setSubmitStatus(createInputStatus(STATUS_ERROR, `${error}`));
|
||||||
@@ -93,7 +97,9 @@ const Emoji = () => {
|
|||||||
async function handleEmojiUpload() {
|
async function handleEmojiUpload() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING, 'Converting emoji...'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_PROCESSING, t(Localization.Admin.StatusMessages.convertingEmoji)),
|
||||||
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
const emojiData = await new Promise<CustomEmoji>((res, rej) => {
|
const emojiData = await new Promise<CustomEmoji>((res, rej) => {
|
||||||
@@ -111,7 +117,9 @@ const Emoji = () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING, 'Uploading emoji...'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(STATUS_PROCESSING, t(Localization.Admin.StatusMessages.uploadingEmoji)),
|
||||||
|
);
|
||||||
|
|
||||||
const response = await fetchData(UPLOAD_EMOJI, {
|
const response = await fetchData(UPLOAD_EMOJI, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -125,7 +133,12 @@ const Emoji = () => {
|
|||||||
throw response;
|
throw response;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Emoji uploaded successfully!'));
|
setSubmitStatus(
|
||||||
|
createInputStatus(
|
||||||
|
STATUS_SUCCESS,
|
||||||
|
t(Localization.Admin.StatusMessages.emojiUploadedSuccessfully),
|
||||||
|
),
|
||||||
|
);
|
||||||
getEmojis();
|
getEmojis();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `${error}`));
|
setSubmitStatus(createInputStatus(STATUS_ERROR, `${error}`));
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import React, { ReactElement, useEffect, useState } from 'react';
|
import React, { ReactElement, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-export-i18n';
|
||||||
import { CREATE_WEBHOOK, DELETE_WEBHOOK, fetchData, WEBHOOKS } from '../../utils/apis';
|
import { CREATE_WEBHOOK, DELETE_WEBHOOK, fetchData, WEBHOOKS } from '../../utils/apis';
|
||||||
import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators';
|
import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators';
|
||||||
|
import { Localization } from '../../types/localization';
|
||||||
|
import { Translation } from '../../components/ui/Translation/Translation';
|
||||||
|
|
||||||
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
||||||
|
|
||||||
@@ -71,6 +74,7 @@ interface Props {
|
|||||||
|
|
||||||
const NewWebhookModal = (props: Props) => {
|
const NewWebhookModal = (props: Props) => {
|
||||||
const { onOk, onCancel, open } = props;
|
const { onOk, onCancel, open } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [selectedEvents, setSelectedEvents] = useState([]);
|
const [selectedEvents, setSelectedEvents] = useState([]);
|
||||||
const [webhookUrl, setWebhookUrl] = useState('');
|
const [webhookUrl, setWebhookUrl] = useState('');
|
||||||
@@ -108,7 +112,7 @@ const NewWebhookModal = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="Create New Webhook"
|
title={t(Localization.Admin.Webhooks.createNewWebhook)}
|
||||||
open={open}
|
open={open}
|
||||||
onOk={save}
|
onOk={save}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
@@ -117,20 +121,22 @@ const NewWebhookModal = (props: Props) => {
|
|||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
value={webhookUrl}
|
value={webhookUrl}
|
||||||
placeholder="https://myserver.com/webhook"
|
placeholder={t(Localization.Admin.Webhooks.webhookUrlPlaceholder)}
|
||||||
onChange={input => setWebhookUrl(input.currentTarget.value.trim())}
|
onChange={input => setWebhookUrl(input.currentTarget.value.trim())}
|
||||||
type="url"
|
type="url"
|
||||||
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>Select the events that will be sent to this webhook.</p>
|
<p>
|
||||||
|
<Translation translationKey={Localization.Admin.Webhooks.selectEvents} />
|
||||||
|
</p>
|
||||||
<Checkbox.Group style={{ width: '100%' }} value={selectedEvents} onChange={onChange}>
|
<Checkbox.Group style={{ width: '100%' }} value={selectedEvents} onChange={onChange}>
|
||||||
<Row>{checkboxes}</Row>
|
<Row>{checkboxes}</Row>
|
||||||
</Checkbox.Group>
|
</Checkbox.Group>
|
||||||
<p>
|
<p>
|
||||||
<Button type="primary" onClick={selectAll}>
|
<Button type="primary" onClick={selectAll}>
|
||||||
Select all
|
<Translation translationKey={Localization.Admin.Webhooks.selectAll} />
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -250,6 +250,66 @@ export const Localization = {
|
|||||||
timestamp: 'Admin.timestamp',
|
timestamp: 'Admin.timestamp',
|
||||||
message: 'Admin.message',
|
message: 'Admin.message',
|
||||||
logs: 'Admin.logs',
|
logs: 'Admin.logs',
|
||||||
|
|
||||||
|
// Form status messages
|
||||||
|
StatusMessages: {
|
||||||
|
updated: 'Admin.StatusMessages.updated',
|
||||||
|
tagsUpdated: 'Admin.StatusMessages.tagsUpdated',
|
||||||
|
variantsUpdated: 'Admin.StatusMessages.variantsUpdated',
|
||||||
|
videoCodecUpdated: 'Admin.StatusMessages.videoCodecUpdated',
|
||||||
|
latencyBufferUpdated: 'Admin.StatusMessages.latencyBufferUpdated',
|
||||||
|
deletingEmoji: 'Admin.StatusMessages.deletingEmoji',
|
||||||
|
emojiDeleted: 'Admin.StatusMessages.emojiDeleted',
|
||||||
|
convertingEmoji: 'Admin.StatusMessages.convertingEmoji',
|
||||||
|
uploadingEmoji: 'Admin.StatusMessages.uploadingEmoji',
|
||||||
|
emojiUploadedSuccessfully: 'Admin.StatusMessages.emojiUploadedSuccessfully',
|
||||||
|
thereWasAnError: 'Admin.StatusMessages.thereWasAnError',
|
||||||
|
fileSizeTooBig: 'Admin.StatusMessages.fileSizeTooBig',
|
||||||
|
fileTypeNotSupported: 'Admin.StatusMessages.fileTypeNotSupported',
|
||||||
|
pleaseEnterTag: 'Admin.StatusMessages.pleaseEnterTag',
|
||||||
|
tagAlreadyUsed: 'Admin.StatusMessages.tagAlreadyUsed',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Actions page
|
||||||
|
Actions: {
|
||||||
|
title: 'Admin.Actions.title',
|
||||||
|
description: 'Admin.Actions.description',
|
||||||
|
readMoreLink: 'Admin.Actions.readMoreLink',
|
||||||
|
createNewAction: 'Admin.Actions.createNewAction',
|
||||||
|
createNewActionTitle: 'Admin.Actions.createNewActionTitle',
|
||||||
|
editActionTitle: 'Admin.Actions.editActionTitle',
|
||||||
|
modalDescription: 'Admin.Actions.modalDescription',
|
||||||
|
onlyHttpsSupported: 'Admin.Actions.onlyHttpsSupported',
|
||||||
|
readMoreAboutActions: 'Admin.Actions.readMoreAboutActions',
|
||||||
|
selectActionType: 'Admin.Actions.selectActionType',
|
||||||
|
linkOrEmbedUrl: 'Admin.Actions.linkOrEmbedUrl',
|
||||||
|
customHtml: 'Admin.Actions.customHtml',
|
||||||
|
htmlEmbedPlaceholder: 'Admin.Actions.htmlEmbedPlaceholder',
|
||||||
|
urlPlaceholder: 'Admin.Actions.urlPlaceholder',
|
||||||
|
titlePlaceholder: 'Admin.Actions.titlePlaceholder',
|
||||||
|
descriptionPlaceholder: 'Admin.Actions.descriptionPlaceholder',
|
||||||
|
iconPlaceholder: 'Admin.Actions.iconPlaceholder',
|
||||||
|
optionalBackgroundColor: 'Admin.Actions.optionalBackgroundColor',
|
||||||
|
openExternally: 'Admin.Actions.openExternally',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Webhooks page
|
||||||
|
Webhooks: {
|
||||||
|
createNewWebhook: 'Admin.Webhooks.createNewWebhook',
|
||||||
|
webhookUrlPlaceholder: 'Admin.Webhooks.webhookUrlPlaceholder',
|
||||||
|
selectEvents: 'Admin.Webhooks.selectEvents',
|
||||||
|
selectAll: 'Admin.Webhooks.selectAll',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Access Tokens page
|
||||||
|
AccessTokens: {
|
||||||
|
createNewAccessToken: 'Admin.AccessTokens.createNewAccessToken',
|
||||||
|
nameDescription: 'Admin.AccessTokens.nameDescription',
|
||||||
|
namePlaceholder: 'Admin.AccessTokens.namePlaceholder',
|
||||||
|
selectPermissions: 'Admin.AccessTokens.selectPermissions',
|
||||||
|
cannotEditAfterCreated: 'Admin.AccessTokens.cannotEditAfterCreated',
|
||||||
|
selectAll: 'Admin.AccessTokens.selectAll',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user