translate system menu

This commit is contained in:
proddy
2022-08-28 10:44:22 +02:00
parent 7122e878a5
commit d8e324a005
16 changed files with 366 additions and 83 deletions

View File

@@ -58,7 +58,7 @@ const SignIn: FC = () => {
enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' }); enqueueSnackbar(LL.INVALID_LOGIN(), { variant: 'warning' });
} }
} else { } else {
enqueueSnackbar(extractErrorMessage(error, 'Unexpected error, please try again'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.ERROR()), { variant: 'error' });
} }
setProcessing(false); setProcessing(false);
} }

View File

@@ -35,8 +35,13 @@ const LayoutMenu: FC = () => {
<LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" /> <LayoutMenuItem icon={SettingsInputAntennaIcon} label="Access Point" to="/ap" />
{features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label={LL.NETWORK_TIME()} to="/ntp" />} {features.ntp && <LayoutMenuItem icon={AccessTimeIcon} label={LL.NETWORK_TIME()} to="/ntp" />}
{features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />} {features.mqtt && <LayoutMenuItem icon={DeviceHubIcon} label="MQTT" to="/mqtt" />}
<LayoutMenuItem icon={LockIcon} label={LL.SECURITY()} to="/security" disabled={!authenticatedContext.me.admin} /> <LayoutMenuItem
<LayoutMenuItem icon={SettingsIcon} label="System" to="/system" /> icon={LockIcon}
label={LL.SECURITY()}
to="/security"
disabled={!authenticatedContext.me.admin}
/>
<LayoutMenuItem icon={SettingsIcon} label={LL.SYSTEM()} to="/system" />
</List> </List>
</> </>
); );

View File

@@ -6,6 +6,8 @@ import { Box, Button, LinearProgress, Theme, Typography, useTheme } from '@mui/m
import CloudUploadIcon from '@mui/icons-material/CloudUpload'; import CloudUploadIcon from '@mui/icons-material/CloudUpload';
import CancelIcon from '@mui/icons-material/Cancel'; import CancelIcon from '@mui/icons-material/Cancel';
import { useI18nContext } from '../../i18n/i18n-react';
const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total); const progressPercentage = (progress: ProgressEvent) => Math.round((progress.loaded * 100) / progress.total);
const getBorderColor = (theme: Theme, props: DropzoneState) => { const getBorderColor = (theme: Theme, props: DropzoneState) => {
@@ -41,14 +43,16 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
const { getRootProps, getInputProps } = dropzoneState; const { getRootProps, getInputProps } = dropzoneState;
const theme = useTheme(); const theme = useTheme();
const { LL } = useI18nContext();
const progressText = () => { const progressText = () => {
if (uploading) { if (uploading) {
if (progress?.lengthComputable) { if (progress?.lengthComputable) {
return `Uploading: ${progressPercentage(progress)}%`; return LL.UPLOADING() + `: ${progressPercentage(progress)}%`;
} }
return 'Uploading\u2026'; return LL.UPLOADING() + `\u2026`;
} }
return 'Drop file or click here'; return LL.UPLOAD_DROP_TEXT();
}; };
return ( return (
@@ -81,7 +85,7 @@ const SingleUpload: FC<SingleUploadProps> = ({ onDrop, onCancel, uploading, prog
/> />
</Box> </Box>
<Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}> <Button startIcon={<CancelIcon />} variant="outlined" color="secondary" onClick={onCancel}>
Cancel {LL.CANCEL()}
</Button> </Button>
</Fragment> </Fragment>
)} )}

View File

@@ -31,6 +31,8 @@ import { ButtonRow, FormLoader, SectionContent } from '../../components';
import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils'; import { extractErrorMessage, formatDateTime, formatLocalDateTime, useRest } from '../../utils';
import { AuthenticatedContext } from '../../contexts/authentication'; import { AuthenticatedContext } from '../../contexts/authentication';
import { useI18nContext } from '../../i18n/i18n-react';
export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE; export const isNtpActive = ({ status }: NTPStatus) => status === NTPSyncStatus.NTP_ACTIVE;
export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED; export const isNtpEnabled = ({ status }: NTPStatus) => status !== NTPSyncStatus.NTP_DISABLED;
@@ -68,6 +70,8 @@ const NTPStatusForm: FC = () => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const { LL } = useI18nContext();
const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value); const updateLocalTime = (event: React.ChangeEvent<HTMLInputElement>) => setLocalTime(event.target.value);
const openSetTime = () => { const openSetTime = () => {
@@ -83,11 +87,11 @@ const NTPStatusForm: FC = () => {
await NTPApi.updateTime({ await NTPApi.updateTime({
local_time: formatLocalDateTime(new Date(localTime)) local_time: formatLocalDateTime(new Date(localTime))
}); });
enqueueSnackbar('Time set', { variant: 'success' }); enqueueSnackbar(LL.TIME_SET(), { variant: 'success' });
setSettingTime(false); setSettingTime(false);
loadData(); loadData();
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem updating time'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setProcessing(false); setProcessing(false);
} }

View File

@@ -14,6 +14,8 @@ import { extractErrorMessage } from '../../utils';
import * as EMSESP from '../../project/api'; import * as EMSESP from '../../project/api';
import { useI18nContext } from '../../i18n/i18n-react';
interface UploadFileProps { interface UploadFileProps {
uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>; uploadGeneralFile: (file: File, config?: FileUploadConfig) => AxiosPromise<void>;
} }
@@ -23,6 +25,8 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const { LL } = useI18nContext();
const saveFile = (json: any, endpoint: string) => { const saveFile = (json: any, endpoint: string) => {
const a = document.createElement('a'); const a = document.createElement('a');
const filename = 'emsesp_' + endpoint + '.json'; const filename = 'emsesp_' + endpoint + '.json';
@@ -35,19 +39,19 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
enqueueSnackbar('File downloaded', { variant: 'info' }); enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
}; };
const downloadSettings = async () => { const downloadSettings = async () => {
try { try {
const response = await EMSESP.getSettings(); const response = await EMSESP.getSettings();
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar('Unable to get settings', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else { } else {
saveFile(response.data, 'settings'); saveFile(response.data, 'settings');
} }
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };
@@ -55,47 +59,43 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
try { try {
const response = await EMSESP.getCustomizations(); const response = await EMSESP.getCustomizations();
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar('Unable to get customizations', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else { } else {
saveFile(response.data, 'customizations'); saveFile(response.data, 'customizations');
} }
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem with downloading'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };
return ( return (
<> <>
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Upload {LL.UPLOAD()}
</Typography> </Typography>
{!uploading && ( {!uploading && (
<Box mb={2} color="warning.main"> <Box mb={2} color="warning.main">
<Typography variant="body2"> <Typography variant="body2">{LL.UPLOAD_TEXT()} </Typography>
Upload a new firmware (.bin) file, settings or customizations (.json) file below
</Typography>
</Box> </Box>
)} )}
<SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} /> <SingleUpload onDrop={uploadFile} onCancel={cancelUpload} uploading={uploading} progress={uploadProgress} />
<Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary"> <Typography sx={{ pt: 2, pb: 2 }} variant="h6" color="primary">
Download {LL.DOWNLOAD()}
</Typography> </Typography>
{!uploading && ( {!uploading && (
<> <>
<Box color="warning.main"> <Box color="warning.main">
<Typography mb={1} variant="body2"> <Typography mb={1} variant="body2">
Download the application settings. Be careful when sharing your settings as this file contains passwords {LL.DOWNLOAD_SETTINGS_TEXT()}
and other sensitive system information
</Typography> </Typography>
</Box> </Box>
<Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}> <Button startIcon={<DownloadIcon />} variant="outlined" color="primary" onClick={() => downloadSettings()}>
settings {LL.SETTINGS()}
</Button> </Button>
<Box color="warning.main"> <Box color="warning.main">
<Typography mt={2} mb={1} variant="body2"> <Typography mt={2} mb={1} variant="body2">
Download the entity customizations {LL.DOWNLOAD_CUSTOMIZATION_TEXT()}{' '}
</Typography> </Typography>
</Box> </Box>
<Button <Button
@@ -104,7 +104,7 @@ const GeneralFileUpload: FC<UploadFileProps> = ({ uploadGeneralFile }) => {
color="primary" color="primary"
onClick={() => downloadCustomizations()} onClick={() => downloadCustomizations()}
> >
customizations {LL.CUSTOMIZATION()}
</Button> </Button>
</> </>
)} )}

View File

@@ -12,6 +12,7 @@ import {
ValidatedPasswordField, ValidatedPasswordField,
ValidatedTextField ValidatedTextField
} from '../../components'; } from '../../components';
import { OTASettings } from '../../types'; import { OTASettings } from '../../types';
import { numberValue, updateValue, useRest } from '../../utils'; import { numberValue, updateValue, useRest } from '../../utils';
@@ -19,12 +20,16 @@ import { ValidateFieldsError } from 'async-validator';
import { validate } from '../../validators'; import { validate } from '../../validators';
import { OTA_SETTINGS_VALIDATOR } from '../../validators/system'; import { OTA_SETTINGS_VALIDATOR } from '../../validators/system';
import { useI18nContext } from '../../i18n/i18n-react';
const OTASettingsForm: FC = () => { const OTASettingsForm: FC = () => {
const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({ const { loadData, saving, data, setData, saveData, errorMessage } = useRest<OTASettings>({
read: SystemApi.readOTASettings, read: SystemApi.readOTASettings,
update: SystemApi.updateOTASettings update: SystemApi.updateOTASettings
}); });
const { LL } = useI18nContext();
const updateFormValue = updateValue(setData); const updateFormValue = updateValue(setData);
const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>(); const [fieldErrors, setFieldErrors] = useState<ValidateFieldsError>();
@@ -48,7 +53,7 @@ const OTASettingsForm: FC = () => {
<> <>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />} control={<Checkbox name="enabled" checked={data.enabled} onChange={updateFormValue} />}
label="Enable OTA Updates" label={LL.ENABLE_OTA()}
/> />
<ValidatedTextField <ValidatedTextField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
@@ -64,7 +69,7 @@ const OTASettingsForm: FC = () => {
<ValidatedPasswordField <ValidatedPasswordField
fieldErrors={fieldErrors} fieldErrors={fieldErrors}
name="password" name="password"
label="Password" label={LL.PASSWORD()}
fullWidth fullWidth
variant="outlined" variant="outlined"
value={data.password} value={data.password}
@@ -80,7 +85,7 @@ const OTASettingsForm: FC = () => {
type="submit" type="submit"
onClick={validateAndSubmit} onClick={validateAndSubmit}
> >
Save {LL.SAVE()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</> </>
@@ -88,7 +93,7 @@ const OTASettingsForm: FC = () => {
}; };
return ( return (
<SectionContent title="OTA Settings" titleGutter> <SectionContent title={'OTA ' + LL.SETTINGS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -4,6 +4,8 @@ import { FC, useRef, useState } from 'react';
import * as SystemApi from '../../api/system'; import * as SystemApi from '../../api/system';
import { FormLoader } from '../../components'; import { FormLoader } from '../../components';
import { useI18nContext } from '../../i18n/i18n-react';
const RESTART_TIMEOUT = 2 * 60 * 1000; const RESTART_TIMEOUT = 2 * 60 * 1000;
const POLL_TIMEOUT = 2000; const POLL_TIMEOUT = 2000;
const POLL_INTERVAL = 5000; const POLL_INTERVAL = 5000;
@@ -12,6 +14,8 @@ const RestartMonitor: FC = () => {
const [failed, setFailed] = useState<boolean>(false); const [failed, setFailed] = useState<boolean>(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>(); const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const { LL } = useI18nContext();
const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT); const timeoutAt = useRef(new Date().getTime() + RESTART_TIMEOUT);
const poll = useRef(async () => { const poll = useRef(async () => {
try { try {
@@ -32,12 +36,7 @@ const RestartMonitor: FC = () => {
useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]); useEffect(() => () => timeoutId && clearTimeout(timeoutId), [timeoutId]);
return ( return <FormLoader message={LL.APPLICATION_RESTARTING() + '...'} errorMessage={failed ? 'Timed out' : undefined} />;
<FormLoader
message="EMS-ESP is restarting, please wait&hellip;"
errorMessage={failed ? 'Timed out waiting for device to restart.' : undefined}
/>
);
}; };
export default RestartMonitor; export default RestartMonitor;

View File

@@ -12,8 +12,13 @@ import OTASettingsForm from './OTASettingsForm';
import SystemLog from './SystemLog'; import SystemLog from './SystemLog';
import { useI18nContext } from '../../i18n/i18n-react';
const System: FC = () => { const System: FC = () => {
useLayoutTitle('System');
const { LL } = useI18nContext();
useLayoutTitle(LL.SYSTEM());
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
const { features } = useContext(FeaturesContext); const { features } = useContext(FeaturesContext);
@@ -22,11 +27,11 @@ const System: FC = () => {
return ( return (
<> <>
<RouterTabs value={routerTab}> <RouterTabs value={routerTab}>
<Tab value="status" label="System Status" /> <Tab value="status" label={LL.SYSTEM() + ' ' + LL.STATUS()} />
<Tab value="log" label="System Log" /> <Tab value="log" label={LL.SYSTEM() + ' ' + LL.LOG()} />
{features.ota && <Tab value="ota" label="OTA Settings" disabled={!me.admin} />} {features.ota && <Tab value="ota" label={"OTA " + LL.SETTINGS()} disabled={!me.admin} />}
{features.upload_firmware && <Tab value="upload" label="Upload/Download" disabled={!me.admin} />} {features.upload_firmware && <Tab value="upload" label={LL.UPLOAD_DOWNLOAD()} disabled={!me.admin} />}
</RouterTabs> </RouterTabs>
<Routes> <Routes>
<Route path="status" element={<SystemStatusForm />} /> <Route path="status" element={<SystemStatusForm />} />

View File

@@ -15,6 +15,9 @@ import DownloadIcon from '@mui/icons-material/GetApp';
import { useSnackbar } from 'notistack'; import { useSnackbar } from 'notistack';
import { EVENT_SOURCE_ROOT } from '../../api/endpoints'; import { EVENT_SOURCE_ROOT } from '../../api/endpoints';
import { useI18nContext } from '../../i18n/i18n-react';
export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log'; export const LOG_EVENTSOURCE_URL = EVENT_SOURCE_ROOT + 'log';
const useWindowSize = () => { const useWindowSize = () => {
@@ -63,6 +66,8 @@ const levelLabel = (level: LogLevel) => {
const SystemLog: FC = () => { const SystemLog: FC = () => {
useWindowSize(); useWindowSize();
const { LL } = useI18nContext();
const { loadData, data, setData } = useRest<LogSettings>({ const { loadData, data, setData } = useRest<LogSettings>({
read: SystemApi.readLogSettings read: SystemApi.readLogSettings
}); });
@@ -104,10 +109,10 @@ const SystemLog: FC = () => {
compact: data.compact compact: data.compact
}); });
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar('Problem applying log settings', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_UPDATING(), { variant: 'error' });
} }
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem applying log settings'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} }
} }
}; };
@@ -159,9 +164,9 @@ const SystemLog: FC = () => {
try { try {
setLogEntries((await SystemApi.readLogEntries()).data); setLogEntries((await SystemApi.readLogEntries()).data);
} catch (error: unknown) { } catch (error: unknown) {
setErrorMessage(extractErrorMessage(error, 'Failed to fetch log')); setErrorMessage(extractErrorMessage(error, LL.PROBLEM_LOADING()));
} }
}, []); }, [LL]);
useEffect(() => { useEffect(() => {
fetchLog(); fetchLog();
@@ -214,7 +219,7 @@ const SystemLog: FC = () => {
</ValidatedTextField> </ValidatedTextField>
</Grid> </Grid>
<Grid item xs={3}> <Grid item xs={3}>
<FormLabel>Buffer size</FormLabel> <FormLabel>{LL.BUFFER_SIZE()}</FormLabel>
<Slider <Slider
value={data.max_messages} value={data.max_messages}
valueLabelDisplay="auto" valueLabelDisplay="auto"
@@ -235,12 +240,12 @@ const SystemLog: FC = () => {
<Grid item> <Grid item>
<BlockFormControlLabel <BlockFormControlLabel
control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />} control={<Checkbox checked={data.compact} onChange={updateFormValue} name="compact" />}
label="Compact" label={LL.COMPACT()}
/> />
</Grid> </Grid>
<Grid item> <Grid item>
<Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}> <Button startIcon={<DownloadIcon />} variant="outlined" color="secondary" onClick={onDownload}>
Export {LL.EXPORT()}
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
@@ -273,7 +278,7 @@ const SystemLog: FC = () => {
}; };
return ( return (
<SectionContent title="System Log" titleGutter id="log-window"> <SectionContent title={LL.SYSTEM() + ' ' + LL.LOG()} titleGutter id="log-window">
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -39,6 +39,8 @@ import { AuthenticatedContext } from '../../contexts/authentication';
import axios from 'axios'; import axios from 'axios';
import { useI18nContext } from '../../i18n/i18n-react';
export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest'; export const VERSIONCHECK_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/latest';
export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest'; export const VERSIONCHECK_DEV_ENDPOINT = 'https://api.github.com/repos/emsesp/EMS-ESP32/releases/tags/latest';
export const uploadURL = window.location.origin + '/system/upload'; export const uploadURL = window.location.origin + '/system/upload';
@@ -48,6 +50,8 @@ function formatNumber(num: number) {
} }
const SystemStatusForm: FC = () => { const SystemStatusForm: FC = () => {
const { LL } = useI18nContext();
const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus }); const { loadData, data, errorMessage } = useRest<SystemStatus>({ read: SystemApi.readSystemStatus });
const { me } = useContext(AuthenticatedContext); const { me } = useContext(AuthenticatedContext);
@@ -80,9 +84,9 @@ const SystemStatusForm: FC = () => {
setProcessing(true); setProcessing(true);
try { try {
await SystemApi.restart(); await SystemApi.restart();
enqueueSnackbar('EMS-ESP is restarting...', { variant: 'info' }); enqueueSnackbar(LL.APPLICATION_RESTARTING(), { variant: 'info' });
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem restarting device'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} finally { } finally {
setConfirmRestart(false); setConfirmRestart(false);
setProcessing(false); setProcessing(false);
@@ -92,7 +96,7 @@ const SystemStatusForm: FC = () => {
const renderRestartDialog = () => ( const renderRestartDialog = () => (
<Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}> <Dialog open={confirmRestart} onClose={() => setConfirmRestart(false)}>
<DialogTitle>Restart</DialogTitle> <DialogTitle>Restart</DialogTitle>
<DialogContent dividers>Are you sure you want to restart EMS-ESP?</DialogContent> <DialogContent dividers>{LL.RESTART_TEXT()}</DialogContent>
<DialogActions> <DialogActions>
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
@@ -119,16 +123,12 @@ const SystemStatusForm: FC = () => {
const renderVersionDialog = () => { const renderVersionDialog = () => {
return ( return (
<Dialog open={showingVersion} onClose={() => setShowingVersion(false)}> <Dialog open={showingVersion} onClose={() => setShowingVersion(false)}>
<DialogTitle>Version Check</DialogTitle> <DialogTitle>{LL.VERSION_CHECK()}</DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<MessageBox <MessageBox my={0} level="info" message={LL.SYSTEM_VERSION_RUNNING() + ' ' + data?.emsesp_version} />
my={0}
level="info"
message={'You are currently running EMS-ESP version ' + data?.emsesp_version}
/>
{latestVersion && ( {latestVersion && (
<Box mt={2} mb={2}> <Box mt={2} mb={2}>
The latest <u>official</u> version is <b>{latestVersion.version}</b>&nbsp;( {LL.THE_LATEST()}&nbsp;<u>official</u> version is <b>{latestVersion.version}</b>&nbsp;(
<Link target="_blank" href={latestVersion.changelog} color="primary"> <Link target="_blank" href={latestVersion.changelog} color="primary">
{'release notes'} {'release notes'}
</Link> </Link>
@@ -142,7 +142,7 @@ const SystemStatusForm: FC = () => {
{latestDevVersion && ( {latestDevVersion && (
<Box mt={2} mb={2}> <Box mt={2} mb={2}>
The latest <u>development</u> version is&nbsp;<b>{latestDevVersion.version}</b> {LL.THE_LATEST()}&nbsp;<u>development</u> version is&nbsp;<b>{latestDevVersion.version}</b>
&nbsp;( &nbsp;(
<Link target="_blank" href={latestDevVersion.changelog} color="primary"> <Link target="_blank" href={latestDevVersion.changelog} color="primary">
{'release notes'} {'release notes'}
@@ -157,17 +157,17 @@ const SystemStatusForm: FC = () => {
<Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}> <Box color="warning.main" p={0} pl={0} pr={0} mt={4} mb={0}>
<Typography variant="body2"> <Typography variant="body2">
Use&nbsp; {LL.USE()}&nbsp;
<Link target="_blank" href={uploadURL} color="primary"> <Link target="_blank" href={uploadURL} color="primary">
{'UPLOAD'} {'UPLOAD'}
</Link> </Link>
&nbsp;to apply the new firmware &nbsp;{LL.SYSTEM_APPLY_FIRMWARE()}
</Typography> </Typography>
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary"> <Button variant="outlined" onClick={() => setShowingVersion(false)} color="secondary">
Close {LL.CLOSE()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -178,9 +178,9 @@ const SystemStatusForm: FC = () => {
setProcessing(true); setProcessing(true);
try { try {
await SystemApi.factoryReset(); await SystemApi.factoryReset();
enqueueSnackbar('Device has been factory reset and will now restart', { variant: 'info' }); enqueueSnackbar(LL.SYSTEM_FACTORY_TEXT(), { variant: 'info' });
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem factory resetting the device'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_UPDATING()), { variant: 'error' });
} finally { } finally {
setConfirmFactoryReset(false); setConfirmFactoryReset(false);
setProcessing(false); setProcessing(false);
@@ -189,8 +189,8 @@ const SystemStatusForm: FC = () => {
const renderFactoryResetDialog = () => ( const renderFactoryResetDialog = () => (
<Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}> <Dialog open={confirmFactoryReset} onClose={() => setConfirmFactoryReset(false)}>
<DialogTitle>Factory Reset</DialogTitle> <DialogTitle>{LL.FACTORY_RESET()}</DialogTitle>
<DialogContent dividers>Are you sure you want to reset the device to its factory defaults?</DialogContent> <DialogContent dividers>{LL.SYSTEM_FACTORY_TEXT_DIALOG()}</DialogContent>
<DialogActions> <DialogActions>
<Button <Button
startIcon={<CancelIcon />} startIcon={<CancelIcon />}
@@ -198,7 +198,7 @@ const SystemStatusForm: FC = () => {
onClick={() => setConfirmFactoryReset(false)} onClick={() => setConfirmFactoryReset(false)}
color="secondary" color="secondary"
> >
Cancel {LL.CANCEL()}
</Button> </Button>
<Button <Button
startIcon={<SettingsBackupRestoreIcon />} startIcon={<SettingsBackupRestoreIcon />}
@@ -208,7 +208,7 @@ const SystemStatusForm: FC = () => {
autoFocus autoFocus
color="error" color="error"
> >
Factory Reset {LL.FACTORY_RESET()}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
@@ -231,7 +231,7 @@ const SystemStatusForm: FC = () => {
<ListItemText primary="EMS-ESP Version" secondary={'v' + data.emsesp_version} /> <ListItemText primary="EMS-ESP Version" secondary={'v' + data.emsesp_version} />
{latestVersion && ( {latestVersion && (
<Button color="primary" onClick={() => setShowingVersion(true)}> <Button color="primary" onClick={() => setShowingVersion(true)}>
Version Check {LL.VERSION_CHECK()}
</Button> </Button>
)} )}
</ListItem> </ListItem>
@@ -335,7 +335,7 @@ const SystemStatusForm: FC = () => {
<Box flexGrow={1} sx={{ '& button': { mt: 2 } }}> <Box flexGrow={1} sx={{ '& button': { mt: 2 } }}>
<ButtonRow> <ButtonRow>
<Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}> <Button startIcon={<RefreshIcon />} variant="outlined" color="secondary" onClick={loadData}>
Refresh {LL.REFRESH()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -348,7 +348,7 @@ const SystemStatusForm: FC = () => {
color="primary" color="primary"
onClick={() => setConfirmRestart(true)} onClick={() => setConfirmRestart(true)}
> >
Restart {LL.RESTART()}
</Button> </Button>
<Button <Button
startIcon={<SettingsBackupRestoreIcon />} startIcon={<SettingsBackupRestoreIcon />}
@@ -356,7 +356,7 @@ const SystemStatusForm: FC = () => {
onClick={() => setConfirmFactoryReset(true)} onClick={() => setConfirmFactoryReset(true)}
color="error" color="error"
> >
Factory reset {LL.FACTORY_RESET()}
</Button> </Button>
</ButtonRow> </ButtonRow>
</Box> </Box>
@@ -370,7 +370,7 @@ const SystemStatusForm: FC = () => {
}; };
return ( return (
<SectionContent title="System Status" titleGutter> <SectionContent title={LL.SYSTEM() + ' ' + LL.STATUS()} titleGutter>
{content()} {content()}
</SectionContent> </SectionContent>
); );

View File

@@ -7,9 +7,13 @@ import { FileUploadConfig } from '../../api/endpoints';
import GeneralFileUpload from './GeneralFileUpload'; import GeneralFileUpload from './GeneralFileUpload';
import RestartMonitor from './RestartMonitor'; import RestartMonitor from './RestartMonitor';
import { useI18nContext } from '../../i18n/i18n-react';
const UploadFileForm: FC = () => { const UploadFileForm: FC = () => {
const [restarting, setRestarting] = useState<boolean>(); const [restarting, setRestarting] = useState<boolean>();
const { LL } = useI18nContext();
const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => { const uploadFile = useRef(async (file: File, config?: FileUploadConfig) => {
const response = await SystemApi.uploadFile(file, config); const response = await SystemApi.uploadFile(file, config);
setRestarting(true); setRestarting(true);
@@ -17,7 +21,7 @@ const UploadFileForm: FC = () => {
}); });
return ( return (
<SectionContent title="Upload/Download" titleGutter> <SectionContent title={LL.UPLOAD_DOWNLOAD()} titleGutter>
{restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />} {restarting ? <RestartMonitor /> : <GeneralFileUpload uploadGeneralFile={uploadFile.current} />}
</SectionContent> </SectionContent>
); );

View File

@@ -14,6 +14,7 @@ const de: Translation = {
LOGGED_IN: 'Eingeloggt als {name}', LOGGED_IN: 'Eingeloggt als {name}',
PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren', PLEASE_SIGNIN: 'Bitte einloggen, um fortzufahren',
UPLOAD_SUCCESSFUL: 'Hochladen erfolgreich', UPLOAD_SUCCESSFUL: 'Hochladen erfolgreich',
DOWNLOAD_SUCCESSFUL: 'DE_Download successful',
INVALID_LOGIN: 'Ungültige Login Daten', INVALID_LOGIN: 'Ungültige Login Daten',
NETWORK_CONNECTION: 'Netzwerkverbindung', NETWORK_CONNECTION: 'Netzwerkverbindung',
SECURITY: 'Sicherheit', SECURITY: 'Sicherheit',
@@ -24,7 +25,7 @@ const de: Translation = {
DESCRIPTION: 'Bezeichnung', DESCRIPTION: 'Bezeichnung',
ENTITIES: 'Entitäten', ENTITIES: 'Entitäten',
REFRESH: 'Aktualisierung', REFRESH: 'Aktualisierung',
EXPORT: 'Export', EXPORT: 'DE_Export',
ENTITY_NAME: 'Entitätsname', ENTITY_NAME: 'Entitätsname',
VALUE: 'Wert', VALUE: 'Wert',
SHOW_FAV: 'nur Favoriten anzeigen', SHOW_FAV: 'nur Favoriten anzeigen',
@@ -145,9 +146,34 @@ const de: Translation = {
HELP_INFORMATION_9: 'und hängen Sie Ihre Systemdetails für eine schnellere Antwort an', HELP_INFORMATION_9: 'und hängen Sie Ihre Systemdetails für eine schnellere Antwort an',
HELP_INFORMATION_10: 'EMS-ESP wird immer ein kostenloses Open-Source-Projekt sein. Bitte erwägen Sie, es mit einem', HELP_INFORMATION_10: 'EMS-ESP wird immer ein kostenloses Open-Source-Projekt sein. Bitte erwägen Sie, es mit einem',
UPLOAD: 'Hochladen', UPLOAD: 'Hochladen',
DOWNLOAD: 'DE_Download',
ABORTED: 'abgebrochen', ABORTED: 'abgebrochen',
FAILED: 'gescheitert', FAILED: 'gescheitert',
SUCCESSFUL: 'erfolgreich' SUCCESSFUL: 'erfolgreich',
SYSTEM: 'DE_System',
LOG: 'DE_Log',
STATUS: 'DE_Status',
UPLOAD_DOWNLOAD: 'DE_Upload/Download',
SYSTEM_VERSION_RUNNING: 'DE_You are currently running version',
SYSTEM_APPLY_FIRMWARE: 'DE_to apply the new firmware',
CLOSE: 'DE_Close',
USE: 'DE_Use',
FACTORY_RESET: 'DE_Factory Reset',
SYSTEM_FACTORY_TEXT: 'DE_Device has been factory reset and will now restart',
SYSTEM_FACTORY_TEXT_DIALOG: 'DE_Are you sure you want to reset the device to its factory defaults?',
VERSION_CHECK: 'DE_Version Check',
THE_LATEST: 'DE_The latest',
BUFFER_SIZE: 'DE_Buffer Size',
COMPACT: 'DE_Compact',
ENABLE_OTA: 'DE_Enable OTA Updates',
DOWNLOAD_CUSTOMIZATION_TEXT: 'DE_Download the entity customizations',
DOWNLOAD_SETTINGS_TEXT:
'DE_Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'DE_Upload a new firmware (.bin) file, settings or customizations (.json) file below',
UPLOADING: 'DE_Uploading',
UPLOAD_DROP_TEXT: 'DE_Drop file or click here',
ERROR: 'DE_Unexpected Error, please try again',
TIME_SET: 'DE_Time set'
}; };
export default de; export default de;

View File

@@ -14,6 +14,7 @@ const en: BaseTranslation = {
LOGGED_IN: 'Logged in as {name}', LOGGED_IN: 'Logged in as {name}',
PLEASE_SIGNIN: 'Please sign in to continue', PLEASE_SIGNIN: 'Please sign in to continue',
UPLOAD_SUCCESSFUL: 'Upload successful', UPLOAD_SUCCESSFUL: 'Upload successful',
DOWNLOAD_SUCCESSFUL: 'Download successful',
INVALID_LOGIN: 'Invalid login details', INVALID_LOGIN: 'Invalid login details',
NETWORK_CONNECTION: 'Network Connection', NETWORK_CONNECTION: 'Network Connection',
SECURITY: 'Security', SECURITY: 'Security',
@@ -145,9 +146,34 @@ const en: BaseTranslation = {
HELP_INFORMATION_9: 'and attach your system details for a faster response', HELP_INFORMATION_9: 'and attach your system details for a faster response',
HELP_INFORMATION_10: 'EMS-ESP will always be a free and open-source project. Please consider supporting it with a', HELP_INFORMATION_10: 'EMS-ESP will always be a free and open-source project. Please consider supporting it with a',
UPLOAD: 'Upload', UPLOAD: 'Upload',
DOWNLOAD: 'Download',
ABORTED: 'aborted', ABORTED: 'aborted',
FAILED: 'failed', FAILED: 'failed',
SUCCESSFUL: 'successful' SUCCESSFUL: 'successful',
SYSTEM: 'System',
LOG: 'Log',
STATUS: 'Status',
UPLOAD_DOWNLOAD: 'Upload/Download',
SYSTEM_VERSION_RUNNING: 'You are currently running version',
SYSTEM_APPLY_FIRMWARE: 'to apply the new firmware',
CLOSE: 'Close',
USE: 'Use',
FACTORY_RESET: 'Factory Reset',
SYSTEM_FACTORY_TEXT: 'Device has been factory reset and will now restart',
SYSTEM_FACTORY_TEXT_DIALOG: 'Are you sure you want to reset the device to its factory defaults?',
VERSION_CHECK: 'Version Check',
THE_LATEST: 'The latest',
BUFFER_SIZE: 'Buffer Size',
COMPACT: 'Compact',
ENABLE_OTA: 'Enable OTA Updates',
DOWNLOAD_CUSTOMIZATION_TEXT: 'Download the entity customizations',
DOWNLOAD_SETTINGS_TEXT:
'Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information',
UPLOAD_TEXT: 'Upload a new firmware (.bin) file, settings or customizations (.json) file below',
UPLOADING: 'Uploading',
UPLOAD_DROP_TEXT: 'Drop file or click here',
ERROR: 'Unexpected Error, please try again',
TIME_SET: 'Time set'
}; };
export default en; export default en;

View File

@@ -67,6 +67,10 @@ type RootTranslation = {
* Upload successful * Upload successful
*/ */
UPLOAD_SUCCESSFUL: string UPLOAD_SUCCESSFUL: string
/**
* Download successful
*/
DOWNLOAD_SUCCESSFUL: string
/** /**
* Invalid login details * Invalid login details
*/ */
@@ -583,6 +587,10 @@ type RootTranslation = {
* Upload * Upload
*/ */
UPLOAD: string UPLOAD: string
/**
* Download
*/
DOWNLOAD: string
/** /**
* aborted * aborted
*/ */
@@ -595,6 +603,98 @@ type RootTranslation = {
* successful * successful
*/ */
SUCCESSFUL: string SUCCESSFUL: string
/**
* System
*/
SYSTEM: string
/**
* Log
*/
LOG: string
/**
* Status
*/
STATUS: string
/**
* Upload/Download
*/
UPLOAD_DOWNLOAD: string
/**
* You are currently running version
*/
SYSTEM_VERSION_RUNNING: string
/**
* to apply the new firmware
*/
SYSTEM_APPLY_FIRMWARE: string
/**
* Close
*/
CLOSE: string
/**
* Use
*/
USE: string
/**
* Factory Reset
*/
FACTORY_RESET: string
/**
* Device has been factory reset and will now restart
*/
SYSTEM_FACTORY_TEXT: string
/**
* Are you sure you want to reset the device to its factory defaults?
*/
SYSTEM_FACTORY_TEXT_DIALOG: string
/**
* Version Check
*/
VERSION_CHECK: string
/**
* The latest
*/
THE_LATEST: string
/**
* Buffer Size
*/
BUFFER_SIZE: string
/**
* Compact
*/
COMPACT: string
/**
* Enable OTA Updates
*/
ENABLE_OTA: string
/**
* Download the entity customizations
*/
DOWNLOAD_CUSTOMIZATION_TEXT: string
/**
* Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information
*/
DOWNLOAD_SETTINGS_TEXT: string
/**
* Upload a new firmware (.bin) file, settings or customizations (.json) file below
*/
UPLOAD_TEXT: string
/**
* Uploading
*/
UPLOADING: string
/**
* Drop file or click here
*/
UPLOAD_DROP_TEXT: string
/**
* Unexpected Error, please try again
*/
ERROR: string
/**
* Time set
*/
TIME_SET: string
} }
export type TranslationFunctions = { export type TranslationFunctions = {
@@ -650,6 +750,10 @@ export type TranslationFunctions = {
* Upload successful * Upload successful
*/ */
UPLOAD_SUCCESSFUL: () => LocalizedString UPLOAD_SUCCESSFUL: () => LocalizedString
/**
* Download successful
*/
DOWNLOAD_SUCCESSFUL: () => LocalizedString
/** /**
* Invalid login details * Invalid login details
*/ */
@@ -1156,6 +1260,10 @@ export type TranslationFunctions = {
* Upload * Upload
*/ */
UPLOAD: () => LocalizedString UPLOAD: () => LocalizedString
/**
* Download
*/
DOWNLOAD: () => LocalizedString
/** /**
* aborted * aborted
*/ */
@@ -1168,6 +1276,98 @@ export type TranslationFunctions = {
* successful * successful
*/ */
SUCCESSFUL: () => LocalizedString SUCCESSFUL: () => LocalizedString
/**
* System
*/
SYSTEM: () => LocalizedString
/**
* Log
*/
LOG: () => LocalizedString
/**
* Status
*/
STATUS: () => LocalizedString
/**
* Upload/Download
*/
UPLOAD_DOWNLOAD: () => LocalizedString
/**
* You are currently running version
*/
SYSTEM_VERSION_RUNNING: () => LocalizedString
/**
* to apply the new firmware
*/
SYSTEM_APPLY_FIRMWARE: () => LocalizedString
/**
* Close
*/
CLOSE: () => LocalizedString
/**
* Use
*/
USE: () => LocalizedString
/**
* Factory Reset
*/
FACTORY_RESET: () => LocalizedString
/**
* Device has been factory reset and will now restart
*/
SYSTEM_FACTORY_TEXT: () => LocalizedString
/**
* Are you sure you want to reset the device to its factory defaults?
*/
SYSTEM_FACTORY_TEXT_DIALOG: () => LocalizedString
/**
* Version Check
*/
VERSION_CHECK: () => LocalizedString
/**
* The latest
*/
THE_LATEST: () => LocalizedString
/**
* Buffer Size
*/
BUFFER_SIZE: () => LocalizedString
/**
* Compact
*/
COMPACT: () => LocalizedString
/**
* Enable OTA Updates
*/
ENABLE_OTA: () => LocalizedString
/**
* Download the entity customizations
*/
DOWNLOAD_CUSTOMIZATION_TEXT: () => LocalizedString
/**
* Download the application settings. Be careful when sharing your settings as this file contains passwords and other sensitive system information
*/
DOWNLOAD_SETTINGS_TEXT: () => LocalizedString
/**
* Upload a new firmware (.bin) file, settings or customizations (.json) file below
*/
UPLOAD_TEXT: () => LocalizedString
/**
* Uploading
*/
UPLOADING: () => LocalizedString
/**
* Drop file or click here
*/
UPLOAD_DROP_TEXT: () => LocalizedString
/**
* Unexpected Error, please try again
*/
ERROR: () => LocalizedString
/**
* Time set
*/
TIME_SET: () => LocalizedString
} }
export type Formatters = {} export type Formatters = {}

View File

@@ -364,9 +364,9 @@ const DashboardData: FC = () => {
try { try {
setCoreData((await EMSESP.readCoreData()).data); setCoreData((await EMSESP.readCoreData()).data);
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Failed to fetch core data'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}, [enqueueSnackbar]); }, [enqueueSnackbar, LL]);
useEffect(() => { useEffect(() => {
fetchCoreData(); fetchCoreData();
@@ -385,7 +385,7 @@ const DashboardData: FC = () => {
try { try {
setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data); setDeviceData((await EMSESP.readDeviceData({ id: unique_id })).data);
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching device data'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };
@@ -393,7 +393,7 @@ const DashboardData: FC = () => {
try { try {
setSensorData((await EMSESP.readSensorData()).data); setSensorData((await EMSESP.readSensorData()).data);
} catch (error: unknown) { } catch (error: unknown) {
enqueueSnackbar(extractErrorMessage(error, 'Problem fetching sensor data'), { variant: 'error' }); enqueueSnackbar(extractErrorMessage(error, LL.PROBLEM_LOADING()), { variant: 'error' });
} }
}; };

View File

@@ -35,7 +35,7 @@ const HelpInformation: FC = () => {
document.body.appendChild(a); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
enqueueSnackbar('System information downloaded', { variant: 'info' }); enqueueSnackbar(LL.DOWNLOAD_SUCCESSFUL(), { variant: 'info' });
}; };
const callAPI = async (endpoint: string) => { const callAPI = async (endpoint: string) => {
@@ -46,7 +46,7 @@ const HelpInformation: FC = () => {
id: 0 id: 0
}); });
if (response.status !== 200) { if (response.status !== 200) {
enqueueSnackbar('API call failed', { variant: 'error' }); enqueueSnackbar(LL.PROBLEM_LOADING(), { variant: 'error' });
} else { } else {
saveFile(response.data, endpoint); saveFile(response.data, endpoint);
} }