Translations

This commit is contained in:
binwiederhier 2022-12-29 08:20:53 -05:00
parent 9be8be49ef
commit 66cb35b5fc
7 changed files with 87 additions and 122 deletions

View file

@ -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:

View file

@ -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",

View file

@ -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;

View file

@ -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>

View 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>
);
};

View file

@ -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);

View file

@ -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>
} }