Translations
This commit is contained in:
parent
9be8be49ef
commit
66cb35b5fc
7 changed files with 87 additions and 122 deletions
|
@ -49,10 +49,7 @@ import (
|
||||||
- "mute" setting
|
- "mute" setting
|
||||||
- figure out what settings are "web" or "phone"
|
- figure out what settings are "web" or "phone"
|
||||||
UI:
|
UI:
|
||||||
- Translations
|
|
||||||
- aria-labels
|
|
||||||
- Home UI sign-in/login to top right
|
- Home UI sign-in/login to top right
|
||||||
-
|
|
||||||
rate limiting:
|
rate limiting:
|
||||||
- login/account endpoints
|
- login/account endpoints
|
||||||
Tests:
|
Tests:
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
{
|
{
|
||||||
|
"signup_title": "Create a ntfy account",
|
||||||
|
"signup_form_username": "Username",
|
||||||
|
"signup_form_password": "Password",
|
||||||
|
"signup_form_confirm_password": "Confirm password",
|
||||||
|
"signup_form_button_submit": "Sign up",
|
||||||
|
"signup_already_have_account": "Already have an account? Sign in!",
|
||||||
|
"signup_disabled": "Signup is disabled",
|
||||||
|
"signup_error_username_taken": "Username {{username}} is already taken",
|
||||||
|
"signup_error_creation_limit_reached": "Account creation limit reached",
|
||||||
|
"signup_error_unknown": "Unknown error. Check logs for details.",
|
||||||
|
"login_title": "Sign in to your ntfy account",
|
||||||
|
"login_form_button_submit": "Sign in",
|
||||||
|
"login_link_signup": "Sign up",
|
||||||
"action_bar_show_menu": "Show menu",
|
"action_bar_show_menu": "Show menu",
|
||||||
"action_bar_logo_alt": "ntfy logo",
|
"action_bar_logo_alt": "ntfy logo",
|
||||||
"action_bar_settings": "Settings",
|
"action_bar_settings": "Settings",
|
||||||
|
|
|
@ -21,6 +21,7 @@ import IconButton from "@mui/material/IconButton";
|
||||||
import {useOutletContext} from "react-router-dom";
|
import {useOutletContext} from "react-router-dom";
|
||||||
import {formatBytes} from "../app/utils";
|
import {formatBytes} from "../app/utils";
|
||||||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
||||||
|
import {Pref, PrefGroup} from "./Pref";
|
||||||
|
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
if (!session.exists()) {
|
if (!session.exists()) {
|
||||||
|
@ -56,9 +57,11 @@ const Basics = () => {
|
||||||
const Username = () => {
|
const Username = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { account } = useOutletContext();
|
const { account } = useOutletContext();
|
||||||
|
const labelId = "prefUsername";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pref title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
||||||
<div>
|
<div aria-labelledby={labelId}>
|
||||||
{session.username()}
|
{session.username()}
|
||||||
{account?.role === "admin"
|
{account?.role === "admin"
|
||||||
? <>{" "}<Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></>
|
? <>{" "}<Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></>
|
||||||
|
@ -72,6 +75,7 @@ const ChangePassword = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dialogKey, setDialogKey] = useState(0);
|
const [dialogKey, setDialogKey] = useState(0);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
const labelId = "prefChangePassword";
|
||||||
|
|
||||||
const handleDialogOpen = () => {
|
const handleDialogOpen = () => {
|
||||||
setDialogKey(prev => prev+1);
|
setDialogKey(prev => prev+1);
|
||||||
|
@ -97,8 +101,8 @@ const ChangePassword = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pref title={t("account_basics_password_title")} description={t("account_basics_password_description")}>
|
<Pref labelId={labelId} title={t("account_basics_password_title")} description={t("account_basics_password_description")}>
|
||||||
<div>
|
<div aria-labelledby={labelId}>
|
||||||
<Typography color="gray" sx={{float: "left", fontSize: "0.7rem", lineHeight: "3.5"}}>⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤</Typography>
|
<Typography color="gray" sx={{float: "left", fontSize: "0.7rem", lineHeight: "3.5"}}>⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤</Typography>
|
||||||
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
||||||
<EditIcon/>
|
<EditIcon/>
|
||||||
|
@ -302,55 +306,4 @@ const DeleteAccountDialog = (props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// FIXME duplicate code
|
|
||||||
|
|
||||||
const PrefGroup = (props) => {
|
|
||||||
return (
|
|
||||||
<div role="table">
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
const Pref = (props) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="row"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
marginTop: "10px",
|
|
||||||
marginBottom: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
role="cell"
|
|
||||||
aria-label={props.title}
|
|
||||||
style={{
|
|
||||||
flex: '1 0 40%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingRight: '30px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div><b>{props.title}</b>{props.subtitle && <em> ({props.subtitle})</em>}</div>
|
|
||||||
{props.description && <div><em>{props.description}</em></div>}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
role="cell"
|
|
||||||
style={{
|
|
||||||
flex: '1 0 calc(60% - 50px)',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Account;
|
export default Account;
|
||||||
|
|
|
@ -46,7 +46,7 @@ const Login = () => {
|
||||||
return (
|
return (
|
||||||
<AvatarBox>
|
<AvatarBox>
|
||||||
<Typography sx={{ typography: 'h6' }}>
|
<Typography sx={{ typography: 'h6' }}>
|
||||||
{t("Sign in to your ntfy account")}
|
{t("login_title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}>
|
<Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -54,7 +54,7 @@ const Login = () => {
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
id="username"
|
id="username"
|
||||||
label={t("Username")}
|
label={t("signup_form_username")}
|
||||||
name="username"
|
name="username"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={ev => setUsername(ev.target.value.trim())}
|
onChange={ev => setUsername(ev.target.value.trim())}
|
||||||
|
@ -65,7 +65,7 @@ const Login = () => {
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
name="password"
|
name="password"
|
||||||
label={t("Password")}
|
label={t("signup_form_password")}
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
value={password}
|
value={password}
|
||||||
|
@ -79,7 +79,7 @@ const Login = () => {
|
||||||
disabled={username === "" || password === ""}
|
disabled={username === "" || password === ""}
|
||||||
sx={{mt: 2, mb: 2}}
|
sx={{mt: 2, mb: 2}}
|
||||||
>
|
>
|
||||||
{t("Sign in")}
|
{t("login_form_button_submit")}
|
||||||
</Button>
|
</Button>
|
||||||
{error &&
|
{error &&
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
@ -94,7 +94,7 @@ const Login = () => {
|
||||||
}
|
}
|
||||||
<Box sx={{width: "100%"}}>
|
<Box sx={{width: "100%"}}>
|
||||||
{config.enableResetPassword && <div style={{float: "left"}}><NavLink to={routes.resetPassword} variant="body1">{t("Reset password")}</NavLink></div>}
|
{config.enableResetPassword && <div style={{float: "left"}}><NavLink to={routes.resetPassword} variant="body1">{t("Reset password")}</NavLink></div>}
|
||||||
{config.enableSignup && <div style={{float: "right"}}><NavLink to={routes.signup} variant="body1">{t("Sign up")}</NavLink></div>}
|
{config.enableSignup && <div style={{float: "right"}}><NavLink to={routes.signup} variant="body1">{t("login_link_signup")}</NavLink></div>}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</AvatarBox>
|
</AvatarBox>
|
||||||
|
|
50
web/src/components/Pref.js
Normal file
50
web/src/components/Pref.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export const PrefGroup = (props) => {
|
||||||
|
return (
|
||||||
|
<div role="table">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Pref = (props) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="row"
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
marginTop: "10px",
|
||||||
|
marginBottom: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="cell"
|
||||||
|
id={props.labelId ?? ""}
|
||||||
|
aria-label={props.title}
|
||||||
|
style={{
|
||||||
|
flex: '1 0 40%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
paddingRight: '30px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div><b>{props.title}</b>{props.subtitle && <em> ({props.subtitle})</em>}</div>
|
||||||
|
{props.description && <div><em>{props.description}</em></div>}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
role="cell"
|
||||||
|
style={{
|
||||||
|
flex: '1 0 calc(60% - 50px)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -37,6 +37,7 @@ import {useTranslation} from "react-i18next";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
||||||
|
import {Pref, PrefGroup} from "./Pref";
|
||||||
|
|
||||||
const Preferences = () => {
|
const Preferences = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -191,55 +192,6 @@ const DeleteAfter = () => {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
const PrefGroup = (props) => {
|
|
||||||
return (
|
|
||||||
<div role="table">
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
const Pref = (props) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="row"
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
marginTop: "10px",
|
|
||||||
marginBottom: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
role="cell"
|
|
||||||
id={props.labelId}
|
|
||||||
aria-label={props.title}
|
|
||||||
style={{
|
|
||||||
flex: '1 0 40%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
paddingRight: '30px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div><b>{props.title}</b></div>
|
|
||||||
{props.description && <div><em>{props.description}</em></div>}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
role="cell"
|
|
||||||
style={{
|
|
||||||
flex: '1 0 calc(60% - 50px)',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Users = () => {
|
const Users = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dialogKey, setDialogKey] = useState(0);
|
const [dialogKey, setDialogKey] = useState(0);
|
||||||
|
|
|
@ -30,27 +30,27 @@ const Signup = () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Signup] Signup for user ${user.username} failed`, e);
|
console.log(`[Signup] Signup for user ${user.username} failed`, e);
|
||||||
if ((e instanceof UsernameTakenError)) {
|
if ((e instanceof UsernameTakenError)) {
|
||||||
setError(t("Username {{username}} is already taken", { username: e.username }));
|
setError(t("signup_error_username_taken", { username: e.username }));
|
||||||
} else if ((e instanceof AccountCreateLimitReachedError)) {
|
} else if ((e instanceof AccountCreateLimitReachedError)) {
|
||||||
setError(t("Account creation limit reached"));
|
setError(t("signup_error_creation_limit_reached"));
|
||||||
} else if (e.message) {
|
} else if (e.message) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
} else {
|
} else {
|
||||||
setError(t("Unknown error. Check logs for details."))
|
setError(t("signup_error_unknown"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!config.enableSignup) {
|
if (!config.enableSignup) {
|
||||||
return (
|
return (
|
||||||
<AvatarBox>
|
<AvatarBox>
|
||||||
<Typography sx={{ typography: 'h6' }}>{t("Signup is disabled")}</Typography>
|
<Typography sx={{ typography: 'h6' }}>{t("signup_disabled")}</Typography>
|
||||||
</AvatarBox>
|
</AvatarBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<AvatarBox>
|
<AvatarBox>
|
||||||
<Typography sx={{ typography: 'h6' }}>
|
<Typography sx={{ typography: 'h6' }}>
|
||||||
{t("Create a ntfy account")}
|
{t("signup_title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}>
|
<Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1, maxWidth: 400}}>
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -58,7 +58,7 @@ const Signup = () => {
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
id="username"
|
id="username"
|
||||||
label="Username"
|
label={t("signup_form_username")}
|
||||||
name="username"
|
name="username"
|
||||||
value={username}
|
value={username}
|
||||||
onChange={ev => setUsername(ev.target.value.trim())}
|
onChange={ev => setUsername(ev.target.value.trim())}
|
||||||
|
@ -69,7 +69,7 @@ const Signup = () => {
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
name="password"
|
name="password"
|
||||||
label="Password"
|
label={t("signup_form_password")}
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
|
@ -81,7 +81,7 @@ const Signup = () => {
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
name="confirm-password"
|
name="confirm-password"
|
||||||
label="Confirm password"
|
label={t("signup_form_confirm_password")}
|
||||||
type="password"
|
type="password"
|
||||||
id="confirm-password"
|
id="confirm-password"
|
||||||
value={confirm}
|
value={confirm}
|
||||||
|
@ -95,7 +95,7 @@ const Signup = () => {
|
||||||
disabled={username === "" || password === "" || password !== confirm}
|
disabled={username === "" || password === "" || password !== confirm}
|
||||||
sx={{mt: 2, mb: 2}}
|
sx={{mt: 2, mb: 2}}
|
||||||
>
|
>
|
||||||
{t("Sign up")}
|
{t("signup_form_button_submit")}
|
||||||
</Button>
|
</Button>
|
||||||
{error &&
|
{error &&
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
@ -112,7 +112,7 @@ const Signup = () => {
|
||||||
{config.enableLogin &&
|
{config.enableLogin &&
|
||||||
<Typography sx={{mb: 4}}>
|
<Typography sx={{mb: 4}}>
|
||||||
<NavLink to={routes.login} variant="body1">
|
<NavLink to={routes.login} variant="body1">
|
||||||
{t("Already have an account? Sign in!")}
|
{t("signup_already_have_account")}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue