feat: settings i18n [skip test] (#4372)

pull/4374/head
James Elliott 2022-11-14 14:49:34 +11:00 committed by GitHub
parent 1a1b85489c
commit 164fc5e80d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 30 deletions

View File

@ -1 +1 @@
{"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal"],"languages":[{"display":"English","locale":"en","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]} {"defaults":{"language":{"display":"English","locale":"en"},"namespace":"portal"},"namespaces":["portal","settings"],"languages":[{"display":"English","locale":"en","namespaces":["portal","settings"],"fallbacks":["en"]},{"display":"Arabic","locale":"ar","namespaces":["portal"],"fallbacks":["en"]},{"display":"Arabic (Saudi Arabia)","locale":"ar-SA","namespaces":["portal"],"fallbacks":["ar","en"]},{"display":"Czech","locale":"cs","namespaces":["portal"],"fallbacks":["en"]},{"display":"Czech (Czechia)","locale":"cs-CZ","namespaces":["portal"],"fallbacks":["cs","en"]},{"display":"Danish","locale":"da","namespaces":["portal"],"fallbacks":["en"]},{"display":"Danish (Denmark)","locale":"da-DK","namespaces":["portal"],"fallbacks":["da","en"]},{"display":"German","locale":"de","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek","locale":"el","namespaces":["portal"],"fallbacks":["en"]},{"display":"Greek (Greece)","locale":"el-GR","namespaces":["portal"],"fallbacks":["el","en"]},{"display":"Spanish","locale":"es","namespaces":["portal"],"fallbacks":["en"]},{"display":"Finnish","locale":"fi","namespaces":["portal"],"fallbacks":["en"]},{"display":"French","locale":"fr","namespaces":["portal"],"fallbacks":["en"]},{"display":"Italian","locale":"it","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese","locale":"ja","namespaces":["portal"],"fallbacks":["en"]},{"display":"Japanese (Japan)","locale":"ja-JP","namespaces":["portal"],"fallbacks":["ja","en"]},{"display":"Norwegian Bokmål","locale":"nb","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål (Norway)","locale":"nb-NO","namespaces":["portal"],"fallbacks":["nb","en"]},{"display":"Dutch","locale":"nl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Norwegian Bokmål","locale":"no","namespaces":["portal"],"fallbacks":["en"]},{"display":"Polish","locale":"pl","namespaces":["portal"],"fallbacks":["en"]},{"display":"Portuguese","locale":"pt","namespaces":["portal"],"fallbacks":["en"]},{"display":"Brazilian Portuguese","locale":"pt-BR","namespaces":["portal"],"fallbacks":["en"]},{"display":"Romanian","locale":"ro","namespaces":["portal"],"fallbacks":["en"]},{"display":"Russian","locale":"ru","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish","locale":"sv","namespaces":["portal"],"fallbacks":["en"]},{"display":"Swedish (Sweden)","locale":"sv-SE","namespaces":["portal"],"fallbacks":["sv","en"]},{"display":"Ukrainian","locale":"uk","namespaces":["portal"],"fallbacks":["en"]},{"display":"Ukrainian (Ukraine)","locale":"uk-UA","namespaces":["portal"],"fallbacks":["uk","en"]},{"display":"Chinese","locale":"zh","namespaces":["portal"],"fallbacks":["en"]},{"display":"Chinese (China)","locale":"zh-CN","namespaces":["portal"],"fallbacks":["zh","en"]},{"display":"Chinese (Taiwan)","locale":"zh-TW","namespaces":["portal"],"fallbacks":["en"]}]}

View File

@ -0,0 +1,28 @@
{
"Actions": "Actions",
"Add": "Add",
"Add new Security Key": "Add new Security Key",
"Attestation Type": "Attestation Type",
"Authenticator Attestation GUID": "Authenticator Attestation GUID",
"Cancel": "Cancel",
"Clone Warning": "Clone Warning",
"Created": "Created",
"Delete": "Delete",
"Details": "Details",
"Edit": "Edit",
"Enabled": "Enabled",
"Last Used": "Last Used",
"Manage your security keys": "Manage your security keys",
"Name": "Name",
"No": "No",
"Provide the details for the new security key": "Provide the details for the new security key",
"Relying Party ID": "Relying Party ID",
"Security Keys": "Security Keys",
"Settings": "Settings",
"Show Details": "Show Details",
"Transports": "Transports",
"Usage Count": "Usage Count",
"Webauthn Credential Identifier": "Credential Identifier: {{id}}",
"Webauthn Public Key": "Public Key: {{key}}",
"Yes": "Yes"
}

View File

@ -31,7 +31,7 @@ i18n.use(Backend)
loadPath: basePath + "/locales/{{lng}}/{{ns}}.json", loadPath: basePath + "/locales/{{lng}}/{{ns}}.json",
}, },
load: "all", load: "all",
ns: ["portal"], ns: ["portal", "settings"],
defaultNS: "portal", defaultNS: "portal",
fallbackLng: { fallbackLng: {
default: ["en"], default: ["en"],

View File

@ -10,10 +10,13 @@ import {
DialogTitle, DialogTitle,
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { useTranslation } from "react-i18next";
interface Props extends DialogProps {} interface Props extends DialogProps {}
export default function AddSecurityKeyDialog(props: Props) { export default function AddSecurityKeyDialog(props: Props) {
const { t: translate } = useTranslation("settings");
const handleAddClick = () => { const handleAddClick = () => {
if (props.onClose) { if (props.onClose) {
props.onClose({}, "backdropClick"); props.onClose({}, "backdropClick");
@ -28,22 +31,22 @@ export default function AddSecurityKeyDialog(props: Props) {
return ( return (
<Dialog {...props}> <Dialog {...props}>
<DialogTitle>Add new Security Key</DialogTitle> <DialogTitle>{translate("Add new Security Key")}</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText>Provide the details for the new security key.</DialogContentText> <DialogContentText>{translate("Provide the details for the new security key")}.</DialogContentText>
<TextField <TextField
autoFocus autoFocus
margin="dense" margin="dense"
id="description" id="description"
label="Description" label={translate("Description")}
type="text" type="text"
fullWidth fullWidth
variant="standard" variant="standard"
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={handleCancelClick}>Cancel</Button> <Button onClick={handleCancelClick}>{translate("Cancel")}</Button>
<Button onClick={handleAddClick}>Add</Button> <Button onClick={handleAddClick}>{translate("Add")}</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@ -31,6 +31,7 @@ import {
Tooltip, Tooltip,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { useTranslation } from "react-i18next";
import { WebauthnDevice } from "@root/models/Webauthn"; import { WebauthnDevice } from "@root/models/Webauthn";
import { getWebauthnDevices } from "@root/services/UserWebauthnDevices"; import { getWebauthnDevices } from "@root/services/UserWebauthnDevices";
@ -42,6 +43,8 @@ interface Props {}
const drawerWidth = 240; const drawerWidth = 240;
export default function SettingsView(props: Props) { export default function SettingsView(props: Props) {
const { t: translate } = useTranslation("settings");
const [webauthnDevices, setWebauthnDevices] = useState<WebauthnDevice[] | undefined>(); const [webauthnDevices, setWebauthnDevices] = useState<WebauthnDevice[] | undefined>();
const [addKeyOpen, setAddKeyOpen] = useState<boolean>(false); const [addKeyOpen, setAddKeyOpen] = useState<boolean>(false);
const [webauthnShowDetails, setWebauthnShowDetails] = useState<number>(-1); const [webauthnShowDetails, setWebauthnShowDetails] = useState<number>(-1);
@ -73,7 +76,7 @@ export default function SettingsView(props: Props) {
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}> <AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}>
<Toolbar variant="dense"> <Toolbar variant="dense">
<Typography style={{ flexGrow: 1 }}>Settings</Typography> <Typography style={{ flexGrow: 1 }}>{translate("Settings")}</Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Drawer <Drawer
@ -92,7 +95,7 @@ export default function SettingsView(props: Props) {
<ListItemIcon> <ListItemIcon>
<SystemSecurityUpdateGoodIcon /> <SystemSecurityUpdateGoodIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={"Security Keys"} /> <ListItemText primary={translate("Security Keys")} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
</List> </List>
@ -101,12 +104,12 @@ export default function SettingsView(props: Props) {
<Box component="main" sx={{ flexGrow: 1, p: 3 }}> <Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Typography>Manage your security keys</Typography> <Typography>{translate("Manage your security keys")}</Typography>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<Stack spacing={1} direction="row"> <Stack spacing={1} direction="row">
<Button color="primary" variant="contained" onClick={handleAddKeyButtonClick}> <Button color="primary" variant="contained" onClick={handleAddKeyButtonClick}>
Add {translate("Add")}
</Button> </Button>
</Stack> </Stack>
</Grid> </Grid>
@ -116,9 +119,9 @@ export default function SettingsView(props: Props) {
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell /> <TableCell />
<TableCell>Name</TableCell> <TableCell>{translate("Name")}</TableCell>
<TableCell>Enabled</TableCell> <TableCell>{translate("Enabled")}</TableCell>
<TableCell align="center">Actions</TableCell> <TableCell align="center">{translate("Actions")}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -131,7 +134,10 @@ export default function SettingsView(props: Props) {
key={x.kid.toString()} key={x.kid.toString()}
> >
<TableCell> <TableCell>
<Tooltip title="Show Details" placement="right"> <Tooltip
title={translate("Show Details")}
placement="right"
>
<IconButton <IconButton
aria-label="expand row" aria-label="expand row"
size="small" size="small"
@ -158,12 +164,15 @@ export default function SettingsView(props: Props) {
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
> >
<Tooltip title="Edit" placement="bottom"> <Tooltip title={translate("Edit")} placement="bottom">
<IconButton aria-label="edit"> <IconButton aria-label="edit">
<EditIcon /> <EditIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip title="Delete" placement="bottom"> <Tooltip
title={translate("Delete")}
placement="bottom"
>
<IconButton aria-label="delete"> <IconButton aria-label="delete">
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
@ -196,7 +205,7 @@ export default function SettingsView(props: Props) {
gutterBottom gutterBottom
component="div" component="div"
> >
Details {translate("Details")}
</Typography> </Typography>
</Box> </Box>
</Grid> </Grid>
@ -218,7 +227,14 @@ export default function SettingsView(props: Props) {
lg={12} lg={12}
xl={12} xl={12}
> >
<Typography>Key ID: {x.kid}</Typography> <Typography>
{translate(
"Webauthn Credential Identifier",
{
id: x.kid.toString(),
},
)}
</Typography>
</Grid> </Grid>
<Grid <Grid
item item
@ -230,6 +246,9 @@ export default function SettingsView(props: Props) {
> >
<Typography> <Typography>
Public Key: {x.public_key} Public Key: {x.public_key}
{translate("Webauthn Public Key", {
key: x.public_key.toString(),
})}
</Typography> </Typography>
</Grid> </Grid>
<Grid <Grid
@ -243,21 +262,29 @@ export default function SettingsView(props: Props) {
<Divider variant="middle" /> <Divider variant="middle" />
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Relying Party ID</Typography> <Typography>
{translate("Relying Party ID")}
</Typography>
<Typography>{x.rpid}</Typography> <Typography>{x.rpid}</Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography> <Typography>
Authenticator Attestation GUID {translate(
"Authenticator Attestation GUID",
)}
</Typography> </Typography>
<Typography>{x.aaguid}</Typography> <Typography>{x.aaguid}</Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Attestation Type</Typography> <Typography>
{translate("Attestation Type")}
</Typography>
<Typography>{x.attestation_type}</Typography> <Typography>{x.attestation_type}</Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Transports</Typography> <Typography>
{translate("Transports")}
</Typography>
<Typography> <Typography>
{x.transports.length === 0 {x.transports.length === 0
? "N/A" ? "N/A"
@ -265,30 +292,40 @@ export default function SettingsView(props: Props) {
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Clone Warning</Typography>
<Typography> <Typography>
{x.clone_warning ? "Yes" : "No"} {translate("Clone Warning")}
</Typography>
<Typography>
{x.clone_warning
? translate("Yes")
: translate("No")}
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Created</Typography> <Typography>
{translate("Created")}
</Typography>
<Typography> <Typography>
{x.created_at.toString()} {x.created_at.toString()}
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Last Used</Typography> <Typography>
{translate("Last Used")}
</Typography>
<Typography> <Typography>
{x.last_used_at === undefined {x.last_used_at === undefined
? "Never" ? translate("Never")
: x.last_used_at.toString()} : x.last_used_at.toString()}
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6} sm={6} md={4} lg={4} xl={3}> <Grid item xs={6} sm={6} md={4} lg={4} xl={3}>
<Typography>Usage Count</Typography> <Typography>
{translate("Usage Count")}
</Typography>
<Typography> <Typography>
{x.sign_count === 0 {x.sign_count === 0
? "Never" ? translate("Never")
: x.sign_count} : x.sign_count}
</Typography> </Typography>
</Grid> </Grid>