From 8fbca78772b8f929c02bfb106daaae226e06b27f Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 21:31:47 +0100 Subject: [PATCH 01/22] refactor: Improve i18n for login --- public/locales/en-US/common.json | 12 +++++ public/locales/en-US/login.json | 35 +++++++++++++++ public/locales/en-US/translation.json | 44 ------------------- .../ConfirmCodeForm/ConfirmCodeForm.tsx | 20 ++++----- .../LoginRoute/ConfirmFromDifferentDevice.tsx | 8 ++-- src/route-widgets/LoginRoute/EmailForm.tsx | 12 ++--- src/route-widgets/LoginRoute/OTPForm.tsx | 40 ++++++++++------- 7 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 public/locales/en-US/common.json create mode 100644 public/locales/en-US/login.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json new file mode 100644 index 0000000..c8704c6 --- /dev/null +++ b/public/locales/en-US/common.json @@ -0,0 +1,12 @@ +{ + "fields": { + "email": { + "label": "Email", + "placeholder": "johndoe@example.com" + }, + "2faCode": { + "label": "Code", + "placeholder": "123456" + } + } +} diff --git a/public/locales/en-US/login.json b/public/locales/en-US/login.json new file mode 100644 index 0000000..899bd47 --- /dev/null +++ b/public/locales/en-US/login.json @@ -0,0 +1,35 @@ +{ + "title": "Log in", + "forms": { + "email": { + "description": "We will send you a code to log in", + "continueActionLabel": "Send code" + }, + "confirmCode": { + "title": "You got mail!", + "description": "We sent you a code to your email. Enter it below to login", + "continueActionLabel": "Log in", + "allowLoginFromDifferentDevices": "Allow login from different devices", + "expiringSoonWarning": "Your code will expire in less than a minute.", + "fields": { + "code": { + "label": "Verification Code", + "errors": { + "invalidChars": "Invalid verification code" + } + } + } + }, + "confirmFromDifferentDevice": { + "title": "Login failed", + "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." + }, + "otp": { + "title": "Two-Factor Authentication", + "description": "Enter the code from your authenticator app", + "isUnavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again.", + "codesLostActionLabel": "I lost my codes", + "continueActionLabel": "Log in" + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 7c6ebbf..9148b99 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -26,50 +26,6 @@ "title": "Overview", "description": "Not much to see here, yet." }, - "LoginRoute": { - "forms": { - "email": { - "title": "Sign in", - "description": "We'll send you a verification code to your email.", - "continueAction": "Send Code", - "form": { - "email": { - "label": "Email", - "placeholder": "johndoe@example.com" - } - } - }, - "confirmCode": { - "title": "You got mail!", - "description": "We sent you a code to your email. Enter it below to login", - "continueAction": "Log in", - "allowLoginFromDifferentDevices": "Allow login from different devices", - "expiringSoon": "Your code will expire in less than a minute.", - "form": { - "code": { - "label": "Verification Code", - "errors": { - "invalidChars": "Invalid verification code" - } - } - } - }, - "confirmFromDifferentDevice": { - "title": "Login failed", - "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." - }, - "otp": { - "title": "Two-factor authentication", - "description": "Please enter the code from your authenticator app.", - "submit": "Log in", - "lost": "I lost my codes", - "code": { - "label": "Code" - }, - "unavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again." - } - } - }, "SignupRoute": { "forms": { "email": { diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx index 8cc1c31..94ffa78 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx @@ -55,7 +55,7 @@ export default function ConfirmCodeForm({ }: ConfirmCodeFormProps): ReactElement { const settings = useLoaderData() as ServerSettings const expirationTime = isDev ? 70 : settings.emailLoginExpirationInSeconds - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const requestDate = useMemo(() => new Date(), []) const [isExpiringSoon, setIsExpiringSoon] = useState(false) @@ -67,7 +67,7 @@ export default function ConfirmCodeForm({ .max(settings.emailLoginTokenLength) .test( "chars", - t("routes.LoginRoute.forms.confirmCode.form.code.errors.invalidChars") as string, + t("forms.confirmCode.fields.code.errors.invalidChars") as string, code => { if (!code) { return false @@ -78,7 +78,7 @@ export default function ConfirmCodeForm({ return code.split("").every(char => chars.includes(char)) }, ) - .label(t("routes.LoginRoute.forms.confirmCode.form.code.label")), + .label(t("forms.confirmCode.fields.code.label")), }) const {mutateAsync} = useMutation< @@ -165,7 +165,7 @@ export default function ConfirmCodeForm({ > - {t("routes.LoginRoute.forms.confirmCode.title")} + {t("forms.confirmCode.title")} @@ -175,7 +175,7 @@ export default function ConfirmCodeForm({ - {t("routes.LoginRoute.forms.confirmCode.description")} + {t("forms.confirmCode.description")} @@ -196,7 +196,7 @@ export default function ConfirmCodeForm({ } labelPlacement="end" label={t( - "routes.LoginRoute.forms.confirmCode.allowLoginFromDifferentDevices", + "forms.confirmCode.allowLoginFromDifferentDevices", )} /> @@ -206,9 +206,7 @@ export default function ConfirmCodeForm({ fullWidth name="code" id="code" - label={t( - "routes.LoginRoute.forms.confirmCode.form.code.label", - )} + label={t("forms.confirmCode.fields.code.label")} value={formik.values.code} onChange={event => { formik.setFieldValue( @@ -250,7 +248,7 @@ export default function ConfirmCodeForm({ type="submit" startIcon={} > - {t("routes.LoginRoute.forms.confirmCode.continueAction")} + {t("forms.confirmCode.continueActionLabel")} @@ -260,7 +258,7 @@ export default function ConfirmCodeForm({ - {t("routes.LoginRoute.forms.confirmCode.expiringSoon")} + {t("forms.confirmCode.expiringSoonWarning")} diff --git a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx index 5f0181e..c05bf8e 100644 --- a/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx +++ b/src/route-widgets/LoginRoute/ConfirmFromDifferentDevice.tsx @@ -22,7 +22,7 @@ export default function ConfirmFromDifferentDevice({ token, onConfirm, }: ConfirmFromDifferentDeviceProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login"]) const {mutate, isLoading, isError} = useMutation( () => verifyLoginWithEmail({ @@ -51,14 +51,12 @@ export default function ConfirmFromDifferentDevice({ - {t("routes.LoginRoute.forms.confirmFromDifferentDevice.title")} + {t("forms.confirmFromDifferentDevice.title")} - {t( - "routes.LoginRoute.forms.confirmFromDifferentDevice.description", - )} + {t("forms.confirmFromDifferentDevice.description")} diff --git a/src/route-widgets/LoginRoute/EmailForm.tsx b/src/route-widgets/LoginRoute/EmailForm.tsx index 69f07f6..5d87e7f 100644 --- a/src/route-widgets/LoginRoute/EmailForm.tsx +++ b/src/route-widgets/LoginRoute/EmailForm.tsx @@ -24,7 +24,7 @@ interface Form { } export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const $password = useRef(null) const schema = yup.object().shape({ @@ -32,7 +32,7 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro .string() .email() .required() - .label(t("routes.LoginRoute.forms.email.form.email.label")), + .label(t("fields.email.label", {ns: "common"})), }) const {mutateAsync} = useMutation(loginWithEmail, { @@ -63,9 +63,9 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro
@@ -77,7 +77,7 @@ export default function EmailForm({onLogin, email: preFilledEmail}: EmailFormPro name="email" id="email" label="Email" - placeholder={t("routes.LoginRoute.forms.email.form.email.placeholder")} + placeholder={t("fields.email.placeholder", {ns: "common"})} inputRef={$password} inputMode="email" value={formik.values.email} diff --git a/src/route-widgets/LoginRoute/OTPForm.tsx b/src/route-widgets/LoginRoute/OTPForm.tsx index 457c21d..a3c88c9 100644 --- a/src/route-widgets/LoginRoute/OTPForm.tsx +++ b/src/route-widgets/LoginRoute/OTPForm.tsx @@ -1,19 +1,21 @@ import * as yup from "yup" import {ReactElement} from "react" -import {useMutation} from "@tanstack/react-query" -import {ServerUser} from "~/server-types" -import {AxiosError} from "axios" -import {verifyOTP} from "~/apis" import {useTranslation} from "react-i18next" import {useFormik} from "formik" +import {BsPhone, BsShieldLockFill} from "react-icons/bs" +import {MdChevronRight} from "react-icons/md" +import {Link as RouterLink} from "react-router-dom" +import {AxiosError} from "axios" + +import {useMutation} from "@tanstack/react-query" +import {LoadingButton} from "@mui/lab" +import {Box, Button, Grid, InputAdornment, TextField, Typography} from "@mui/material" + +import {verifyOTP} from "~/apis" import {parseFastAPIError} from "~/utils" import {MultiStepFormElement} from "~/components" -import {Box, Button, Grid, InputAdornment, TextField, Typography} from "@mui/material" -import {BsPhone, BsShieldLockFill} from "react-icons/bs" -import {LoadingButton} from "@mui/lab" -import {MdChevronRight} from "react-icons/md" import {useErrorSuccessSnacks} from "~/hooks" -import {Link as RouterLink} from "react-router-dom" +import {ServerUser} from "~/server-types" interface Form { code: string @@ -30,7 +32,7 @@ export default function OTPForm({ onConfirm, onCodeUnavailable, }: OTPFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["login", "common"]) const {showError} = useErrorSuccessSnacks() const {mutateAsync} = useMutation( code => @@ -42,7 +44,7 @@ export default function OTPForm({ onSuccess: onConfirm, onError: error => { if (error.response?.status === 410 || error.response?.status === 404) { - showError(t("routes.LoginRoute.forms.otp.unavailable").toString()) + showError(t("forms.otp.isUnavailable").toString()) onCodeUnavailable() } }, @@ -50,7 +52,10 @@ export default function OTPForm({ ) const schema = yup.object().shape({ - code: yup.string().required().label(t("routes.LoginRoute.forms.otp.code.label")), + code: yup + .string() + .required() + .label(t("fields.2faCode.label", {ns: "common"})), }) const formik = useFormik({ @@ -81,7 +86,7 @@ export default function OTPForm({ > - {t("routes.LoginRoute.forms.otp.title")} + {t("forms.otp.title")} @@ -91,7 +96,7 @@ export default function OTPForm({ - {t("routes.LoginRoute.forms.otp.description")} + {t("forms.otp.description")} @@ -101,7 +106,8 @@ export default function OTPForm({ fullWidth name="code" id="code" - label={t("routes.LoginRoute.forms.otp.code.label")} + placeholder={t("fields.2faCode.placeholder", {ns: "common"})} + label={t("fields.2faCode.label", {ns: "common"})} value={formik.values.code} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -125,12 +131,12 @@ export default function OTPForm({ type="submit" startIcon={} > - {t("routes.LoginRoute.forms.otp.submit")} + {t("forms.otp.continueActionLabel")} From 1e6bf650b58abb6047a3de13f3f9027d8cb7648f Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 21:37:47 +0100 Subject: [PATCH 02/22] refactor: Improve i18n for recover 2fa --- public/locales/en-US/common.json | 3 +++ public/locales/en-US/recover-2fa.json | 10 ++++++++++ src/routes/Recover2FARoute.tsx | 23 +++++++++++++---------- 3 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 public/locales/en-US/recover-2fa.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index c8704c6..8f42f06 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -7,6 +7,9 @@ "2faCode": { "label": "Code", "placeholder": "123456" + }, + "recoveryCode": { + "label": "Recovery Code" } } } diff --git a/public/locales/en-US/recover-2fa.json b/public/locales/en-US/recover-2fa.json new file mode 100644 index 0000000..66897eb --- /dev/null +++ b/public/locales/en-US/recover-2fa.json @@ -0,0 +1,10 @@ +{ + "title": "Recover Two-Factor Authentication", + "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", + "continueActionLabel": "Disable 2FA", + "events": { + "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", + "canLogInNow": "Two-factor authentication has been disabled. You can log in now.", + "loggedIn": "Two-factor authentication has been disabled. You are logged in now." + } +} diff --git a/src/routes/Recover2FARoute.tsx b/src/routes/Recover2FARoute.tsx index 9f88c16..e0e2a1c 100644 --- a/src/routes/Recover2FARoute.tsx +++ b/src/routes/Recover2FARoute.tsx @@ -20,7 +20,7 @@ interface Form { } export default function Recover2FARoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["recover-2fa", "common"]) const {showError, showSuccess} = useErrorSuccessSnacks() const {login} = useContext(AuthContext) const navigate = useNavigate() @@ -29,17 +29,17 @@ export default function Recover2FARoute(): ReactElement { try { const user = await getMe() - showSuccess(t("routes.Recover2FARoute.loggedIn").toString()) + showSuccess(t("events.loggedIn").toString()) login(user) navigate("/aliases") } catch (error) { - showSuccess(t("routes.Recover2FARoute.canLoginNow").toString()) + showSuccess(t("events.canLogInNow").toString()) navigate("/auth/login") } }, onError: error => { if (error.response?.status == 401) { - showError(t("routes.Recover2FARoute.unauthorized").toString()) + showError(t("events.unauthorized").toString()) navigate("/auth/login") } else { showError(error) @@ -48,7 +48,10 @@ export default function Recover2FARoute(): ReactElement { }) const schema = yup.object().shape({ - recoveryCode: yup.string().required().label(t("routes.LoginRoute.forms.otp.code.label")), + recoveryCode: yup + .string() + .required() + .label(t("fields.recoveryCode.label", {ns: "common"})), }) const formik = useFormik({ @@ -76,7 +79,7 @@ export default function Recover2FARoute(): ReactElement { > - {t("routes.Recover2FARoute.title")} + {t("title")} @@ -86,17 +89,17 @@ export default function Recover2FARoute(): ReactElement { - {t("routes.Recover2FARoute.description")} + {t("description")} - + } > - {t("routes.Recover2FARoute.forms.submit")} + {t("continueActionLabel")} From 6370f367ed6e926036a661fbda2e01d10ce99f93 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 21:49:01 +0100 Subject: [PATCH 03/22] refactor: Improve i18n for signup --- public/locales/en-US/common.json | 13 +++++++++++- .../locales/en-US/relay-service-detected.json | 6 ++++++ public/locales/en-US/signup.json | 18 +++++++++++++++++ public/locales/en-US/translation.json | 11 ---------- .../EmailForm/DetectEmailAutofillService.tsx | 16 +++++++-------- .../SignupRoute/EmailForm/EmailForm.tsx | 20 +++++++++---------- .../SignupRoute/YouGotMail/YouGotMail.tsx | 16 +++++++-------- 7 files changed, 59 insertions(+), 41 deletions(-) create mode 100644 public/locales/en-US/relay-service-detected.json create mode 100644 public/locales/en-US/signup.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 8f42f06..a382f1b 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -2,7 +2,10 @@ "fields": { "email": { "label": "Email", - "placeholder": "johndoe@example.com" + "placeholder": "johndoe@example.com", + "errors": { + "disposable": "Disposable email addresses are not allowed." + } }, "2faCode": { "label": "Code", @@ -11,5 +14,13 @@ "recoveryCode": { "label": "Recovery Code" } + }, + "messages": { + "errors": { + "unknown": "An unknown error occurred." + } + }, + "general": { + "cancelLabel": "Cancel" } } diff --git a/public/locales/en-US/relay-service-detected.json b/public/locales/en-US/relay-service-detected.json new file mode 100644 index 0000000..02ec783 --- /dev/null +++ b/public/locales/en-US/relay-service-detected.json @@ -0,0 +1,6 @@ +{ + "title": "Email relay service detected", + "description": "We detected that you are using an email relay service to sign up. This KleckRelay instance does not support relaying to another email relay service. You can either choose a different instance or sign up with a different email address.", + "detectedExplanation": "Detected email relay:", + "closeActionLabel": "Got it" +} diff --git a/public/locales/en-US/signup.json b/public/locales/en-US/signup.json new file mode 100644 index 0000000..40e654c --- /dev/null +++ b/public/locales/en-US/signup.json @@ -0,0 +1,18 @@ +{ + "forms": { + "email": { + "title": "Sign up", + "description": "We only need your email and you are ready to go!", + "continueActionLabel": "Continue" + }, + "mailVerification": { + "title": "You got mail!", + "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", + "editEmail": { + "title": "Edit email address?", + "description": "Would you like to return to the previous step and edit your email address?", + "continueActionLabel": "Yes, edit email" + } + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 9148b99..087d829 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -28,17 +28,6 @@ }, "SignupRoute": { "forms": { - "email": { - "title": "Sign up", - "description": "We only need your email and you are ready to go!", - "continueAction": "Continue", - "form": { - "email": { - "label": "Email", - "placeholder": "johndoe@example.com" - } - } - }, "mailVerification": { "title": "You got mail!", "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", diff --git a/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx b/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx index dfb2e81..7cf4284 100644 --- a/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx +++ b/src/route-widgets/SignupRoute/EmailForm/DetectEmailAutofillService.tsx @@ -12,6 +12,7 @@ import { DialogTitle, Grid, } from "@mui/material" +import {useTranslation} from "react-i18next" export interface DetectEmailAutofillServiceProps { domains: string[] @@ -32,6 +33,8 @@ const STORAGE_KEY = "has-shown-email-autofill-service" export default function DetectEmailAutofillService({ domains, }: DetectEmailAutofillServiceProps): ReactElement { + const {t} = useTranslation("relay-service-detected") + const $hasDetected = useRef(false) const [type, setType] = useState(null) @@ -100,19 +103,14 @@ export default function DetectEmailAutofillService({ return ( setType(null)}> - Email relay service detected + {t("title")} - - We detected that you are using an email relay service to sign up. This - KleckRelay instance does not support relaying to another email relay - service. You can either choose a different instance or sign up with a - different email address. - + {t("description")} - Detected email relay: + {t("detectedExplanation")} {TYPE_NAME_MAP[type!]} @@ -121,7 +119,7 @@ export default function DetectEmailAutofillService({ diff --git a/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx b/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx index 27b544f..48d686b 100644 --- a/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx +++ b/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx @@ -27,7 +27,7 @@ interface Form { } export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["signup", "common"]) const $password = useRef(null) const schema = yup.object().shape({ @@ -35,7 +35,7 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R .string() .email() .required() - .label(t("routes.SignupRoute.forms.email.form.email.label")), + .label(t("fields.email.label", {ns: "common"})), }) const {mutateAsync} = useMutation(signup, { @@ -53,13 +53,13 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R if (isDisposable) { setErrors({ - email: "Disposable email addresses are not allowed", + email: t("fields.email.errors.disposable", {ns: "common"}), }) return } } catch { setErrors({ - detail: "An error occurred", + detail: t("messages.errors.unknown", {ns: "common"}), }) return } @@ -82,9 +82,9 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R @@ -95,10 +95,8 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R autoFocus name="email" id="email" - label={t("routes.SignupRoute.forms.email.form.email.label")} - placeholder={t( - "routes.SignupRoute.forms.email.form.email.placeholder", - )} + label={t("fields.email.label", {ns: "common"})} + placeholder={t("fields.email.placeholder", {ns: "common"})} inputMode="email" inputRef={$password} value={formik.values.email} diff --git a/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx b/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx index c01de46..bc94bbd 100644 --- a/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx +++ b/src/route-widgets/SignupRoute/YouGotMail/YouGotMail.tsx @@ -23,7 +23,7 @@ export interface YouGotMailProps { } export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["signup", "common"]) const [askToEditEmail, setAskToEditEmail] = useState(false) @@ -43,12 +43,12 @@ export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactEle > - {t("routes.SignupRoute.forms.mailVerification.title")} + {t("forms.mailVerification.title")} - {t("routes.SignupRoute.forms.mailVerification.description")} + {t("forms.mailVerification.description")} @@ -72,20 +72,18 @@ export default function YouGotMail({email, onGoBack}: YouGotMailProps): ReactEle - - {t("routes.SignupRoute.forms.mailVerification.editEmail.title")} - + {t("forms.mailVerification.editEmail.title")} - {t("routes.SignupRoute.forms.mailVerification.editEmail.description")} + {t("forms.mailVerification.editEmail.description")} From e4fdcecbe64d27ad732a84413d84e6e1c5cd2c94 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 21:51:41 +0100 Subject: [PATCH 04/22] refactor: Improve i18n for verify email --- public/locales/en-US/translation.json | 13 ------------- public/locales/en-US/verify-email.json | 7 +++++++ src/routes/VerifyEmailRoute.tsx | 10 +++++----- 3 files changed, 12 insertions(+), 18 deletions(-) create mode 100644 public/locales/en-US/verify-email.json diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 087d829..2c68792 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -26,19 +26,6 @@ "title": "Overview", "description": "Not much to see here, yet." }, - "SignupRoute": { - "forms": { - "mailVerification": { - "title": "You got mail!", - "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", - "editEmail": { - "title": "Edit email address?", - "description": "Would you like to return to the previous step and edit your email address?", - "continueAction": "Yes, edit email" - } - } - } - }, "VerifyEmailRoute": { "title": "Verify your email", "isLoading": "Verifying your email...", diff --git a/public/locales/en-US/verify-email.json b/public/locales/en-US/verify-email.json new file mode 100644 index 0000000..98a10ef --- /dev/null +++ b/public/locales/en-US/verify-email.json @@ -0,0 +1,7 @@ +{ + "title": "Verify your email", + "isLoading": "We are verifying your email address...", + "errors": { + "invalid": "The verification link is invalid or has expired." + } +} diff --git a/src/routes/VerifyEmailRoute.tsx b/src/routes/VerifyEmailRoute.tsx index 5641ecc..7c28982 100644 --- a/src/routes/VerifyEmailRoute.tsx +++ b/src/routes/VerifyEmailRoute.tsx @@ -17,9 +17,9 @@ import {AuthContext} from "~/components" const emailSchema = yup.string().email() export default function VerifyEmailRoute(): ReactElement { + const {t} = useTranslation("verify-email") const theme = useTheme() const navigate = useNavigate() - const {t} = useTranslation() const {login} = useContext(AuthContext) const [_, setEmail] = useLocalStorage("signup-form-state-email", "") @@ -32,7 +32,7 @@ export default function VerifyEmailRoute(): ReactElement { const tokenSchema = yup .string() .length(serverSettings.emailVerificationLength) - .test("token", t("routes.VerifyEmailRoute.errors.code.invalid") as string, token => { + .test("token", t("errors.invalid") as string, token => { if (!token) { return false } @@ -69,13 +69,13 @@ export default function VerifyEmailRoute(): ReactElement { > - {t("routes.VerifyEmailRoute.title")} + {t("title")} {loading ? ( - {t("routes.VerifyEmailRoute.isLoading")} + {t("isLoading")} ) : ( @@ -85,7 +85,7 @@ export default function VerifyEmailRoute(): ReactElement { - {t("routes.VerifyEmailRoute.isCodeInvalid")} + {t("errors.invalid")} From 7229078778f3f59fb10daf129d6bb543b95758d3 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 21:52:57 +0100 Subject: [PATCH 05/22] refactor: Improve i18n for logout --- public/locales/en-US/logout.json | 4 ++++ public/locales/en-US/translation.json | 23 ----------------------- src/routes/LogoutRoute.tsx | 8 +++----- 3 files changed, 7 insertions(+), 28 deletions(-) create mode 100644 public/locales/en-US/logout.json diff --git a/public/locales/en-US/logout.json b/public/locales/en-US/logout.json new file mode 100644 index 0000000..ce11cd5 --- /dev/null +++ b/public/locales/en-US/logout.json @@ -0,0 +1,4 @@ +{ + "title": "Log out", + "description": "We are logging you out..." +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 2c68792..1f9d057 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -26,29 +26,6 @@ "title": "Overview", "description": "Not much to see here, yet." }, - "VerifyEmailRoute": { - "title": "Verify your email", - "isLoading": "Verifying your email...", - "isCodeInvalid": "Sorry, but this verification code is invalid.", - "errors": { - "code": { - "invalid": "The verification code is invalid." - } - } - }, - "Recover2FARoute": { - "title": "Recover Two-Factor-Authentication", - "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", - "forms": { - "recoveryCode": { - "label": "Recovery Code" - }, - "submit": "Disable 2FA" - }, - "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", - "canLoginNow": "Two-factor authentication has been disabled. You can now log in.", - "loggedIn": "Two-factor authentication has been disabled. You are now logged in." - }, "CompleteAccountRoute": { "forms": { "generateReports": { diff --git a/src/routes/LogoutRoute.tsx b/src/routes/LogoutRoute.tsx index 0d48c58..29021bc 100644 --- a/src/routes/LogoutRoute.tsx +++ b/src/routes/LogoutRoute.tsx @@ -8,7 +8,7 @@ import {useNavigateToNext} from "~/hooks" import {AuthContext} from "~/components" export default function LogoutRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("logout") const navigateToNext = useNavigateToNext("/auth/login") const {logout} = useContext(AuthContext) @@ -23,16 +23,14 @@ export default function LogoutRoute(): ReactElement { - {t("routes.LogoutRoute.title")} + {t("title")} - - {t("routes.LogoutRoute.description")} - + {t("description")} From e5d1ee73c693c771ef86fa5ad1d4189a44990a54 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 22:53:23 +0100 Subject: [PATCH 06/22] refactor: Improve i18n for complete account --- public/locales/en-US/common.json | 16 ++++++- public/locales/en-US/complete-account.json | 12 ++++++ public/locales/en-US/translation.json | 6 --- src/components/widgets/SimpleForm.tsx | 7 +++- .../GenerateEmailReportsForm.tsx | 18 +++----- .../CompleteAccountRoute/PasswordForm.tsx | 42 ++++++++----------- src/routes/Root.tsx | 17 ++++---- 7 files changed, 64 insertions(+), 54 deletions(-) create mode 100644 public/locales/en-US/complete-account.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index a382f1b..cd42469 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -13,6 +13,17 @@ }, "recoveryCode": { "label": "Recovery Code" + }, + "password": { + "label": "Password", + "placeholder": "********" + }, + "passwordConfirmation": { + "label": "Confirm Password", + "placeholder": "********", + "errors": { + "mismatch": "Passwords do not match." + } } }, "messages": { @@ -21,6 +32,9 @@ } }, "general": { - "cancelLabel": "Cancel" + "cancelLabel": "Cancel", + "yesLabel": "Yes", + "noLabel": "No", + "continueLabel": "Continue" } } diff --git a/public/locales/en-US/complete-account.json b/public/locales/en-US/complete-account.json new file mode 100644 index 0000000..eb7f1ca --- /dev/null +++ b/public/locales/en-US/complete-account.json @@ -0,0 +1,12 @@ +{ + "forms": { + "askForGeneration": { + "title": "Generate Email Reports?", + "description": "Would you like to create fully encrypted email reports for your mails? Only you will be able to access them. Not even we can decrypt them." + }, + "enterPassword": { + "title": "Set up your password", + "description": "Please enter a safe password so that we can encrypt your data." + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 1f9d057..48e310c 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -28,12 +28,6 @@ }, "CompleteAccountRoute": { "forms": { - "generateReports": { - "title": "Generate Email Reports?", - "description": "Would you like to create fully encrypted email reports for your mails? Only you will be able to access them. Not even we can decrypt them.", - "continueAction": "Yes", - "cancelAction": "No" - }, "password": { "title": "Set up your password", "description": "Please enter a safe password so that we can encrypt your data.", diff --git a/src/components/widgets/SimpleForm.tsx b/src/components/widgets/SimpleForm.tsx index 36fc510..bd295c6 100644 --- a/src/components/widgets/SimpleForm.tsx +++ b/src/components/widgets/SimpleForm.tsx @@ -5,12 +5,13 @@ import React, {ReactElement, useEffect, useState} from "react" import {Alert, Button, Grid, Snackbar, Typography, TypographyProps} from "@mui/material" import {LoadingButton} from "@mui/lab" import {OverrideProps} from "@mui/types" +import {useTranslation} from "react-i18next" export interface SimpleFormProps { title: string description: string - continueActionLabel: string + continueActionLabel?: string children?: ReactElement[] cancelActionLabel?: string isSubmitting?: boolean @@ -32,6 +33,8 @@ export default function SimpleForm({ titleComponent = "h1", isSubmitting = false, }: SimpleFormProps): ReactElement { + const {t} = useTranslation("common") + const [showSnackbar, setShowSnackbar] = useState(false) useEffect(() => { @@ -101,7 +104,7 @@ export default function SimpleForm({ type="submit" startIcon={} > - {continueActionLabel} + {continueActionLabel || t("general.continueLabel")} diff --git a/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx b/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx index 291ba68..814bb0e 100644 --- a/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm.tsx @@ -17,7 +17,7 @@ export default function GenerateEmailReportsForm({ onNo, onYes, }: GenerateEmailReportsFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["complete-account", "common"]) return ( @@ -37,9 +37,7 @@ export default function GenerateEmailReportsForm({ - {t( - "routes.CompleteAccountRoute.forms.generateReports.title", - )} + {t("forms.askForGeneration.title")} @@ -49,9 +47,7 @@ export default function GenerateEmailReportsForm({ - {t( - "routes.CompleteAccountRoute.forms.generateReports.description", - )} + {t("forms.askForGeneration.description")} @@ -62,9 +58,7 @@ export default function GenerateEmailReportsForm({ @@ -73,9 +67,7 @@ export default function GenerateEmailReportsForm({ color="primary" onClick={onYes} > - {t( - "routes.CompleteAccountRoute.forms.generateReports.continueAction", - )} + {t("general.yesLabel", {ns: "common"})} diff --git a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx index 6c90a8a..6c0a929 100644 --- a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx @@ -11,7 +11,7 @@ import {Box, InputAdornment} from "@mui/material" import {useMutation} from "@tanstack/react-query" import {AuthContext, PasswordField, SimpleForm} from "~/components" -import {setupEncryptionForUser} from "~/utils" +import {parseFastAPIError, setupEncryptionForUser} from "~/utils" import {useExtensionHandler, useNavigateToNext, useSystemPreferredTheme, useUser} from "~/hooks" import {ServerSettings, ServerUser} from "~/server-types" import {UpdateAccountData, updateAccount} from "~/apis" @@ -27,7 +27,7 @@ interface Form { } export default function PasswordForm({onDone}: PasswordFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["complete-account", "common"]) const user = useUser() const theme = useSystemPreferredTheme() const serverSettings = useLoaderData() as ServerSettings @@ -36,17 +36,18 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement const $password = useRef(null) const $passwordConfirmation = useRef(null) const schema = yup.object().shape({ - password: yup.string().required(), + password: yup + .string() + .required() + .label(t("fields.password.label", {ns: "common"})), passwordConfirmation: yup .string() .required() .oneOf( [yup.ref("password"), null], - t( - "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.mustMatchHelperText", - ) as string, + t("fields.passwordConfirmation.errors.mismatch", {ns: "common"}) as string, ) - .label(t("routes.CompleteAccountRoute.forms.password.form.passwordConfirm.label")), + .label(t("fields.passwordConfirmation.label", {ns: "common"})), }) const {_setEncryptionPassword, login} = useContext(AuthContext) @@ -89,7 +90,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement }, ) } catch (error) { - setErrors({detail: t("general.defaultError")}) + setErrors(parseFastAPIError(error as AxiosError)) } }, }) @@ -109,11 +110,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement {[ @@ -123,12 +121,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement autoFocus id="password" name="password" - label={t( - "routes.CompleteAccountRoute.forms.password.form.password.label", - )} - placeholder={t( - "routes.CompleteAccountRoute.forms.password.form.password.placeholder", - )} + label={t("fields.password.label", {ns: "common"})} + placeholder={t("fields.password.placeholder", {ns: "common"})} autoComplete="new-password" value={formik.values.password} onChange={formik.handleChange} @@ -148,12 +142,10 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement fullWidth id="passwordConfirmation" name="passwordConfirmation" - label={t( - "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.label", - )} - placeholder={t( - "routes.CompleteAccountRoute.forms.password.form.passwordConfirm.placeholder", - )} + label={t("fields.passwordConfirmation.label", {ns: "common"})} + placeholder={t("fields.passwordConfirmation.placeholder", { + ns: "common", + })} value={formik.values.passwordConfirmation} onChange={formik.handleChange} disabled={formik.isSubmitting} diff --git a/src/routes/Root.tsx b/src/routes/Root.tsx index f560233..0c4b05c 100644 --- a/src/routes/Root.tsx +++ b/src/routes/Root.tsx @@ -1,15 +1,18 @@ import {Outlet} from "react-router-dom" -import React, {ReactElement} from "react" +import React, {ReactElement, Suspense} from "react" import {AppLoadingScreen, AuthContextProvider, ExtensionSignalHandler} from "~/components" +import LoadingPage from "~/components/widgets/LoadingPage" export default function RootRoute(): ReactElement { return ( - - - - - - + }> + + + + + + + ) } From 648a222aefd071725c3741d2f1b71ac5584464d9 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 4 Mar 2023 22:54:52 +0100 Subject: [PATCH 07/22] refactor: Improve i18n for complete account --- public/locales/en-US/complete-account.json | 4 ++++ public/locales/en-US/translation.json | 16 ---------------- src/routes/CompleteAccountRoute.tsx | 8 +++----- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/public/locales/en-US/complete-account.json b/public/locales/en-US/complete-account.json index eb7f1ca..8bc7792 100644 --- a/public/locales/en-US/complete-account.json +++ b/public/locales/en-US/complete-account.json @@ -8,5 +8,9 @@ "title": "Set up your password", "description": "Please enter a safe password so that we can encrypt your data." } + }, + "alreadyCompleted": { + "title": "Encryption already enabled", + "description": "You already have encryption enabled. Changing passwords is currently not supported." } } diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 48e310c..bf0d437 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -28,22 +28,6 @@ }, "CompleteAccountRoute": { "forms": { - "password": { - "title": "Set up your password", - "description": "Please enter a safe password so that we can encrypt your data.", - "continueAction": "Continue", - "form": { - "password": { - "label": "Password", - "placeholder": "********" - }, - "passwordConfirm": { - "label": "Confirm Password", - "placeholder": "Re-enter your password", - "mustMatchHelperText": "Passwords do not match." - } - } - }, "available": { "title": "Encryption already enabled", "description": "You already have encryption enabled. Changing passwords is currently not supported." diff --git a/src/routes/CompleteAccountRoute.tsx b/src/routes/CompleteAccountRoute.tsx index a361946..e4db796 100644 --- a/src/routes/CompleteAccountRoute.tsx +++ b/src/routes/CompleteAccountRoute.tsx @@ -9,7 +9,7 @@ import GenerateEmailReportsForm from "~/route-widgets/CompleteAccountRoute/Gener import PasswordForm from "~/route-widgets/CompleteAccountRoute/PasswordForm" export default function CompleteAccountRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("complete-account") const {encryptionStatus} = useContext(AuthContext) const navigateToNext = useNavigateToNext() @@ -50,13 +50,11 @@ export default function CompleteAccountRoute(): ReactElement { > - {t("routes.CompleteAccountRoute.forms.available.title")} + {t("alreadyCompleted.title")} - - {t("routes.CompleteAccountRoute.forms.available.description")} - + {t("alreadyCompleted.description")} From c3c2453013ebc6320cfca07eb07f62210a37cdba Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 09:03:37 +0100 Subject: [PATCH 08/22] refactor: Improve i18n for aliases --- public/locales/en-US/aliases.json | 31 +++++++++++ public/locales/en-US/common.json | 18 ++++++- public/locales/en-US/translation.json | 54 ------------------- src/components/widgets/NoSearchResults.tsx | 8 ++- src/init-i18n.ts | 1 + .../AliasesRoute/CreateAliasButton.tsx | 10 ++-- .../AliasesRoute/CustomAliasDialog.tsx | 22 +++----- .../AliasesRoute/EmptyStateScreen.tsx | 8 ++- src/routes/AliasesRoute.tsx | 37 +++++-------- 9 files changed, 80 insertions(+), 109 deletions(-) create mode 100644 public/locales/en-US/aliases.json diff --git a/public/locales/en-US/aliases.json b/public/locales/en-US/aliases.json new file mode 100644 index 0000000..b08ad11 --- /dev/null +++ b/public/locales/en-US/aliases.json @@ -0,0 +1,31 @@ +{ + "title": "Aliases", + "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", + "emptyState": { + "title": "Welcome to your Aliases!", + "description": "Create your first Alias to get started." + }, + "pageActions": { + "search": { + "placeholder": "Search for names" + }, + "searchFilter": { + "active": "Active", + "inactive": "Inactive" + }, + "typeFilter": { + "custom": "Custom made", + "random": "Randomly generated" + } + }, + "actions": { + "createRandomAlias": { + "title": "Create Random Alias" + }, + "createCustomAlias": { + "title": "Create Custom Alias", + "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", + "continueActionLabel": "Create Alias" + } + } +} diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index cd42469..820fe8e 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -24,11 +24,23 @@ "errors": { "mismatch": "Passwords do not match." } + }, + "customAliasLocal": { + "label": "Address", + "placeholder": "awesome-fish" + }, + "search": { + "label": "Search" } }, "messages": { "errors": { - "unknown": "An unknown error occurred." + "unknown": "An unknown error occurred.", + "copyFailed": "Copying to clipboard did not work. Please copy the text manually." + }, + "alias": { + "addressCopied": "Address has been copied to your clipboard!", + "created": "Alias has been created successfully!" } }, "general": { @@ -36,5 +48,9 @@ "yesLabel": "Yes", "noLabel": "No", "continueLabel": "Continue" + }, + "noSearchResults": { + "title": "Nothing found", + "description": "We couldn't find anything for your search query. Try again with a different query." } } diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index bf0d437..3c80795 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -26,52 +26,6 @@ "title": "Overview", "description": "Not much to see here, yet." }, - "CompleteAccountRoute": { - "forms": { - "available": { - "title": "Encryption already enabled", - "description": "You already have encryption enabled. Changing passwords is currently not supported." - } - } - }, - "AliasesRoute": { - "title": "Aliases", - "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", - "emptyState": { - "title": "Welcome to your Aliases!", - "description": "Create your first Alias to get started." - }, - "pageActions": { - "search": { - "label": "Search", - "placeholder": "Search for names" - }, - "searchFilter": { - "active": "Active", - "inactive": "Inactive" - }, - "typeFilter": { - "custom": "Custom made", - "random": "Randomly generated" - } - }, - "actions": { - "createRandomAlias": { - "label": "Create Random Alias" - }, - "createCustomAlias": { - "label": "Create Custom Alias", - "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", - "continueAction": "Create Alias", - "form": { - "address": { - "label": "Address", - "placeholder": "awesome-fish" - } - } - } - } - }, "AliasDetailRoute": { "title": "Alias Details", "sections": { @@ -236,10 +190,6 @@ } } }, - "LogoutRoute": { - "title": "Log out", - "description": "We are logging you out..." - }, "ReservedAliasesRoute": { "title": "Reserved Aliases", "pageActions": { @@ -448,10 +398,6 @@ "random": "This is a randomly generated alias", "custom": "This is a custom-made alias" }, - "NoSearchResults": { - "title": "Nothing found", - "description": "We couldn't find anything for your search query. Try again with a different query." - }, "LockNavigationContextProvider": { "title": "Are you sure you want to leave?", "description": "You have unsaved changes. If you leave, your changes will be lost.", diff --git a/src/components/widgets/NoSearchResults.tsx b/src/components/widgets/NoSearchResults.tsx index 12a0ca9..1c09fc4 100644 --- a/src/components/widgets/NoSearchResults.tsx +++ b/src/components/widgets/NoSearchResults.tsx @@ -5,20 +5,18 @@ import {FaQuestion} from "react-icons/fa" import {Grid, Typography} from "@mui/material" export default function NoSearchResults(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") return ( - {t("components.NoSearchResults.title")} + {t("noSearchResults.title")} - - {t("components.NoSearchResults.description")} - + {t("noSearchResults.description")} ) diff --git a/src/init-i18n.ts b/src/init-i18n.ts index f2275df..bdb19b0 100644 --- a/src/init-i18n.ts +++ b/src/init-i18n.ts @@ -15,6 +15,7 @@ i18n.use(HttpApi) .init({ debug: isDev, fallbackLng: "en-US", + load: "all", backend: { loadPath: "/locales/{{lng}}/{{ns}}.json", }, diff --git a/src/route-widgets/AliasesRoute/CreateAliasButton.tsx b/src/route-widgets/AliasesRoute/CreateAliasButton.tsx index 4423ae3..ba14833 100644 --- a/src/route-widgets/AliasesRoute/CreateAliasButton.tsx +++ b/src/route-widgets/AliasesRoute/CreateAliasButton.tsx @@ -26,7 +26,7 @@ import {AuthContext, EncryptionStatus} from "~/components" import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog" export function CreateAliasButton(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const {_encryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) @@ -54,7 +54,7 @@ export function CreateAliasButton(): ReactElement { { onError: showError, onSuccess: async alias => { - showSuccess(t("relations.alias.mutations.success.aliasCreation")) + showSuccess(t("messages.alias.created", {ns: "common"})) await queryClient.invalidateQueries({ queryKey: ["get_aliases"], @@ -82,7 +82,7 @@ export function CreateAliasButton(): ReactElement { }) } > - {t("routes.AliasesRoute.actions.createRandomAlias.label")} + {t("actions.createRandomAlias.title")} diff --git a/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx b/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx index 5c2ed45..84907c4 100644 --- a/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/AliasesRoute/EmptyStateScreen.tsx @@ -5,7 +5,7 @@ import {FaMask} from "react-icons/fa" import {Container, Grid, Typography} from "@mui/material" export default function EmptyStateScreen(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("aliases") return ( @@ -20,16 +20,14 @@ export default function EmptyStateScreen(): ReactElement { > - {t("routes.AliasesRoute.emptyState.title")} + {t("emptyState.title")} - - {t("routes.AliasesRoute.emptyState.description")} - + {t("emptyState.description")} diff --git a/src/routes/AliasesRoute.tsx b/src/routes/AliasesRoute.tsx index 8e74b1d..7bffdfe 100644 --- a/src/routes/AliasesRoute.tsx +++ b/src/routes/AliasesRoute.tsx @@ -36,7 +36,7 @@ enum TypeFilter { } export default function AliasesRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const [searchValue, setSearchValue] = useState("") const [queryValue, setQueryValue] = useState("") @@ -131,7 +131,7 @@ export default function AliasesRoute(): ReactElement { return ( @@ -146,10 +146,8 @@ export default function AliasesRoute(): ReactElement { setQueryValue(event.target.value) }) }} - label={t("routes.AliasesRoute.pageActions.search.label")} - placeholder={t( - "routes.AliasesRoute.pageActions.search.placeholder", - )} + label={t("fields.search.label", {ns: "common"})} + placeholder={t("pageActions.search.placeholder")} id="search" InputProps={{ startAdornment: ( @@ -166,9 +164,7 @@ export default function AliasesRoute(): ReactElement { + - - {t("routes.AliasesRoute.isInCopyMode")} + {t("isInCopyMode")} From 76b3fc640dedfa342a055bc3d695004d6955ed01 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 09:35:02 +0100 Subject: [PATCH 09/22] refactor: Improve i18n for aliases --- public/locales/en-US/alias-notes.json | 30 ++++++ public/locales/en-US/aliases.json | 53 +++++++++++ public/locales/en-US/common.json | 9 +- public/locales/en-US/translation.json | 94 ------------------- src/components/widgets/AliasTypeIndicator.tsx | 7 +- src/constants/enum-mappings.ts | 4 +- .../AliasDetailRoute/AliasAddress.tsx | 9 +- .../AliasDetailRoute/AliasNotesForm.tsx | 48 +++------- .../AliasDetailRoute/AliasPreferencesForm.tsx | 39 ++++---- .../ChangeAliasActivationStatusSwitch.tsx | 6 +- src/routes/AliasDetailRoute.tsx | 17 ++-- 11 files changed, 140 insertions(+), 176 deletions(-) create mode 100644 public/locales/en-US/alias-notes.json diff --git a/public/locales/en-US/alias-notes.json b/public/locales/en-US/alias-notes.json new file mode 100644 index 0000000..70a2bd5 --- /dev/null +++ b/public/locales/en-US/alias-notes.json @@ -0,0 +1,30 @@ +{ + "title": "Notes", + "form": { + "createdAt": { + "label": "Created at", + "empty": "Unavailable" + }, + "creationContext": { + "label": "Creation Context", + "values": { + "web": "Created on this instance", + "extension": "Created in the extension", + "extension-inline": "Created using the extension" + } + }, + "createdOn": { + "label": "Created on" + }, + "personalNotes": { + "label": "Personal Notes", + "helperText": "You can enter personal notes for this alias here. Notes are encrypted." + }, + "websites": { + "label": "Websites", + "emptyText": "You haven't used this alias on any site yet.", + "placeholder": "https://example.com", + "helperText": "Add a website to this alias. Used to autofill." + } + } +} diff --git a/public/locales/en-US/aliases.json b/public/locales/en-US/aliases.json index b08ad11..832a2f7 100644 --- a/public/locales/en-US/aliases.json +++ b/public/locales/en-US/aliases.json @@ -1,5 +1,6 @@ { "title": "Aliases", + "detailsTitle": "Alias Details", "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", "emptyState": { "title": "Welcome to your Aliases!", @@ -26,6 +27,58 @@ "title": "Create Custom Alias", "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", "continueActionLabel": "Create Alias" + }, + "delete": { + "label": "Delete Alias", + "description": "Are you sure you want to delete this alias?", + "continueActionLabel": "Delete Alias" + } + }, + "aliasTypeExplanation": { + "random": "This is a randomly generated alias", + "custom": "This is a custom-made alias" + }, + "settings": { + "title": "Settings", + "description": "These settings apply to this alias only. You can either set a value manually or refer to your default settings. Note that this does change in behavior. When you set a value to refer to your default setting, the alias will always use the latest value. So when you change your default setting, the alias will automatically use the new value.", + "continueActionLabel": "Save Settings", + "fields": { + "removeTrackers": { + "label": "Remove Trackers", + "helperText": "Remove single-pixel image trackers as well as url trackers." + }, + "createMailReport": { + "label": "Create Mail Reports", + "helperText": "Create reports of emails sent to aliases. Reports are end-to-end encrypted. Only you can access them." + }, + "proxyImages": { + "label": "Proxy Images", + "helperText": "Proxies images in your emails through this KleckRelay instance. This adds an extra layer of privacy. Images are loaded immediately after we receive the email. They then will be stored for some time (cache time). During that time, the image will be served from us. This means the sender has no idea you have opened the mail. After the cache time, the image is loaded from the sender, but it will be forwarded by us. This means the sender will not be able to access your IP address nor your browser data." + }, + "imageProxyFormat": { + "label": "Image File Type", + "values": { + "jpeg": "JPEG", + "png": "PNG", + "webp": "WEBP" + } + }, + "proxyUserAgent": { + "label": "Proxy User Agent", + "helperText": "An User Agent is a identifier each browser and email client sends when retrieving files, such as images. You can specify here what user agent you would like to be used when we forward it. User Agents are kept up-to-date.", + "values": { + "apple-mail": "Apple Mail", + "google-mail": "Google Mail", + "outlook-windows": "Outlook / Windows", + "outlook-macos": "Outlook / MacOS", + "firefox": "Firefox Browser", + "chrome": "Chrome Browser" + } + }, + "expandUrlShorteners": { + "label": "Expand URL Shorteners", + "helperText": "Expand shortened URLs (for example bit.ly) to their original URL. This way those services can't track you." + } } } } diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 820fe8e..542e80e 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -40,14 +40,19 @@ }, "alias": { "addressCopied": "Address has been copied to your clipboard!", - "created": "Alias has been created successfully!" + "created": "Alias has been created successfully!", + "deleted": "Alias has been deleted!", + "updated": "Alias has been updated successfully!", + "changedToEnabled": "Alias has been enabled", + "changedToDisabled": "Alias has been disabled" } }, "general": { "cancelLabel": "Cancel", "yesLabel": "Yes", "noLabel": "No", - "continueLabel": "Continue" + "continueLabel": "Continue", + "unavailableValue": "Unavailable" }, "noSearchResults": { "title": "Nothing found", diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 3c80795..b1128ab 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -26,57 +26,6 @@ "title": "Overview", "description": "Not much to see here, yet." }, - "AliasDetailRoute": { - "title": "Alias Details", - "sections": { - "settings": { - "title": "Settings", - "description": "These settings apply to this alias only. You can either set a value manually or refer to your default settings. Note that this does change in behavior. When you set a value to refer to your default setting, the alias will always use the latest value. So when you change your default setting, the alias will automatically use the new value." - }, - "notes": { - "title": "Notes", - "form": { - "createdAt": { - "label": "Created at", - "empty": "Unavailable" - }, - "creationContext": { - "label": "Creation Context", - "web": { - "label": "Created on this instance" - }, - "extension": { - "label": "Created in the extension" - }, - "extension-inline": { - "label": "Created using the extension" - } - }, - "createdOn": { - "label": "Created on" - }, - "personalNotes": { - "label": "Personal Notes", - "empty": "-", - "helperText": "You can enter personal notes for this alias here. Notes are encrypted." - }, - "websites": { - "label": "Websites", - "emptyText": "You haven't used this alias on any site yet.", - "placeholder": "https://example.com", - "helperText": "Add a website to this alias. Used to autofill." - } - } - } - }, - "actions": { - "delete": { - "label": "Delete Alias", - "description": "Are you sure you want to delete this alias?", - "continueAction": "Delete Alias" - } - } - }, "ReportsRoute": { "title": "Reports", "emptyState": { @@ -394,10 +343,6 @@ "ErrorLoadingDataMessage": { "tryAgain": "Try Again" }, - "AliasTypeIndicator": { - "random": "This is a randomly generated alias", - "custom": "This is a custom-made alias" - }, "LockNavigationContextProvider": { "title": "Are you sure you want to leave?", "description": "You have unsaved changes. If you leave, your changes will be lost.", @@ -439,45 +384,6 @@ "addressCopiedToClipboard": "Address has been copied to your clipboard!", "aliasDeleted": "Alias has been deleted!" } - }, - "settings": { - "removeTrackers": { - "label": "Remove Trackers", - "helperText": "Remove single-pixel image trackers as well as url trackers." - }, - "createMailReports": { - "label": "Create Mail Reports", - "helperText": "Create reports of emails sent to aliases. Reports are end-to-end encrypted. Only you can access them." - }, - "proxyImages": { - "label": "Proxy Images", - "helperText": "Proxies images in your emails through this KleckRelay instance. This adds an extra layer of privacy. Images are loaded immediately after we receive the email. They then will be stored for some time (cache time). During that time, the image will be served from us. This means the sender has no idea you have opened the mail. After the cache time, the image is loaded from the sender, but it will be forwarded by us. This means the sender will not be able to access your IP address nor your browser data." - }, - "imageProxyFormat": { - "label": "Image File Type", - "enumTexts": { - "jpeg": "JPEG", - "png": "PNG", - "webp": "WEBP" - } - }, - "proxyUserAgent": { - "label": "Proxy User Agent", - "helperText": "An User Agent is a identifier each browser and email client sends when retrieving files, such as images. You can specify here what user agent you would like to be used when we forward it. User Agents are kept up-to-date.", - "enumTexts": { - "apple-mail": "Apple Mail", - "google-mail": "Google Mail", - "outlook-windows": "Outlook / Windows", - "outlook-macos": "Outlook / MacOS", - "firefox": "Firefox Browser", - "chrome": "Chrome Browser" - } - }, - "expandUrlShorteners": { - "label": "Expand URL Shorteners", - "helperText": "Expand shortened URLs (for example bit.ly) to their original URL. This way those services can't track you." - }, - "saveAction": "Save Settings" } }, "report": { diff --git a/src/components/widgets/AliasTypeIndicator.tsx b/src/components/widgets/AliasTypeIndicator.tsx index 4e7028e..4a4b709 100644 --- a/src/components/widgets/AliasTypeIndicator.tsx +++ b/src/components/widgets/AliasTypeIndicator.tsx @@ -16,13 +16,10 @@ export const ALIAS_TYPE_ICON_MAP: Record = { [AliasType.CUSTOM]: , } -const ALIAS_TYPE_TOOLTIP_MAP = createEnumMapFromTranslation( - "components.AliasTypeIndicator", - AliasType, -) +const ALIAS_TYPE_TOOLTIP_MAP = createEnumMapFromTranslation("aliasTypeExplanation", AliasType) export default function AliasTypeIndicator({type}: AliasTypeIndicatorProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("aliases") return ( // @ts-ignore diff --git a/src/constants/enum-mappings.ts b/src/constants/enum-mappings.ts index ecf2070..1f8a448 100644 --- a/src/constants/enum-mappings.ts +++ b/src/constants/enum-mappings.ts @@ -2,10 +2,10 @@ import {ImageProxyFormatType, ProxyUserAgentType} from "~/server-types" import {createEnumMapFromTranslation} from "~/utils" export const IMAGE_PROXY_FORMAT_TYPE_NAME_MAP = createEnumMapFromTranslation( - "relations.alias.settings.imageProxyFormat.enumTexts", + "settings.fields.imageProxyFormat.values", ImageProxyFormatType, ) export const PROXY_USER_AGENT_TYPE_NAME_MAP = createEnumMapFromTranslation( - "relations.alias.settings.proxyUserAgent.enumTexts", + "settings.fields.proxyUserAgent.values", ProxyUserAgentType, ) diff --git a/src/route-widgets/AliasDetailRoute/AliasAddress.tsx b/src/route-widgets/AliasDetailRoute/AliasAddress.tsx index 4c33840..163a1ee 100644 --- a/src/route-widgets/AliasDetailRoute/AliasAddress.tsx +++ b/src/route-widgets/AliasDetailRoute/AliasAddress.tsx @@ -12,7 +12,7 @@ export interface AliasAddressProps { } export default function AliasAddress({address}: AliasAddressProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const [{value, error}, copyToClipboard] = useCopyToClipboard() return ( @@ -26,11 +26,8 @@ export default function AliasAddress({address}: AliasAddressProps): ReactElement > {address} - - + + ) } diff --git a/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx b/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx index 94680e3..599bfb5 100644 --- a/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx +++ b/src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx @@ -64,21 +64,19 @@ const CREATION_CONTEXT_ICON_MAP: Record - {t("routes.AliasDetailRoute.sections.notes.title")} + {t("title")} @@ -211,11 +209,9 @@ export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProp {notes.data.createdAt && ( } - label={t( - "routes.AliasDetailRoute.sections.notes.form.createdAt.label", - )} + label={t("form.createdAt.label")} > {notes.data.createdAt && ( @@ -231,13 +227,11 @@ export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProp {t( - `routes.AliasDetailRoute.sections.notes.form.creationContext.${notes.data.creationContext}.label`, + `form.creationContext.values.${notes.data.creationContext}`, )} @@ -247,9 +241,7 @@ export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProp } - label={t( - "routes.AliasDetailRoute.sections.notes.form.createdOn.label", - )} + label={t("form.createdOn.label")} > )} - + {isInEditMode ? ( {isInEditMode ? ( diff --git a/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx b/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx index e08ca66..9af8ce2 100644 --- a/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx +++ b/src/route-widgets/AliasDetailRoute/AliasPreferencesForm.tsx @@ -44,7 +44,7 @@ export default function AliasPreferencesForm({ alias, queryKey, }: AliasPreferencesFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const {_decryptUsingMasterPassword} = useContext(AuthContext) @@ -52,31 +52,34 @@ export default function AliasPreferencesForm({ removeTrackers: yup .mixed() .oneOf([true, false, null]) - .label(t("relations.alias.settings.removeTrackers.label")), + .label(t("settings.fields.removeTrackers.label")), createMailReport: yup .mixed() .oneOf([true, false, null]) - .label(t("relations.alias.settings.createMailReports.label")), - proxyImages: yup.mixed().oneOf([true, false, null]), + .label(t("settings.fields.createMailReport.label")), + proxyImages: yup + .mixed() + .oneOf([true, false, null]) + .label(t("settings.fields.proxyImages.label")), imageProxyFormat: yup .mixed() .oneOf([null, ...Object.values(ImageProxyFormatType)]) - .label(t("relations.alias.settings.imageProxyFormat.label")), + .label(t("settings.fields.imageProxyFormat.label")), proxyUserAgent: yup .mixed() .oneOf([null, ...Object.values(ProxyUserAgentType)]) - .label(t("relations.alias.settings.proxyUserAgent.label")), + .label(t("settings.fields.proxyUserAgent.label")), expandUrlShorteners: yup .mixed() .oneOf([true, false, null]) - .label(t("relations.alias.settings.expandUrlShorteners.label")), + .label(t("settings.fields.expandUrlShorteners.label")), }) const {mutateAsync} = useMutation( data => updateAlias(alias.id, data), { onSuccess: async newAlias => { - showSuccess(t("relations.alias.mutations.success.aliasUpdated")) + showSuccess(t("messages.alias.updated", {ns: "common"})) ;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes( newAlias.encryptedNotes, _decryptUsingMasterPassword, @@ -126,7 +129,7 @@ export default function AliasPreferencesForm({ } name="removeTrackers" @@ -134,9 +137,7 @@ export default function AliasPreferencesForm({ } name="createMailReport" @@ -146,9 +147,7 @@ export default function AliasPreferencesForm({ } name="proxyImages" @@ -160,7 +159,7 @@ export default function AliasPreferencesForm({ } @@ -173,7 +172,7 @@ export default function AliasPreferencesForm({ } > - {t("relations.alias.settings.saveAction")} + {t("settings.continueActionLabel")} - - {t("routes.AliasDetailRoute.sections.settings.description")} - + {t("settings.description")} diff --git a/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx b/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx index 5c7b749..f538d6c 100644 --- a/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx +++ b/src/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch.tsx @@ -24,7 +24,7 @@ export default function ChangeAliasActivationStatusSwitch({ isActive, queryKey, }: ChangeAliasActivationStatusSwitchProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const {showError, showSuccess} = useErrorSuccessSnacks() const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) @@ -83,8 +83,8 @@ export default function ChangeAliasActivationStatusSwitch({ showSuccess( isActive - ? t("relations.alias.mutations.success.aliasChangedToDisabled") - : t("relations.alias.mutations.success.aliasChangedToEnabled"), + ? t("messages.alias.changedToDisabled") + : t("messages.alias.changedToEnabled"), ) } catch {} }} diff --git a/src/routes/AliasDetailRoute.tsx b/src/routes/AliasDetailRoute.tsx index ce85126..24804c8 100644 --- a/src/routes/AliasDetailRoute.tsx +++ b/src/routes/AliasDetailRoute.tsx @@ -25,7 +25,7 @@ import ChangeAliasActivationStatusSwitch from "~/route-widgets/AliasDetailRoute/ import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes" export default function AliasDetailRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["aliases", "common"]) const serverSettings = useLoaderData() as ServerSettings const {id: aliasID} = useParams() const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext) @@ -46,17 +46,17 @@ export default function AliasDetailRoute(): ReactElement { return ( deleteAlias(aliasID!)} - label={t("routes.AliasDetailRoute.actions.delete.label")} - description={t("routes.AliasDetailRoute.actions.delete.description")} - continueLabel={t("routes.AliasDetailRoute.actions.delete.continueAction")} + label={t("actions.delete.label")} + description={t("actions.delete.description")} + continueLabel={t("actions.delete.continueActionLabel")} navigateTo={"/aliases"} - successMessage={t("relations.alias.mutations.success.aliasDeleted")} + successMessage={t("messages.alias.deleted", {ns: "common"})} /> ) } @@ -97,10 +97,7 @@ export default function AliasDetailRoute(): ReactElement { )} , - + , ]} From 2b6364478b856dc365f1280ef8b9b8c1e8d4a5bf Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 09:45:41 +0100 Subject: [PATCH 10/22] refactor: Improve i18n for settings alias preferences --- public/locales/en-US/common.json | 3 +- .../locales/en-US/settings-preferences.json | 5 ++ public/locales/en-US/settings.json | 7 +++ public/locales/en-US/translation.json | 8 --- src/routes/SettingsAliasPreferencesRoute.tsx | 52 ++++++++----------- src/routes/SettingsRoute.tsx | 8 +-- 6 files changed, 41 insertions(+), 42 deletions(-) create mode 100644 public/locales/en-US/settings-preferences.json create mode 100644 public/locales/en-US/settings.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 542e80e..c80c4b9 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -52,7 +52,8 @@ "yesLabel": "Yes", "noLabel": "No", "continueLabel": "Continue", - "unavailableValue": "Unavailable" + "unavailableValue": "Unavailable", + "experimentalFeatureExplanation": "This is an experimental feature." }, "noSearchResults": { "title": "Nothing found", diff --git a/public/locales/en-US/settings-preferences.json b/public/locales/en-US/settings-preferences.json new file mode 100644 index 0000000..fe30a4e --- /dev/null +++ b/public/locales/en-US/settings-preferences.json @@ -0,0 +1,5 @@ +{ + "title": "Alias Preferences", + "description": "Select default values for your aliases. This only affects aliases you haven't set a custom value for.", + "continueActionLabel": "Save preferences" +} diff --git a/public/locales/en-US/settings.json b/public/locales/en-US/settings.json new file mode 100644 index 0000000..7ab1855 --- /dev/null +++ b/public/locales/en-US/settings.json @@ -0,0 +1,7 @@ +{ + "title": "Settings", + "actions": { + "enable2fa": "Two-Factor-Authentication", + "aliasPreferences": "Alias Preferences" + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index b1128ab..d148f42 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -90,18 +90,10 @@ } }, "SettingsRoute": { - "title": "Settings", "forms": { "aliasPreferences": { - "title": "Alias Preferences", - "description": "Select default values for your aliases. This only affects aliases you haven't set a custom value for.", - "saveAction": "Save preferences" } }, - "actions": { - "enable2fa": "Two-Factor-Authentication", - "aliasPreferences": "Alias Preferences" - }, "2fa": { "title": "Two-Factor-Authentication", "alreadyEnabled": "You have successfully enabled 2FA!", diff --git a/src/routes/SettingsAliasPreferencesRoute.tsx b/src/routes/SettingsAliasPreferencesRoute.tsx index e8dd998..eb1609b 100644 --- a/src/routes/SettingsAliasPreferencesRoute.tsx +++ b/src/routes/SettingsAliasPreferencesRoute.tsx @@ -44,30 +44,26 @@ interface Form { } export default function SettingsAliasPreferencesRoute(): ReactElement { + const {t} = useTranslation(["aliases", "settings-preferences", "common"]) const {_updateUser} = useContext(AuthContext) const user = useUser() const {showError, showSuccess} = useErrorSuccessSnacks() - const {t} = useTranslation() const schema = yup.object().shape({ - removeTrackers: yup.boolean().label(t("relations.alias.settings.removeTrackers.label")), - createMailReport: yup - .boolean() - .label(t("relations.alias.settings.createMailReports.label")), - proxyImages: yup.boolean().label(t("relations.alias.settings.proxyImages.label")), + removeTrackers: yup.boolean().label(t("settings.fields.removeTrackers.label")), + createMailReport: yup.boolean().label(t("settings.fields.createMailReport.label")), + proxyImages: yup.boolean().label(t("settings.fields.proxyImages.label")), imageProxyFormat: yup .mixed() .oneOf(Object.values(ImageProxyFormatType)) .required() - .label(t("relations.alias.settings.imageProxyFormat.label")), + .label(t("settings.fields.imageProxyFormat.label")), proxyUserAgent: yup .mixed() .oneOf(Object.values(ProxyUserAgentType)) .required() - .label(t("relations.alias.settings.proxyUserAgent.label")), - expandUrlShorteners: yup - .boolean() - .label(t("relations.alias.settings.expandUrlShorteners.label")), + .label(t("settings.fields.proxyUserAgent.label")), + expandUrlShorteners: yup.boolean().label(t("settings.fields.expandUrlShorteners.label")), }) const {mutateAsync} = useMutation( @@ -121,8 +117,8 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { return (
} labelPlacement="start" - label={t("relations.alias.settings.removeTrackers.label")} + label={t("settings.fields.removeTrackers.label")} /> {(formik.touched.createMailReport && formik.errors.createMailReport) || - t("relations.alias.settings.removeTrackers.helperText")} + t("settings.fields.removeTrackers.helperText")} @@ -174,7 +170,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { /> } labelPlacement="start" - label={t("relations.alias.settings.createMailReports.label")} + label={t("settings.fields.createMailReport.label")} /> {(formik.touched.createMailReport && formik.errors.createMailReport) || - t("relations.alias.settings.createMailReports.helperText")} + t("settings.fields.createMailReport.helperText")} @@ -202,7 +198,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { /> } labelPlacement="start" - label={t("relations.alias.settings.proxyImages.label")} + label={t("settings.fields.proxyImages.label")} /> {(formik.touched.proxyImages && formik.errors.proxyImages) || - t("relations.alias.settings.proxyImages.helperText")} + t("settings.fields.proxyImages.helperText")} - {t("general.experimentalFeature")} + {t("general.experimentalFeatureExplanation", {ns: "common"})} @@ -242,9 +238,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { }} name="imageProxyFormat" id="imageProxyFormat" - label={t( - "relations.alias.settings.imageProxyFormat.label", - )} + label={t("settings.fields.imageProxyFormat.label")} value={formik.values.imageProxyFormat} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -286,7 +280,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { select name="proxyUserAgent" id="proxyUserAgent" - label={t("relations.alias.settings.proxyUserAgent.label")} + label={t("settings.fields.proxyUserAgent.label")} value={formik.values.proxyUserAgent} onChange={formik.handleChange} disabled={formik.isSubmitting} @@ -312,7 +306,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { )} > {(formik.touched.proxyUserAgent && formik.errors.proxyUserAgent) || - t("relations.alias.settings.proxyUserAgent.helperText")} + t("settings.fields.proxyUserAgent.helperText")} @@ -330,7 +324,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { /> } labelPlacement="start" - label={t("relations.alias.settings.expandUrlShorteners.label")} + label={t("settings.fields.expandUrlShorteners.label")} /> {(formik.touched.expandUrlShorteners && formik.errors.expandUrlShorteners) || - t("relations.alias.settings.expandUrlShorteners.helperText")} + t("settings.fields.expandUrlShorteners.helperText")} - {t("general.experimentalFeature")} + {t("general.experimentalFeatureExplanation", {ns: "common"})} @@ -357,7 +351,7 @@ export default function SettingsAliasPreferencesRoute(): ReactElement { type="submit" startIcon={} > - {t("routes.SettingsRoute.forms.aliasPreferences.saveAction")} + {t("continueActionLabel", {ns: "settings-preferences"})}
diff --git a/src/routes/SettingsRoute.tsx b/src/routes/SettingsRoute.tsx index f3e7b27..bb0b06e 100644 --- a/src/routes/SettingsRoute.tsx +++ b/src/routes/SettingsRoute.tsx @@ -9,22 +9,22 @@ import {List, ListItemButton, ListItemIcon, ListItemText} from "@mui/material" import {SimplePageBuilder} from "~/components" export default function SettingsRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings") return ( - + - + - + From 47053cdb50701e80735e848fc464bb1ccdee72c8 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 10:04:39 +0100 Subject: [PATCH 11/22] refactor: Improve i18n for settings 2fa --- public/locales/en-US/common.json | 5 ++- public/locales/en-US/settings-2fa.json | 33 +++++++++++++++++++ public/locales/en-US/translation.json | 4 --- .../Settings2FARoute/Setup2FA.tsx | 8 ++--- .../Settings2FARoute/VerifyOTPForm.tsx | 25 +++++++------- src/routes/Settings2FARoute.tsx | 8 ++--- 6 files changed, 56 insertions(+), 27 deletions(-) create mode 100644 public/locales/en-US/settings-2fa.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index c80c4b9..2aa8ffc 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -9,7 +9,10 @@ }, "2faCode": { "label": "Code", - "placeholder": "123456" + "placeholder": "123456", + "errors": { + "shouldOnlyBeDigits": "The code should only contain digits." + } }, "recoveryCode": { "label": "Recovery Code" diff --git a/public/locales/en-US/settings-2fa.json b/public/locales/en-US/settings-2fa.json new file mode 100644 index 0000000..1c2333e --- /dev/null +++ b/public/locales/en-US/settings-2fa.json @@ -0,0 +1,33 @@ +{ + "title": "Two-Factor-Authentication", + "alreadyEnabled": "You have successfully enabled 2FA!", + "setup": { + "description": "Enable 2FA to add an extra layer of security to your account. Each time you log in, you will need to enter a code generated from your authenticator app. This makes it harder for an attacker to hack into your account as they would need to have access to your phone.", + "setupLabel": "Enable 2FA", + "continueActionLabel": "Enable 2FA", + "codeExpired": "The verification time for your current Two-Factor-Authentication code has expired. A new code has been generated.", + "recoveryCodes": { + "title": "Note down your recovery codes", + "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", + "continueActionLabel": "I have noted down my recovery codes" + }, + "success": "You have successfully enabled 2FA!" + }, + "delete": { + "label": "Disable 2FA", + "steps": { + "askType": { + "code": "I have my 2FA code", + "recoveryCode": "I have a recovery code" + }, + "askCode": { + "label": "Code" + }, + "askRecoveryCode": { + "label": "Recovery Code" + } + }, + "submit": "Disable 2FA", + "success": "You have successfully disabled 2FA!" + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index d148f42..2b285be 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -90,10 +90,6 @@ } }, "SettingsRoute": { - "forms": { - "aliasPreferences": { - } - }, "2fa": { "title": "Two-Factor-Authentication", "alreadyEnabled": "You have successfully enabled 2FA!", diff --git a/src/route-widgets/Settings2FARoute/Setup2FA.tsx b/src/route-widgets/Settings2FARoute/Setup2FA.tsx index 327e772..2b9a311 100644 --- a/src/route-widgets/Settings2FARoute/Setup2FA.tsx +++ b/src/route-widgets/Settings2FARoute/Setup2FA.tsx @@ -18,7 +18,7 @@ export interface Setup2FAProps { } export default function Setup2FA({onSuccess}: Setup2FAProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings-2fa") const {showError} = useErrorSuccessSnacks() const { @@ -33,9 +33,7 @@ export default function Setup2FA({onSuccess}: Setup2FAProps): ReactElement { return ( - - {t("routes.SettingsRoute.2fa.setup.description")} - + {t("setup.description")} {secret ? ( @@ -55,7 +53,7 @@ export default function Setup2FA({onSuccess}: Setup2FAProps): ReactElement { variant="contained" startIcon={} > - {t("routes.SettingsRoute.2fa.setup.setupLabel")} + {t("setup.setupLabel")} )} diff --git a/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx b/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx index 87082e1..9b395dd 100644 --- a/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx +++ b/src/route-widgets/Settings2FARoute/VerifyOTPForm.tsx @@ -41,7 +41,7 @@ export default function Settings2FARoute({ onRecreateRequired, secret, }: VerifyOTPFormProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["settings-2fa", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const user = useUser() const theme = useTheme() @@ -52,16 +52,19 @@ export default function Settings2FARoute({ code: yup .string() .required() + .matches( + /^[0-9]+$/, + t("fields.2faCode.errors.shouldOnlyBeDigits", {ns: "common"}) as string, + ) .length(6) - .matches(/^[0-9]+$/, t("routes.SettingsRoute.2fa.setup.code.onlyDigits").toString()) - .label(t("routes.SettingsRoute.2fa.setup.code.label")), + .label(t("fields.2faCode.label", {ns: "common"})), }) const {mutateAsync} = useMutation(verify2FASetup, { onSuccess: () => setShowRecoveryCodes(true), onError: error => { if (error.response?.status === 409 || error.response?.status === 410) { - showError(t("routes.SettingsRoute.2fa.setup.expired").toString()) + showError(t("setup.codeExpired").toString()) onRecreateRequired() } else { showError(error) @@ -107,7 +110,7 @@ export default function Settings2FARoute({ error={!!formik.errors.code} helperText={formik.errors.code} name="code" - label={t("routes.SettingsRoute.2fa.setup.code.label")} + label={t("fields.2faCode.label", {ns: "common"})} disabled={formik.isSubmitting} InputProps={{ startAdornment: ( @@ -125,7 +128,7 @@ export default function Settings2FARoute({ variant="contained" loading={formik.isSubmitting} > - {t("routes.SettingsRoute.2fa.setup.submit")} + {t("setup.continueActionLabel")} @@ -133,7 +136,7 @@ export default function Settings2FARoute({ - {t("routes.SettingsRoute.2fa.setup.recoveryCodes.title")} + {t("setup.recoveryCodes.title")} {code}

))} - - {t("routes.SettingsRoute.2fa.setup.recoveryCodes.description")} - + {t("setup.recoveryCodes.description")}
diff --git a/src/routes/Settings2FARoute.tsx b/src/routes/Settings2FARoute.tsx index 068dc3c..4c71a7b 100644 --- a/src/routes/Settings2FARoute.tsx +++ b/src/routes/Settings2FARoute.tsx @@ -11,20 +11,18 @@ import Setup2FA from "~/route-widgets/Settings2FARoute/Setup2FA" import getHas2FAEnabled from "~/apis/get-has-2fa-enabled" export default function Settings2FARoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings-2fa") const queryKey = ["get_2fa_enabled"] const query = useQuery(queryKey, getHas2FAEnabled) return ( - + query={query}> {has2FAEnabled => has2FAEnabled ? ( - - {t("routes.SettingsRoute.2fa.alreadyEnabled")} - + {t("alreadyEnabled")} From 67ceddc3f7ca14e1d07fdc1b85bc7b2d05e1f3bd Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 10:08:41 +0100 Subject: [PATCH 12/22] refactor: Improve i18n for settings 2fa --- public/locales/en-US/settings-2fa.json | 4 +- public/locales/en-US/translation.json | 38 ------------------- .../Settings2FARoute/Delete2FA.tsx | 16 ++++---- 3 files changed, 10 insertions(+), 48 deletions(-) diff --git a/public/locales/en-US/settings-2fa.json b/public/locales/en-US/settings-2fa.json index 1c2333e..fb4e637 100644 --- a/public/locales/en-US/settings-2fa.json +++ b/public/locales/en-US/settings-2fa.json @@ -14,7 +14,7 @@ "success": "You have successfully enabled 2FA!" }, "delete": { - "label": "Disable 2FA", + "title": "Disable 2FA", "steps": { "askType": { "code": "I have my 2FA code", @@ -27,7 +27,7 @@ "label": "Recovery Code" } }, - "submit": "Disable 2FA", + "continueActionLabel": "Disable 2FA", "success": "You have successfully disabled 2FA!" } } diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 2b285be..5610130 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -89,44 +89,6 @@ } } }, - "SettingsRoute": { - "2fa": { - "title": "Two-Factor-Authentication", - "alreadyEnabled": "You have successfully enabled 2FA!", - "setup": { - "description": "Enable 2FA to add an extra layer of security to your account. Each time you log in, you will need to enter a code generated from your authenticator app. This makes it harder for an attacker to hack into your account as they would need to have access to your phone.", - "setupLabel": "Enable 2FA", - "code": { - "label": "Code", - "description": "Enter the code generated by your authenticator app.", - "onlyDigits": "The code can only contain digits." - }, - "submit": "Enable 2FA", - "expired": "The verification time for your current Two-Factor-Authentication code has expired. A new code has been generated.", - "recoveryCodes": { - "title": "Note down your recovery codes", - "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", - "submit": "I have noted down my recovery codes" - }, - "success": "You have successfully enabled 2FA!" - }, - "delete": { - "showAction": "Disable 2FA", - "askType": { - "code": "I have my 2FA code", - "recoveryCode": "I have a recovery code" - }, - "askCode": { - "label": "Code" - }, - "askRecoveryCode": { - "label": "Recovery Code" - }, - "submit": "Disable 2FA", - "success": "You have successfully disabled 2FA!" - } - } - }, "ReservedAliasesRoute": { "title": "Reserved Aliases", "pageActions": { diff --git a/src/route-widgets/Settings2FARoute/Delete2FA.tsx b/src/route-widgets/Settings2FARoute/Delete2FA.tsx index eb0c2a0..146554d 100644 --- a/src/route-widgets/Settings2FARoute/Delete2FA.tsx +++ b/src/route-widgets/Settings2FARoute/Delete2FA.tsx @@ -17,7 +17,7 @@ export interface Delete2FAProps { } export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("settings-2fa") const {showSuccess, showError} = useErrorSuccessSnacks() const {mutate} = useMutation(delete2FA, { onSuccess: () => { @@ -36,7 +36,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { case "showAction": return ( ) @@ -45,7 +45,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { @@ -53,7 +53,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { onClick={() => setView("askRecoveryCode")} startIcon={} > - {t("routes.SettingsRoute.2fa.delete.askType.recoveryCode")} + {t("delete.steps.askType.recoveryCode")} @@ -65,7 +65,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { setValue(e.target.value)} /> @@ -76,7 +76,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { variant="contained" startIcon={} > - {t("routes.SettingsRoute.2fa.delete.submit")} + {t("delete.continueActionLabel")} @@ -88,7 +88,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { setValue(e.target.value)} /> @@ -99,7 +99,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { variant="contained" startIcon={} > - {t("routes.SettingsRoute.2fa.delete.submit")} + {t("delete.continueActionLabel")} From 908d1b81a46a3c274bb9cc18fb1a18ac6917b687 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 10:19:41 +0100 Subject: [PATCH 13/22] refactor: Improve i18n for admin global settings --- .../locales/en-US/admin-global-settings.json | 71 ++++++++ public/locales/en-US/admin.json | 7 + public/locales/en-US/common.json | 4 +- public/locales/en-US/translation.json | 92 ---------- .../GlobalSettingsRoute/SettingsDisabled.tsx | 9 +- .../GlobalSettingsRoute/SettingsForm.tsx | 172 +++++------------- src/routes/AdminRoute.tsx | 8 +- 7 files changed, 138 insertions(+), 225 deletions(-) create mode 100644 public/locales/en-US/admin-global-settings.json create mode 100644 public/locales/en-US/admin.json diff --git a/public/locales/en-US/admin-global-settings.json b/public/locales/en-US/admin-global-settings.json new file mode 100644 index 0000000..155606c --- /dev/null +++ b/public/locales/en-US/admin-global-settings.json @@ -0,0 +1,71 @@ +{ + "title": "Global Settings", + "description": "Configure global settings for your instance.", + "updatedSuccessfullyMessage": "Settings have been saved successfully!", + "randomAliasesPreview": { + "title": "Random aliases will look like this", + "helperText": "This is just a preview. Those are not real aliases." + }, + "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", + "resetLabel": "Reset to defaults", + "disabled": { + "title": "Global settings are disabled", + "description": "Global settings have been disabled. You can enable them in the configuration file." + }, + "fields": { + "randomEmailIdMinLength": { + "label": "Minimum random alias ID length", + "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." + }, + "randomEmailIdChars": { + "label": "Random alias character pool", + "description": "Characters that are used to generate random emails." + }, + "randomEmailLengthIncreaseOnPercentage": { + "label": "Percentage of used aliases", + "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." + }, + "customEmailSuffixLength": { + "label": "Custom email suffix length", + "description": "The length of the custom email suffix." + }, + "customEmailSuffixChars": { + "label": "Custom email suffix character pool", + "description": "Characters that are used to generate custom email suffixes." + }, + "imageProxyStorageLifeTimeInHours": { + "label": "Image proxy storage lifetime", + "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", + "unit_one": "hour", + "unit_other": "hours" + }, + "enableImageProxy": { + "label": "Enable image proxy", + "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." + }, + "enableImageProxyStorage": { + "label": "Enable image proxy storage", + "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." + }, + "userEmailEnableDisposableEmails": { + "label": "Enable disposable emails for new accounts", + "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." + }, + "userEmailEnableOtherRelays": { + "label": "Enable other relays for new accounts", + "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." + }, + "allowStatistics": { + "label": "Allow statistics", + "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." + }, + "allowAliasDeletion": { + "label": "Allow alias deletion", + "description": "If enabled, users will be able to delete their aliases." + }, + "maxAliasesPerUser": { + "label": "Maximum aliases per user", + "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." + } + } +} diff --git a/public/locales/en-US/admin.json b/public/locales/en-US/admin.json new file mode 100644 index 0000000..7eb0c5d --- /dev/null +++ b/public/locales/en-US/admin.json @@ -0,0 +1,7 @@ +{ + "title": "Site configuration", + "routes": { + "reservedAliases": "Reserved Aliases", + "settings": "Global Settings" + } +} diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 2aa8ffc..ebc82d5 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -56,7 +56,9 @@ "noLabel": "No", "continueLabel": "Continue", "unavailableValue": "Unavailable", - "experimentalFeatureExplanation": "This is an experimental feature." + "experimentalFeatureExplanation": "This is an experimental feature.", + "saveLabel": "Save", + "resetLabel": "Reset" }, "noSearchResults": { "title": "Nothing found", diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 5610130..bcfb7d6 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -129,98 +129,6 @@ "reservedAliases": "Reserved Aliases", "settings": "Global Settings" }, - "forms": { - "reservedAliases": { - "title": "Reserved Aliases", - "description": "Define what alias should forward to whom.", - "saveAction": "Create Alias", - "fields": { - "local": { - "label": "Local" - }, - "users": { - "label": "Users", - "me": "{{email}} (Me)" - } - }, - "explanation": { - "step1": "User from outside", - "step2": "Sends mail to", - "step4": "KleckRelay forwards to" - } - }, - "settings": { - "randomEmailIdMinLength": { - "label": "Minimum random alias ID length", - "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." - }, - "randomEmailIdChars": { - "label": "Random alias character pool", - "description": "Characters that are used to generate random emails." - }, - "randomEmailLengthIncreaseOnPercentage": { - "label": "Percentage of used aliases", - "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." - }, - "customEmailSuffixLength": { - "label": "Custom email suffix length", - "description": "The length of the custom email suffix." - }, - "customEmailSuffixChars": { - "label": "Custom email suffix character pool", - "description": "Characters that are used to generate custom email suffixes." - }, - "imageProxyStorageLifeTimeInHours": { - "label": "Image proxy storage lifetime", - "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", - "unit_one": "hour", - "unit_other": "hours" - }, - "enableImageProxy": { - "label": "Enable image proxy", - "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." - }, - "enableImageProxyStorage": { - "label": "Enable image proxy storage", - "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." - }, - "userEmailEnableDisposableEmails": { - "label": "Enable disposable emails for new accounts", - "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." - }, - "userEmailEnableOtherRelays": { - "label": "Enable other relays for new accounts", - "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." - }, - "allowStatistics": { - "label": "Allow statistics", - "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." - }, - "allowAliasDeletion": { - "label": "Allow alias deletion", - "description": "If enabled, users will be able to delete their aliases." - }, - "maxAliasesPerUser": { - "label": "Maximum aliases per user", - "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." - } - } - }, - "settings": { - "title": "Global Settings", - "description": "Configure global settings for your instance.", - "successMessage": "Settings have been saved successfully!", - "randomAliasesPreview": { - "title": "Random aliases will look like this", - "helperText": "This is just a preview. Those are not real aliases." - }, - "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", - "resetLabel": "Reset to defaults", - "disabled": { - "title": "Global settings are disabled", - "description": "Global settings have been disabled. You can enable them in the configuration file." - } - }, "reservedAlias": { "actions": { "delete": { diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx index 79a15e3..692f9f6 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsDisabled.tsx @@ -5,8 +5,7 @@ import {useTranslation} from "react-i18next" import {Container, Grid, Typography} from "@mui/material" export default function SettingsDisabled(): ReactElement { - console.log("asdas") - const {t} = useTranslation() + const {t} = useTranslation("admin-global-settings") return ( @@ -21,16 +20,14 @@ export default function SettingsDisabled(): ReactElement { > - {t("routes.AdminRoute.settings.disabled.title")} + {t("disabled.title")} - - {t("routes.AdminRoute.settings.disabled.description")} - + {t("disabled.description")} diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx index 81f91d7..948867a 100644 --- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx +++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx @@ -44,7 +44,7 @@ const DEFAULT_POOLS = createPool({ }) export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - const {t} = useTranslation() + const {t} = useTranslation(["admin-global-settings", "common"]) const {showSuccess, showError} = useErrorSuccessSnacks() const validationSchema = yup.object().shape({ @@ -52,50 +52,33 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { .number() .min(1) .max(1_023) - .label(t("routes.AdminRoute.forms.settings.randomEmailIdMinLength.label")), - randomEmailIdChars: yup - .string() - .label(t("routes.AdminRoute.forms.settings.randomEmailIdChars.label")), + .label(t("fields.randomEmailIdMinLength.label")), + randomEmailIdChars: yup.string().label(t("fields.randomEmailIdChars.label")), randomEmailLengthIncreaseOnPercentage: yup .number() .min(0) .max(1) - .label( - t("routes.AdminRoute.forms.settings.randomEmailLengthIncreaseOnPercentage.label"), - ), + .label(t("fields.randomEmailLengthIncreaseOnPercentage.label")), imageProxyStorageLifeTimeInHours: yup .number() - .label(t("routes.AdminRoute.forms.settings.imageProxyStorageLifeTimeInHours.label")), + .label(t("fields.imageProxyStorageLifeTimeInHours.label")), customEmailSuffixLength: yup .number() .min(1) .max(1_023) - .label(t("routes.AdminRoute.forms.settings.customEmailSuffixLength-label")), - customEmailSuffixChars: yup - .string() - .label(t("routes.AdminRoute.forms.settings.customEmailSuffixChars.label")), + .label(t("fields.customEmailSuffixLength.label")), + customEmailSuffixChars: yup.string().label(t("fields.customEmailSuffixChars.label")), userEmailEnableDisposableEmails: yup .boolean() - .label(t("routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label")), + .label(t("fields.userEmailEnableDisposableEmails.label")), userEmailEnableOtherRelays: yup .boolean() - .label(t("routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label")), - enableImageProxy: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.enableImageProxy.label")), - enableImageProxyStorage: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.enableImageProxyStorage.label")), - allowStatistics: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.allowStatistics.label")), - allowAliasDeletion: yup - .boolean() - .label(t("routes.AdminRoute.forms.settings.allowAliasDeletion.label")), - maxAliasesPerUser: yup - .number() - .label(t("routes.AdminRoute.forms.settings.maxAliasesPerUser.label")) - .min(0), + .label(t("fields.userEmailEnableOtherRelays.label")), + enableImageProxy: yup.boolean().label(t("fields.enableImageProxy.label")), + enableImageProxyStorage: yup.boolean().label(t("fields.enableImageProxyStorage.label")), + allowStatistics: yup.boolean().label(t("fields.allowStatistics.label")), + allowAliasDeletion: yup.boolean().label(t("fields.allowAliasDeletion.label")), + maxAliasesPerUser: yup.number().label(t("fields.maxAliasesPerUser.label")).min(0), } as Record) const {mutateAsync} = useMutation< @@ -109,7 +92,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { return } - showSuccess(t("routes.AdminRoute.settings.successMessage")) + showSuccess(t("updatedSuccessfullyMessage")) queryClient.setQueryData>(queryKey, newSettings) }, @@ -144,12 +127,12 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - {t("routes.AdminRoute.settings.title")} + {t("title")} - {t("routes.AdminRoute.settings.description")} + {t("description")} @@ -160,9 +143,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { - {t( - "routes.AdminRoute.forms.settings.imageProxyStorageLifeTimeInHours.unit", - { - count: - formik.values - .imageProxyStorageLifeTimeInHours || 0, - }, - )} + {t("fields.imageProxyStorageLifeTimeInHours.unit", { + count: + formik.values + .imageProxyStorageLifeTimeInHours || 0, + })} ), }} @@ -421,9 +373,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label", - )} + label={t("fields.userEmailEnableDisposableEmails.label")} /> {(formik.touched.userEmailEnableDisposableEmails && formik.errors.userEmailEnableDisposableEmails) || - t( - "routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.description", - )} + t("fields.userEmailEnableDisposableEmails.description")} @@ -450,9 +398,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label", - )} + label={t("fields.userEmailEnableOtherRelays.label")} /> {(formik.touched.userEmailEnableOtherRelays && formik.errors.userEmailEnableOtherRelays) || - t( - "routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.description", - )} + t("fields.userEmailEnableOtherRelays.description")} @@ -481,9 +425,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.enableImageProxy.label", - )} + label={t("fields.enableImageProxy.label")} /> {(formik.touched.enableImageProxy && formik.errors.enableImageProxy) || - t( - "routes.AdminRoute.forms.settings.enableImageProxy.description", - )} + t("fields.enableImageProxy.description")} @@ -513,9 +453,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.enableImageProxyStorage.label", - )} + label={t("fields.enableImageProxyStorage.label")} /> {(formik.touched.enableImageProxyStorage && formik.errors.enableImageProxyStorage) || - t( - "routes.AdminRoute.forms.settings.enableImageProxyStorage.description", - )} + t("fields.enableImageProxyStorage.description")} @@ -545,9 +481,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.allowStatistics.label", - )} + label={t("fields.allowStatistics.label")} /> {(formik.touched.allowStatistics && formik.errors.allowStatistics) || - t( - "routes.AdminRoute.forms.settings.allowStatistics.description", - )} + t("fields.allowStatistics.description")} @@ -574,9 +506,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { /> } disabled={formik.isSubmitting} - label={t( - "routes.AdminRoute.forms.settings.allowAliasDeletion.label", - )} + label={t("fields.allowAliasDeletion.label")} /> {(formik.touched.allowAliasDeletion && formik.errors.allowAliasDeletion) || - t( - "routes.AdminRoute.forms.settings.allowAliasDeletion.description", - )} + t("fields.allowAliasDeletion.description")} @@ -608,7 +536,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { formik.submitForm() }} > - {t("routes.AdminRoute.settings.resetLabel")} + {t("general.resetLabel", {ns: "common"})} @@ -618,7 +546,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) { type="submit" startIcon={} > - {t("general.saveLabel")} + {t("general.saveLabel", {ns: "common"})} diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx index 994dc15..e1eab67 100644 --- a/src/routes/AdminRoute.tsx +++ b/src/routes/AdminRoute.tsx @@ -11,7 +11,7 @@ import {useNavigateToNext, useUser} from "~/hooks" import ServerStatus from "~/route-widgets/AdminRoute/ServerStatus" export default function AdminRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("title") const navigateToNext = useNavigateToNext() const user = useUser() @@ -22,20 +22,20 @@ export default function AdminRoute(): ReactElement { }, [user.isAdmin, navigateToNext]) return ( - + - + - + From b600d56e9591d5ee7af2381e3d990ae5ba8a0df9 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 10:33:44 +0100 Subject: [PATCH 14/22] refactor: Improve i18n for admin create reserved aliases --- .../locales/en-US/admin-reserved-aliases.json | 38 ++++++++ public/locales/en-US/common.json | 3 + public/locales/en-US/translation.json | 92 +++++++++++++++++++ .../AliasExplanation.tsx | 8 +- .../UsersSelectField.tsx | 15 ++- .../ReservedAliasesRoute/EmptyStateScreen.tsx | 12 +-- src/routes/AdminRoute.tsx | 2 +- src/routes/CreateReservedAliasRoute.tsx | 24 ++--- src/routes/ReservedAliasesRoute.tsx | 14 ++- 9 files changed, 164 insertions(+), 44 deletions(-) create mode 100644 public/locales/en-US/admin-reserved-aliases.json diff --git a/public/locales/en-US/admin-reserved-aliases.json b/public/locales/en-US/admin-reserved-aliases.json new file mode 100644 index 0000000..f8cf123 --- /dev/null +++ b/public/locales/en-US/admin-reserved-aliases.json @@ -0,0 +1,38 @@ +{ + "title": "Reserved Aliases", + "pageActions": { + "search": { + "placeholder": "Search for aliases" + } + }, + "actions": { + "create": { + "label": "Create new Reserved Alias" + } + }, + "userAmount_one": "Forwards to one user", + "userAmount_other": "Forwards to {{count}} users", + "emptyState": { + "title": "Create your first reserved alias", + "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." + }, + "createNew": { + "title": "Reserved Aliases", + "description": "Define what alias should forward to whom.", + "continueActionLabel": "Create Reserved Alias", + "fields": { + "active": { + "label": "Active" + }, + "users": { + "label": "Users", + "me": "{{email}} (Me)" + } + }, + "explanation": { + "step1": "User from outside", + "step2": "Sends mail to", + "step4": "KleckRelay forwards to" + } + } +} diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index ebc82d5..d93c5be 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -32,6 +32,9 @@ "label": "Address", "placeholder": "awesome-fish" }, + "local": { + "label": "Address" + }, "search": { "label": "Search" } diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index bcfb7d6..5610130 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -129,6 +129,98 @@ "reservedAliases": "Reserved Aliases", "settings": "Global Settings" }, + "forms": { + "reservedAliases": { + "title": "Reserved Aliases", + "description": "Define what alias should forward to whom.", + "saveAction": "Create Alias", + "fields": { + "local": { + "label": "Local" + }, + "users": { + "label": "Users", + "me": "{{email}} (Me)" + } + }, + "explanation": { + "step1": "User from outside", + "step2": "Sends mail to", + "step4": "KleckRelay forwards to" + } + }, + "settings": { + "randomEmailIdMinLength": { + "label": "Minimum random alias ID length", + "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." + }, + "randomEmailIdChars": { + "label": "Random alias character pool", + "description": "Characters that are used to generate random emails." + }, + "randomEmailLengthIncreaseOnPercentage": { + "label": "Percentage of used aliases", + "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." + }, + "customEmailSuffixLength": { + "label": "Custom email suffix length", + "description": "The length of the custom email suffix." + }, + "customEmailSuffixChars": { + "label": "Custom email suffix character pool", + "description": "Characters that are used to generate custom email suffixes." + }, + "imageProxyStorageLifeTimeInHours": { + "label": "Image proxy storage lifetime", + "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", + "unit_one": "hour", + "unit_other": "hours" + }, + "enableImageProxy": { + "label": "Enable image proxy", + "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." + }, + "enableImageProxyStorage": { + "label": "Enable image proxy storage", + "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." + }, + "userEmailEnableDisposableEmails": { + "label": "Enable disposable emails for new accounts", + "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." + }, + "userEmailEnableOtherRelays": { + "label": "Enable other relays for new accounts", + "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." + }, + "allowStatistics": { + "label": "Allow statistics", + "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." + }, + "allowAliasDeletion": { + "label": "Allow alias deletion", + "description": "If enabled, users will be able to delete their aliases." + }, + "maxAliasesPerUser": { + "label": "Maximum aliases per user", + "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." + } + } + }, + "settings": { + "title": "Global Settings", + "description": "Configure global settings for your instance.", + "successMessage": "Settings have been saved successfully!", + "randomAliasesPreview": { + "title": "Random aliases will look like this", + "helperText": "This is just a preview. Those are not real aliases." + }, + "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", + "resetLabel": "Reset to defaults", + "disabled": { + "title": "Global settings are disabled", + "description": "Global settings have been disabled. You can enable them in the configuration file." + } + }, "reservedAlias": { "actions": { "delete": { diff --git a/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx b/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx index 6e538ab..1d5f199 100644 --- a/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx +++ b/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx @@ -14,7 +14,7 @@ export interface AliasExplanationProps { } export default function AliasExplanation({local, emails}: AliasExplanationProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") const theme = useTheme() const serverSettings = useLoaderData() as ServerSettings @@ -37,7 +37,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps) - {t("routes.AdminRoute.forms.reservedAliases.explanation.step1")} + {t("createNew.explanation.step1")} @@ -49,7 +49,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps) - {t("routes.AdminRoute.forms.reservedAliases.explanation.step2")} + {t("createNew.explanation.step2")} @@ -76,7 +76,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps) - {t("routes.AdminRoute.forms.reservedAliases.explanation.step4")} + {t("createNew.explanation.step4")} diff --git a/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx b/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx index 9fb60f9..e7657b7 100644 --- a/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx +++ b/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx @@ -37,7 +37,7 @@ export default function UsersSelectField({ error, ...props }: UsersSelectFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") const meUser = useUser() const {data: {users} = {}} = useQuery( ["getAdminUsers"], @@ -49,7 +49,7 @@ export default function UsersSelectField({ return ( - {t("routes.AdminRoute.forms.reservedAliases.fields.users.label")} + {t("createNew.fields.users.label")} {...props} @@ -98,7 +98,7 @@ export default function UsersSelectField({ name="users" id="users" error={error} - label={t("routes.AdminRoute.forms.reservedAliases.fields.users.label")} + label={t("createNew.fields.users.label")} > {users ? ( users.map(user => ( @@ -108,12 +108,9 @@ export default function UsersSelectField({ primary={(() => { // Check if user is me if (user.id === meUser.id) { - return t( - "routes.AdminRoute.forms.reservedAliases.fields.users.me", - { - email: user.email.address, - }, - ) + return t("createNew.fields.users.me", { + email: user.email.address, + }) } return user.email.address diff --git a/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx b/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx index ee300e9..b3299c8 100644 --- a/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx @@ -1,11 +1,11 @@ import {ReactElement} from "react" import {useTranslation} from "react-i18next" - -import {Container, Grid, Typography} from "@mui/material" import {BsStarFill} from "react-icons/bs" +import {Container, Grid, Typography} from "@mui/material" + export default function EmptyStateScreen(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") return ( @@ -20,16 +20,14 @@ export default function EmptyStateScreen(): ReactElement { > - {t("routes.ReservedAliasesRoute.emptyState.title")} + {t("emptyState.title")} - - {t("routes.ReservedAliasesRoute.emptyState.description")} - + {t("emptyState.description")} diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx index e1eab67..f87f3f2 100644 --- a/src/routes/AdminRoute.tsx +++ b/src/routes/AdminRoute.tsx @@ -11,7 +11,7 @@ import {useNavigateToNext, useUser} from "~/hooks" import ServerStatus from "~/route-widgets/AdminRoute/ServerStatus" export default function AdminRoute(): ReactElement { - const {t} = useTranslation("title") + const {t} = useTranslation("admin") const navigateToNext = useNavigateToNext() const user = useUser() diff --git a/src/routes/CreateReservedAliasRoute.tsx b/src/routes/CreateReservedAliasRoute.tsx index 7e9f933..9d28a2a 100644 --- a/src/routes/CreateReservedAliasRoute.tsx +++ b/src/routes/CreateReservedAliasRoute.tsx @@ -26,7 +26,7 @@ interface Form { } export default function CreateReservedAliasRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const {showSuccess} = useErrorSuccessSnacks() const navigateToNext = useNavigateToNext("/admin/reserved-aliases") const {mutateAsync: createAlias} = useMutation< @@ -35,7 +35,7 @@ export default function CreateReservedAliasRoute(): ReactElement { CreateReservedAliasData >(createReservedAlias, { onSuccess: () => { - showSuccess(t("relations.alias.mutations.success.aliasCreation")) + showSuccess(t("messages.alias.created", {ns: "common"})) navigateToNext() }, }) @@ -44,10 +44,8 @@ export default function CreateReservedAliasRoute(): ReactElement { local: yup .string() .required() - .label(t("routes.AdminRoute.forms.reservedAliases.fields.local.label")), - isActive: yup - .boolean() - .label(t("routes.AdminRoute.forms.reservedAliases.fields.isActive.label")), + .label(t("fields.local.label", {ns: "common"})), + isActive: yup.boolean().label(t("fields.active.label")), // Only store IDs of users, as they provide a reference to the user users: yup .array() @@ -60,7 +58,7 @@ export default function CreateReservedAliasRoute(): ReactElement { }), }), ) - .label(t("routes.AdminRoute.forms.reservedAliases.fields.users.label")), + .label(t("fields.users.label")), }) const formik = useFormik
({ validationSchema: schema, @@ -87,12 +85,10 @@ export default function CreateReservedAliasRoute(): ReactElement { {[ @@ -110,9 +106,7 @@ export default function CreateReservedAliasRoute(): ReactElement { }} name="local" id="local" - label={t( - "routes.AdminRoute.forms.reservedAliases.fields.local.label", - )} + label={t("fields.local.label", {ns: "common"})} value={formik.values.local} onChange={formik.handleChange} disabled={formik.isSubmitting} diff --git a/src/routes/ReservedAliasesRoute.tsx b/src/routes/ReservedAliasesRoute.tsx index 4b6ca4a..7060f87 100644 --- a/src/routes/ReservedAliasesRoute.tsx +++ b/src/routes/ReservedAliasesRoute.tsx @@ -22,7 +22,7 @@ import {NoSearchResults, QueryResult, SimplePage} from "~/components" import EmptyStateScreen from "~/route-widgets/ReservedAliasesRoute/EmptyStateScreen" export default function ReservedAliasesRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const [showSearch, setShowSearch] = useState(false) const [searchValue, setSearchValue] = useState("") const [queryValue, setQueryValue] = useState("") @@ -44,7 +44,7 @@ export default function ReservedAliasesRoute(): ReactElement { return ( - {t("routes.ReservedAliasesRoute.actions.create.label")} + {t("actions.create.label")} } > @@ -108,7 +106,7 @@ export default function ReservedAliasesRoute(): ReactElement { @{alias.domain} } - secondary={t("routes.ReservedAliasesRoute.userAmount", { + secondary={t("userAmount", { count: alias.users.length, })} /> From 038c157100c2ad1435a56d5445729b201ea85aa3 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 10:42:12 +0100 Subject: [PATCH 15/22] refactor: Improve i18n for admin updating reserved aliases --- .../locales/en-US/admin-reserved-aliases.json | 24 +++-- public/locales/en-US/common.json | 3 +- public/locales/en-US/translation.json | 97 ------------------- .../UsersSelectField.tsx | 8 +- .../AliasActivationSwitch.tsx | 6 +- .../AliasUsersList.tsx | 8 +- src/routes/ReservedAliasDetailRoute.tsx | 16 ++- 7 files changed, 34 insertions(+), 128 deletions(-) diff --git a/public/locales/en-US/admin-reserved-aliases.json b/public/locales/en-US/admin-reserved-aliases.json index f8cf123..21e9f96 100644 --- a/public/locales/en-US/admin-reserved-aliases.json +++ b/public/locales/en-US/admin-reserved-aliases.json @@ -1,5 +1,6 @@ { "title": "Reserved Aliases", + "detailsTitle": "Reserved Alias Details", "pageActions": { "search": { "placeholder": "Search for aliases" @@ -8,6 +9,11 @@ "actions": { "create": { "label": "Create new Reserved Alias" + }, + "delete": { + "label": "Delete Reserved Alias", + "description": "Are you sure you want to delete this reserved alias?", + "continueActionLabel": "Delete Reserved Alias" } }, "userAmount_one": "Forwards to one user", @@ -16,19 +22,19 @@ "title": "Create your first reserved alias", "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." }, + "fields": { + "active": { + "label": "Active" + }, + "users": { + "label": "Users", + "me": "{{email}} (Me)" + } + }, "createNew": { "title": "Reserved Aliases", "description": "Define what alias should forward to whom.", "continueActionLabel": "Create Reserved Alias", - "fields": { - "active": { - "label": "Active" - }, - "users": { - "label": "Users", - "me": "{{email}} (Me)" - } - }, "explanation": { "step1": "User from outside", "step2": "Sends mail to", diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index d93c5be..53af3f0 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -61,7 +61,8 @@ "unavailableValue": "Unavailable", "experimentalFeatureExplanation": "This is an experimental feature.", "saveLabel": "Save", - "resetLabel": "Reset" + "resetLabel": "Reset", + "loading": "Loading..." }, "noSearchResults": { "title": "Nothing found", diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 5610130..c47a945 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -124,103 +124,6 @@ } }, "AdminRoute": { - "title": "Site configuration", - "routes": { - "reservedAliases": "Reserved Aliases", - "settings": "Global Settings" - }, - "forms": { - "reservedAliases": { - "title": "Reserved Aliases", - "description": "Define what alias should forward to whom.", - "saveAction": "Create Alias", - "fields": { - "local": { - "label": "Local" - }, - "users": { - "label": "Users", - "me": "{{email}} (Me)" - } - }, - "explanation": { - "step1": "User from outside", - "step2": "Sends mail to", - "step4": "KleckRelay forwards to" - } - }, - "settings": { - "randomEmailIdMinLength": { - "label": "Minimum random alias ID length", - "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." - }, - "randomEmailIdChars": { - "label": "Random alias character pool", - "description": "Characters that are used to generate random emails." - }, - "randomEmailLengthIncreaseOnPercentage": { - "label": "Percentage of used aliases", - "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." - }, - "customEmailSuffixLength": { - "label": "Custom email suffix length", - "description": "The length of the custom email suffix." - }, - "customEmailSuffixChars": { - "label": "Custom email suffix character pool", - "description": "Characters that are used to generate custom email suffixes." - }, - "imageProxyStorageLifeTimeInHours": { - "label": "Image proxy storage lifetime", - "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", - "unit_one": "hour", - "unit_other": "hours" - }, - "enableImageProxy": { - "label": "Enable image proxy", - "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." - }, - "enableImageProxyStorage": { - "label": "Enable image proxy storage", - "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." - }, - "userEmailEnableDisposableEmails": { - "label": "Enable disposable emails for new accounts", - "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." - }, - "userEmailEnableOtherRelays": { - "label": "Enable other relays for new accounts", - "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." - }, - "allowStatistics": { - "label": "Allow statistics", - "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." - }, - "allowAliasDeletion": { - "label": "Allow alias deletion", - "description": "If enabled, users will be able to delete their aliases." - }, - "maxAliasesPerUser": { - "label": "Maximum aliases per user", - "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." - } - } - }, - "settings": { - "title": "Global Settings", - "description": "Configure global settings for your instance.", - "successMessage": "Settings have been saved successfully!", - "randomAliasesPreview": { - "title": "Random aliases will look like this", - "helperText": "This is just a preview. Those are not real aliases." - }, - "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", - "resetLabel": "Reset to defaults", - "disabled": { - "title": "Global settings are disabled", - "description": "Global settings have been disabled. You can enable them in the configuration file." - } - }, "reservedAlias": { "actions": { "delete": { diff --git a/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx b/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx index e7657b7..4c0609a 100644 --- a/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx +++ b/src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx @@ -49,7 +49,7 @@ export default function UsersSelectField({ return ( - {t("createNew.fields.users.label")} + {t("fields.users.label")} {...props} @@ -98,7 +98,7 @@ export default function UsersSelectField({ name="users" id="users" error={error} - label={t("createNew.fields.users.label")} + label={t("fields.users.label")} > {users ? ( users.map(user => ( @@ -108,7 +108,7 @@ export default function UsersSelectField({ primary={(() => { // Check if user is me if (user.id === meUser.id) { - return t("createNew.fields.users.me", { + return t("fields.users.me", { email: user.email.address, }) } @@ -119,7 +119,7 @@ export default function UsersSelectField({ )) ) : ( - {t("general.loading")} + {t("general.loading", {ns: "common"})} )} {helperText ? {helperText} : null} diff --git a/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx b/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx index 6057d11..e2fc46c 100644 --- a/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx +++ b/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx @@ -22,7 +22,7 @@ export default function AliasActivationSwitch({ isActive, queryKey, }: AliasActivationSwitch): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const {showError, showSuccess} = useErrorSuccessSnacks() const {isLoading, mutateAsync} = useMutation< ReservedAlias, @@ -76,8 +76,8 @@ export default function AliasActivationSwitch({ showSuccess( isActive - ? t("relations.alias.mutations.success.aliasChangedToDisabled") - : t("relations.alias.mutations.success.aliasChangedToEnabled"), + ? t("messages.alias.changedToDisabled") + : t("messages.alias.changedToEnabled"), ) } catch {} }} diff --git a/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx b/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx index 75c549a..524f921 100644 --- a/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx +++ b/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx @@ -40,7 +40,7 @@ interface Form { } export default function AliasUsersList({users, queryKey, id}: AliasUsersListProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const {showError, showSuccess} = useErrorSuccessSnacks() const {mutateAsync} = useMutation< ReservedAlias, @@ -73,7 +73,7 @@ export default function AliasUsersList({users, queryKey, id}: AliasUsersListProp } }, onSuccess: async newAlias => { - showSuccess(t("relations.alias.mutations.success.aliasUpdated")) + showSuccess(t("messages.alias.updated", {ns: "common"})) await queryClient.cancelQueries(queryKey) @@ -102,7 +102,7 @@ export default function AliasUsersList({users, queryKey, id}: AliasUsersListProp }), }), ) - .label(t("routes.AliasDetailRoute.sections.users.fields.users.label")), + .label(t("fields.users.label")), }) const initialValues: Form = { users: users, @@ -126,7 +126,7 @@ export default function AliasUsersList({users, queryKey, id}: AliasUsersListProp - {t("routes.ReservedAliasDetailRoute.sections.users.title")} + {t("fields.users.label")} diff --git a/src/routes/ReservedAliasDetailRoute.tsx b/src/routes/ReservedAliasDetailRoute.tsx index 74cbdbb..2d58bb8 100644 --- a/src/routes/ReservedAliasDetailRoute.tsx +++ b/src/routes/ReservedAliasDetailRoute.tsx @@ -14,7 +14,7 @@ import AliasAddress from "~/route-widgets/AliasDetailRoute/AliasAddress" import AliasUsersList from "~/route-widgets/ReservedAliasDetailRoute/AliasUsersList" export default function ReservedAliasDetailRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["admin-reserved-aliases", "common"]) const params = useParams() const queryKey = ["get_reserved_alias", params.id!] @@ -22,20 +22,16 @@ export default function ReservedAliasDetailRoute(): ReactElement { return ( deleteReservedAlias(params.id!)} - label={t("routes.AdminRoute.reservedAlias.actions.delete.label")} - description={t( - "routes.adminRoute.reservedAlias.actions.delete.description", - )} - continueLabel={t( - "routes.AdminRoute.reservedAlias.actions.delete.continueAction", - )} + label={t("actions.delete.label")} + description={t("actions.delete.description")} + continueLabel={t("actions.delete.continueActionLabel")} navigateTo="/admin/reserved-aliases" - successMessage={t("relations.alias.mutations.success.aliasDeleted")} + successMessage={t("messages.alias.deleted", {ns: "common"})} /> ) } From 26dece616b52572bb972676ac51f3e6801ce5323 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 10:43:55 +0100 Subject: [PATCH 16/22] refactor: Improve i18n for admin --- public/locales/en-US/admin.json | 5 +++ public/locales/en-US/translation.json | 43 ------------------- src/route-widgets/AdminRoute/ServerStatus.tsx | 8 ++-- 3 files changed, 9 insertions(+), 47 deletions(-) diff --git a/public/locales/en-US/admin.json b/public/locales/en-US/admin.json index 7eb0c5d..5484e20 100644 --- a/public/locales/en-US/admin.json +++ b/public/locales/en-US/admin.json @@ -3,5 +3,10 @@ "routes": { "reservedAliases": "Reserved Aliases", "settings": "Global Settings" + }, + "serverStatus": { + "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", + "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", + "success": "Everything okay with your server! The last cleanup job was {{relativeDescription}}." } } diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index c47a945..cf1782d 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -89,50 +89,7 @@ } } }, - "ReservedAliasesRoute": { - "title": "Reserved Aliases", - "pageActions": { - "search": { - "label": "Search", - "placeholder": "Search for aliases" - } - }, - "actions": { - "create": { - "label": "Create new Reserved Alias" - } - }, - "userAmount_one": "Forwards to one user", - "userAmount_other": "Forwards to {{count}} users", - "emptyState": { - "title": "Create your first reserved alias", - "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." - } - }, - "ReservedAliasDetailRoute": { - "title": "Reserved Alias Details", - "sections": { - "users": { - "title": "Users", - "fields": { - "users": { - "label": "Users", - "me": "{{email}} (Me)" - } - } - } - } - }, "AdminRoute": { - "reservedAlias": { - "actions": { - "delete": { - "label": "Delete Reserved Alias", - "description": "Are you sure you want to delete this reserved alias?", - "continueAction": "Delete Reserved Alias" - } - } - }, "serverStatus": { "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", diff --git a/src/route-widgets/AdminRoute/ServerStatus.tsx b/src/route-widgets/AdminRoute/ServerStatus.tsx index 8140b5e..19ace79 100644 --- a/src/route-widgets/AdminRoute/ServerStatus.tsx +++ b/src/route-widgets/AdminRoute/ServerStatus.tsx @@ -18,8 +18,8 @@ import decryptCronReportData from "~/apis/helpers/decrypt-cron-report-data" const MAX_REPORT_DAY_THRESHOLD = 5 function ServerStatus(): ReactElement | null { + const {t} = useTranslation("admin") const serverSettings = useLoaderData() as ServerSettings - const {t} = useTranslation() const {_decryptUsingPrivateKey} = useContext(AuthContext) const query = useQuery(["get_latest_cron_report"], async () => { @@ -50,7 +50,7 @@ function ServerStatus(): ReactElement | null { if (report.createdAt < thresholdDate) { return ( - {t("routes.AdminRoute.serverStatus.noRecentReports", { + {t("serverStatus.noRecentReports", { date: format(new Date(report.createdAt), "Pp"), })} @@ -60,7 +60,7 @@ function ServerStatus(): ReactElement | null { if (report.reportData.report.status === "error") { return ( - {t("routes.AdminRoute.serverStatus.error", { + {t("serverStatus.error", { relativeDescription: formatRelative( new Date(report.createdAt), new Date(), @@ -72,7 +72,7 @@ function ServerStatus(): ReactElement | null { return ( - {t("routes.AdminRoute.serverStatus.success", { + {t("serverStatus.success", { relativeDescription: formatRelative( new Date(report.createdAt), new Date(), From 727ae4ffbf7d65b76031aa1418792cdb9a49bfb8 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 11:14:20 +0100 Subject: [PATCH 17/22] refactor: Improve i18n for reports --- public/locales/en-US/common.json | 3 + public/locales/en-US/reports.json | 68 ++++++++++++++++ public/locales/en-US/translation.json | 81 ------------------- .../ExpandedUrlsListItem.tsx | 4 +- .../ProxiedImagesListItem.tsx | 8 +- .../SinglePixelImageTrackersListItem.tsx | 4 +- .../ReportsRoute/EmptyStateScreen.tsx | 8 +- .../ReportsRoute/ReportInformationItem.tsx | 8 +- src/routes/ReportDetailRoute.tsx | 28 +++---- src/routes/ReportsRoute.tsx | 8 +- 10 files changed, 100 insertions(+), 120 deletions(-) create mode 100644 public/locales/en-US/reports.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 53af3f0..242d6e4 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -51,6 +51,9 @@ "updated": "Alias has been updated successfully!", "changedToEnabled": "Alias has been enabled", "changedToDisabled": "Alias has been disabled" + }, + "report": { + "deleted": "Report has been deleted!" } }, "general": { diff --git a/public/locales/en-US/reports.json b/public/locales/en-US/reports.json new file mode 100644 index 0000000..954a704 --- /dev/null +++ b/public/locales/en-US/reports.json @@ -0,0 +1,68 @@ +{ + "title": "Reports", + "detailsTitle": "Report Details", + "emptyState": { + "title": "Welcome to your Reports!", + "description": "Here you will find your email reports. Currently, you don't have any reports. Wait until you receive an email." + }, + "pageActions": { + "sort": { + "label": "Sorting", + "values": { + "List": "List reports by their date", + "GroupByAlias": "Group reports by their alias" + } + } + }, + "emailMeta": { + "flow": "{{from}} -> {{to}}", + "emptySubject": "" + }, + "actions": { + "delete": { + "label": "Delete Report", + "description": "Are you sure you want to delete this report?", + "continueActionLabel": "Delete Report" + } + }, + "sections": { + "information": { + "title": "Email Information", + "form": { + "from": { + "label": "From" + }, + "to": { + "label": "To" + }, + "subject": { + "label": "Subject" + } + } + }, + "trackers": { + "title": "Trackers", + "results": { + "imageTrackers": { + "text_zero": "No image trackers found", + "text_one": "Removed 1 image tracker", + "text_other": "Removed {{count}} image trackers" + }, + "proxiedImages": { + "text_zero": "No images found", + "text_one": "Forwarding 1 image", + "text_other": "Forwarding {{count}} images", + "status": { + "isStored": "Stored on Server", + "isProxying": "Being forwarded" + } + }, + "expandedUrls": { + "text_zero": "No shortened URLs found", + "text_one": "Expanded 1 URL", + "text_other": "Expanded {{count}} URLs" + } + } + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index cf1782d..3ade83d 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -25,76 +25,6 @@ "OverviewRoute": { "title": "Overview", "description": "Not much to see here, yet." - }, - "ReportsRoute": { - "title": "Reports", - "emptyState": { - "title": "Welcome to your Reports!", - "description": "Here you will find your email reports. Currently, you don't have any reports. Wait until you receive an email." - }, - "pageActions": { - "sort": { - "List": "List reports by their date", - "GroupByAlias": "Group reports by their alias" - } - } - }, - "ReportDetailRoute": { - "title": "Report Details", - "actions": { - "delete": { - "label": "Delete Report", - "description": "Are you sure you want to delete this report?", - "continueAction": "Delete Report" - } - }, - "sections": { - "information": { - "title": "Email Information", - "form": { - "from": { - "label": "From" - }, - "to": { - "label": "To" - }, - "subject": { - "label": "Subject" - } - } - }, - "trackers": { - "title": "Trackers", - "results": { - "imageTrackers": { - "text_zero": "No image trackers found", - "text_one": "Removed 1 image tracker", - "text_other": "Removed {{count}} image trackers" - }, - "proxiedImages": { - "text_zero": "No images found", - "text_one": "Forwarding 1 image", - "text_other": "Forwarding {{count}} images", - "status": { - "isStored": "Stored on Server", - "isProxying": "Being forwarded" - } - }, - "expandedUrls": { - "text_zero": "No shortened URLs found", - "text_one": "Expanded 1 URL", - "text_other": "Expanded {{count}} URLs" - } - } - } - } - }, - "AdminRoute": { - "serverStatus": { - "noRecentReports": "There seems to be some issues with your server. The server hasn't done its cleanup in the last few days. The last report was on {{date}}.", - "error": "There was an error during the last server cleanup job from {{relativeDescription}}. Please check the logs for more information.", - "success": "Everything okay with your server! The last cleanup job was {{relativeDescription}}." - } } }, @@ -196,16 +126,5 @@ } } }, - "report": { - "mutations": { - "success": { - "reportDeleted": "Report has been deleted!" - } - }, - "emailMeta": { - "flow": "{{from}} -> {{to}}", - "emptySubject": "" - } - } } } diff --git a/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx b/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx index d366d4a..43b48bd 100644 --- a/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx +++ b/src/route-widgets/ReportDetailRoute/ExpandedUrlsListItem.tsx @@ -12,13 +12,13 @@ export interface ExpandedUrlsListItemProps { } export default function ExpandedUrlsListItem({urls}: ExpandedUrlsListItemProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") return ( } - title={t("routes.ReportDetailRoute.sections.trackers.results.expandedUrls.text", { + title={t("sections.trackers.results.expandedUrls.text", { count: urls.length, })} > diff --git a/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx b/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx index 0a03a86..f5681e0 100644 --- a/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx +++ b/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx @@ -17,14 +17,14 @@ export interface ProxiedImagesListItemProps { } export default function ProxiedImagesListItem({images}: ProxiedImagesListItemProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") const serverSettings = useLoaderData() as ServerSettings return ( } - title={t("routes.ReportDetailRoute.sections.trackers.results.proxiedImages.text", { + title={t("sections.trackers.results.proxiedImages.text", { count: images.length, })} > @@ -62,11 +62,11 @@ export default function ProxiedImagesListItem({images}: ProxiedImagesListItemPro ) ) { return t( - "routes.ReportDetailRoute.sections.trackers.results.proxiedImages.status.isStored", + "sections.trackers.results.proxiedImages.status.isStored", ) } else { return t( - "routes.ReportDetailRoute.sections.trackers.results.proxiedImages.status.isProxying", + "sections.trackers.results.proxiedImages.status.isProxying", ) } })()} diff --git a/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx b/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx index c02e411..decc661 100644 --- a/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx +++ b/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx @@ -14,7 +14,7 @@ export interface SinglePixelImageTrackersListItemProps { export default function SinglePixelImageTrackersListItem({ images, }: SinglePixelImageTrackersListItemProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") const imagesPerTracker = images.reduce((acc, value) => { acc[value.trackerName] = [...(acc[value.trackerName] || []), value] @@ -26,7 +26,7 @@ export default function SinglePixelImageTrackersListItem({ } - title={t("routes.ReportDetailRoute.sections.trackers.results.imageTrackers.text", { + title={t("sections.trackers.results.imageTrackers.text", { count: images.length, })} > diff --git a/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx b/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx index eb1511c..640cb41 100644 --- a/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx @@ -5,23 +5,21 @@ import {MdTextSnippet} from "react-icons/md" import {Container, Grid, Typography} from "@mui/material" export default function EmptyStateScreen(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") return ( - {t("routes.ReportsRoute.emptyState.title")} + {t("emptyState.title")} - - {t("routes.ReportsRoute.emptyState.description")} - + {t("emptyState.description")} diff --git a/src/route-widgets/ReportsRoute/ReportInformationItem.tsx b/src/route-widgets/ReportsRoute/ReportInformationItem.tsx index aacbc66..2d423dc 100644 --- a/src/route-widgets/ReportsRoute/ReportInformationItem.tsx +++ b/src/route-widgets/ReportsRoute/ReportInformationItem.tsx @@ -11,18 +11,16 @@ export interface ReportInformationItemProps { } export default function ReportInformationItem({report}: ReportInformationItemProps): ReactElement { + const {t} = useTranslation("reports") const navigate = useNavigate() - const {t} = useTranslation() return ( navigate(`/reports/${report.id}`)}> {t("relations.report.emailMeta.emptySubject")} - ) + report.messageDetails.content.subject || {t("emailMeta.emptySubject")} } - secondary={t("relations.report.emailMeta.flow", { + secondary={t("emailMeta.flow", { from: report.messageDetails.meta.from, to: report.messageDetails.meta.to, })} diff --git a/src/routes/ReportDetailRoute.tsx b/src/routes/ReportDetailRoute.tsx index 7d4b36f..67a2dd9 100644 --- a/src/routes/ReportDetailRoute.tsx +++ b/src/routes/ReportDetailRoute.tsx @@ -21,7 +21,7 @@ import ProxiedImagesListItem from "~/route-widgets/ReportDetailRoute/ProxiedImag import SinglePixelImageTrackersListItem from "~/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem" function ReportDetailRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["reports", "common"]) const params = useParams() const query = useQuery(["get_report", params.id], () => @@ -30,16 +30,16 @@ function ReportDetailRoute(): ReactElement { return ( deleteReport(params.id!)} - label={t("routes.ReportDetailRoute.actions.delete.label")} - description={t("routes.ReportDetailRoute.actions.delete.description")} - continueLabel={t("routes.ReportDetailRoute.actions.delete.continueAction")} + label={t("actions.delete.label")} + description={t("delete.description")} + continueLabel={t("actions.delete.continueActionLabel")} navigateTo={"/reports"} - successMessage={t("relations.report.mutations.success.reportDeleted")} + successMessage={t("messages.report.deleted", {ns: "common"})} /> ) } @@ -52,16 +52,14 @@ function ReportDetailRoute(): ReactElement { {[ {[ { @@ -72,9 +70,7 @@ function ReportDetailRoute(): ReactElement { { (report as DecryptedReportContent) @@ -85,7 +81,7 @@ function ReportDetailRoute(): ReactElement { { @@ -99,9 +95,7 @@ function ReportDetailRoute(): ReactElement { = { [SortingView.GroupByAlias]: , } const SORTING_VIEW_NAME_MAP: Record = createEnumMapFromTranslation( - "routes.ReportsRoute.pageActions.sort", + "pageActions.sort.values", SortingView, ) function ReportsRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("reports") const query = useQuery, AxiosError>(["get_reports"], getReports) @@ -40,13 +40,13 @@ function ReportsRoute(): ReactElement { return ( 0 && ( setSortingView(event.target.value as SortingView)} - label="Sorting" + label={t("pageActions.sort.label")} id="sorting" InputProps={{ startAdornment: ( From e41825a61417b39a3ce74d6f83a00ba673313593 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 11:26:38 +0100 Subject: [PATCH 18/22] refactor: Improve i18n for decryption --- public/locales/en-US/common.json | 17 ++++- public/locales/en-US/decryption.json | 21 ++++++ public/locales/en-US/translation.json | 64 ------------------- .../DecryptionPasswordMissingAlert.tsx | 20 ++---- .../AuthenticateRoute/NavigationButton.tsx | 12 ++-- src/routes/AuthenticateRoute.tsx | 6 +- src/routes/AuthenticatedRoute.tsx | 4 +- src/routes/EnterDecryptionPassword.tsx | 21 +++--- 8 files changed, 63 insertions(+), 102 deletions(-) create mode 100644 public/locales/en-US/decryption.json diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json index 242d6e4..4c0504a 100644 --- a/public/locales/en-US/common.json +++ b/public/locales/en-US/common.json @@ -19,7 +19,10 @@ }, "password": { "label": "Password", - "placeholder": "********" + "placeholder": "********", + "errors": { + "invalid": "Password is invalid." + } }, "passwordConfirmation": { "label": "Confirm Password", @@ -70,5 +73,17 @@ "noSearchResults": { "title": "Nothing found", "description": "We couldn't find anything for your search query. Try again with a different query." + }, + "navigation": { + "overview": "Overview", + "aliases": "Aliases", + "reports": "Reports", + "settings": "Settings", + "admin": "Admin" + }, + "routes": { + "signup": "Sign up", + "login": "Log in", + "logout": "Log out" } } diff --git a/public/locales/en-US/decryption.json b/public/locales/en-US/decryption.json new file mode 100644 index 0000000..5a2147e --- /dev/null +++ b/public/locales/en-US/decryption.json @@ -0,0 +1,21 @@ +{ + "actions": { + "enterDecryptionPassword": { + "title": "Decrypt Reports", + "description": "Please enter your password so that your reports can de decrypted.", + "cancelActionLabel": "Decrypt later" + }, + "passwordMissing": { + "unavailable": { + "title": "Encryption required", + "description": "You need to set up encryption to use this feature.", + "continueActionLabel": "Set up encryption" + }, + "passwordRequired": { + "title": "Password required", + "description": "Your decryption password is required to view this section.", + "continueActionLabel": "Enter password" + } + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 3ade83d..0f28243 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -21,61 +21,13 @@ "appError": "We are sorry but there was an error. Please try again later." }, - "routes": { - "OverviewRoute": { - "title": "Overview", - "description": "Not much to see here, yet." - } - }, - "components": { - "NavigationButton": { - "overview": "Overview", - "aliases": "Aliases", - "reports": "Reports", - "settings": "Settings", - "admin": "Admin" - }, - "AuthenticateRoute": { - "signup": "Sign up", - "login": "Log in" - }, - "AuthenticatedRoute": { - "logout": "Logout" - }, - "EnterDecryptionPassword": { - "title": "Decrypt Reports", - "description": "Please enter your password so that your reports can de decrypted.", - "cancelAction": "Decrypt later", - "continueAction": "Continue", - "form": { - "password": { - "label": "Password", - "placeholder": "********", - "errors": { - "invalidPassword": "Password is invalid" - } - } - } - }, "ResendMailButton": { "label": "Resend Mail" }, "OpenMailButton": { "label": "Open Mail" }, - "DecryptionPasswordMissingAlert": { - "unavailable": { - "title": "Encryption required", - "description": "You need to set up encryption to use this feature.", - "continueAction": "Set up encryption" - }, - "passwordRequired": { - "title": "Password required", - "description": "Your decryption password is required to view this section.", - "continueAction": "Enter password" - } - }, "TimedButton": { "remainingTime_one": "({{count}})", "remainingTime_other": "({{count}})" @@ -111,20 +63,4 @@ } } }, - - "relations": { - "alias": { - "mutations": { - "success": { - "aliasCreation": "Created Alias successfully!", - "aliasUpdated": "Updated Alias successfully!", - "notesUpdated": "Updated & encrypted notes successfully!", - "aliasChangedToEnabled": "Alias has been enabled", - "aliasChangedToDisabled": "Alias has been disabled", - "addressCopiedToClipboard": "Address has been copied to your clipboard!", - "aliasDeleted": "Alias has been deleted!" - } - } - }, - } } diff --git a/src/components/widgets/DecryptionPasswordMissingAlert.tsx b/src/components/widgets/DecryptionPasswordMissingAlert.tsx index be8b59b..13ac737 100644 --- a/src/components/widgets/DecryptionPasswordMissingAlert.tsx +++ b/src/components/widgets/DecryptionPasswordMissingAlert.tsx @@ -15,7 +15,7 @@ export interface WithEncryptionRequiredProps { export default function DecryptionPasswordMissingAlert({ children = <>, }: WithEncryptionRequiredProps): JSX.Element { - const {t} = useTranslation() + const {t} = useTranslation("decryption") const {handleAnchorClick} = useContext(LockNavigationContext) const {encryptionStatus} = useContext(AuthContext) const theme = useTheme() @@ -33,12 +33,12 @@ export default function DecryptionPasswordMissingAlert({ > - {t("components.DecryptionPasswordMissingAlert.unavailable.title")} + {t("actions.passwordMissing.unavailable.title")} - {t("components.DecryptionPasswordMissingAlert.unavailable.description")} + {t("actions.passwordMissing.unavailable.description")} @@ -49,9 +49,7 @@ export default function DecryptionPasswordMissingAlert({ startIcon={} onClick={handleAnchorClick} > - {t( - "components.DecryptionPasswordMissingAlert.unavailable.continueAction", - )} + {t("actions.passwordMissing.unavailable.continueActionLabel")} @@ -70,14 +68,12 @@ export default function DecryptionPasswordMissingAlert({ > - {t("components.DecryptionPasswordMissingAlert.passwordRequired.title")} + {t("actions.passwordMissing.passwordRequired.title")} - {t( - "components.DecryptionPasswordMissingAlert.passwordRequired.description", - )} + {t("actions.passwordMissing.passwordRequired.description")} @@ -87,9 +83,7 @@ export default function DecryptionPasswordMissingAlert({ startIcon={} onClick={handleAnchorClick} > - {t( - "components.DecryptionPasswordMissingAlert.passwordRequired.continueAction", - )} + {t("actions.passwordMissing.passwordRequired.continueActionLabel")} diff --git a/src/route-widgets/AuthenticateRoute/NavigationButton.tsx b/src/route-widgets/AuthenticateRoute/NavigationButton.tsx index f7666ab..406cc13 100644 --- a/src/route-widgets/AuthenticateRoute/NavigationButton.tsx +++ b/src/route-widgets/AuthenticateRoute/NavigationButton.tsx @@ -30,11 +30,11 @@ const SECTION_ICON_MAP: Record = { } const SECTION_TEXT_MAP: Record = { - [NavigationSection.Overview]: "components.NavigationButton.overview", - [NavigationSection.Aliases]: "components.NavigationButton.aliases", - [NavigationSection.Reports]: "components.NavigationButton.reports", - [NavigationSection.Settings]: "components.NavigationButton.settings", - [NavigationSection.Admin]: "components.NavigationButton.admin", + [NavigationSection.Overview]: "navigation.overview", + [NavigationSection.Aliases]: "navigation.aliases", + [NavigationSection.Reports]: "navigation.reports", + [NavigationSection.Settings]: "navigation.settings", + [NavigationSection.Admin]: "navigation.admin", } const PATH_SECTION_MAP: Record = { @@ -46,7 +46,7 @@ const PATH_SECTION_MAP: Record = { } export default function NavigationButton({section}: NavigationButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const {handleAnchorClick} = useContext(LockNavigationContext) const location = useLocation() diff --git a/src/routes/AuthenticateRoute.tsx b/src/routes/AuthenticateRoute.tsx index 2bd1787..d75d2c5 100644 --- a/src/routes/AuthenticateRoute.tsx +++ b/src/routes/AuthenticateRoute.tsx @@ -8,7 +8,7 @@ import {Box, Button, Grid} from "@mui/material" import {LanguageButton} from "~/components" export default function AuthenticateRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") return ( } > - {t("components.AuthenticateRoute.signup")} + {t("routes.signup")} @@ -46,7 +46,7 @@ export default function AuthenticateRoute(): ReactElement { size="small" startIcon={} > - {t("components.AuthenticateRoute.login")} + {t("routes.login")} diff --git a/src/routes/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute.tsx index f04ca7c..fb25b95 100644 --- a/src/routes/AuthenticatedRoute.tsx +++ b/src/routes/AuthenticatedRoute.tsx @@ -12,7 +12,7 @@ import NavigationButton, { } from "~/route-widgets/AuthenticateRoute/NavigationButton" export default function AuthenticatedRoute(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("common") const theme = useTheme() const user = useUser() @@ -90,7 +90,7 @@ export default function AuthenticatedRoute(): ReactElement { to="/auth/logout" startIcon={} > - {t("components.AuthenticatedRoute.logout")} + {t("routes.logout")} diff --git a/src/routes/EnterDecryptionPassword.tsx b/src/routes/EnterDecryptionPassword.tsx index 5ff8445..17c10a1 100644 --- a/src/routes/EnterDecryptionPassword.tsx +++ b/src/routes/EnterDecryptionPassword.tsx @@ -17,7 +17,7 @@ interface Form { } export default function EnterDecryptionPassword(): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["decryption", "common"]) const navigate = useNavigate() const navigateToNext = useNavigateToNext() const user = useUser() @@ -29,7 +29,7 @@ export default function EnterDecryptionPassword(): ReactElement { password: yup .string() .required() - .label(t("components.EnterDecryptionPassword.form.password.label")), + .label(t("fields.password.label", {ns: "common"})), }) const formik = useFormik({ @@ -46,9 +46,7 @@ export default function EnterDecryptionPassword(): ReactElement { } catch (error) { // Password is incorrect setErrors({ - password: t( - "components.EnterDecryptionPassword.form.password.errors.invalidPassword", - ), + password: t("fields.password.errors,invalid", {ns: "common"}), }) } }, @@ -67,10 +65,9 @@ export default function EnterDecryptionPassword(): ReactElement { return ( @@ -81,10 +78,8 @@ export default function EnterDecryptionPassword(): ReactElement { autoFocus name="password" id="password" - label={t("components.EnterDecryptionPassword.form.password.label")} - placeholder={t( - "components.EnterDecryptionPassword.form.password.placeholder", - )} + label={t("fields.password.label", {ns: "common"})} + placeholder={t("fields.password.placeholder", {ns: "common"})} value={formik.values.password} onChange={formik.handleChange} disabled={formik.isSubmitting} From 012d78f8ed00f8c38fe852b6ae24fef11a19b0a4 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 11:31:39 +0100 Subject: [PATCH 19/22] refactor: Improve i18n for components --- public/locales/en-US/components.json | 33 +++++++++++++++++++ public/locales/en-US/translation.json | 33 +------------------ .../LockNavigationContextProvider.tsx | 10 +++--- .../widgets/ErrorLoadingDataMessage.tsx | 6 ++-- src/components/widgets/OpenMailButton.tsx | 4 +-- .../widgets/StringPoolField/AddNewDialog.tsx | 12 +++---- .../StringPoolField/StringPoolField.tsx | 6 ++-- src/components/widgets/TimedButton.tsx | 6 ++-- .../ConfirmCodeForm/ResendMailButton.tsx | 4 +-- .../YouGotMail/ResendMailButton.tsx | 4 +-- 10 files changed, 57 insertions(+), 61 deletions(-) create mode 100644 public/locales/en-US/components.json diff --git a/public/locales/en-US/components.json b/public/locales/en-US/components.json new file mode 100644 index 0000000..c735d6d --- /dev/null +++ b/public/locales/en-US/components.json @@ -0,0 +1,33 @@ +{ + "ResendMailButton": { + "label": "Resend Mail" + }, + "OpenMailButton": { + "label": "Open Mail" + }, + "TimedButton": { + "remainingTime_one": "({{count}})", + "remainingTime_other": "({{count}})" + }, + "ErrorLoadingDataMessage": { + "tryAgain": "Try Again" + }, + "LockNavigationContextProvider": { + "title": "Are you sure you want to leave?", + "description": "You have unsaved changes. If you leave, your changes will be lost.", + "continueLabel": "Leave" + }, + "StringPoolField": { + "addCustom": { + "label": "Add custom" + }, + "forms": { + "addNew": { + "title": "Add new value", + "description": "Enter your characters you would like to include", + "label": "Characters", + "submit": "Add" + } + } + } +} diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json index 0f28243..a05b8e5 100644 --- a/public/locales/en-US/translation.json +++ b/public/locales/en-US/translation.json @@ -22,24 +22,6 @@ }, "components": { - "ResendMailButton": { - "label": "Resend Mail" - }, - "OpenMailButton": { - "label": "Open Mail" - }, - "TimedButton": { - "remainingTime_one": "({{count}})", - "remainingTime_other": "({{count}})" - }, - "ErrorLoadingDataMessage": { - "tryAgain": "Try Again" - }, - "LockNavigationContextProvider": { - "title": "Are you sure you want to leave?", - "description": "You have unsaved changes. If you leave, your changes will be lost.", - "continueLabel": "Leave" - }, "passwordShareConfirmationDialog": { "title": "Share Password?", "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", @@ -48,19 +30,6 @@ "doNotShare": "Do not share", "decideLater": "Decide later", "doNotAskAgain": "Do not ask again" - }, - "StringPoolField": { - "addCustom": { - "label": "Add custom" - }, - "forms": { - "addNew": { - "title": "Add new value", - "description": "Enter your characters you would like to include", - "label": "Characters", - "submit": "Add" - } - } } - }, + } } diff --git a/src/components/LockNavigation/LockNavigationContextProvider.tsx b/src/components/LockNavigation/LockNavigationContextProvider.tsx index 099d858..d4fe433 100644 --- a/src/components/LockNavigation/LockNavigationContextProvider.tsx +++ b/src/components/LockNavigation/LockNavigationContextProvider.tsx @@ -22,7 +22,7 @@ export interface LockNavigationContextProviderProps { export default function LockNavigationContextProvider({ children, }: LockNavigationContextProviderProps): JSX.Element { - const {t} = useTranslation() + const {t} = useTranslation(["components", "common"]) const navigate = useNavigate() const [isLocked, setIsLocked] = useState(false) @@ -97,18 +97,18 @@ export default function LockNavigationContextProvider({ {children} - {t("components.LockNavigationContextProvider.title")} + {t("LockNavigationContextProvider.title")} - {t("components.LockNavigationContextProvider.description")} + {t("LockNavigationContextProvider.description")} diff --git a/src/components/widgets/ErrorLoadingDataMessage.tsx b/src/components/widgets/ErrorLoadingDataMessage.tsx index 1072813..0029164 100644 --- a/src/components/widgets/ErrorLoadingDataMessage.tsx +++ b/src/components/widgets/ErrorLoadingDataMessage.tsx @@ -12,7 +12,7 @@ export default function ErrorLoadingDataMessage({ message, onRetry, }: ErrorLoadingDataMessageProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") return ( @@ -20,9 +20,7 @@ export default function ErrorLoadingDataMessage({ {message} - + ) diff --git a/src/components/widgets/OpenMailButton.tsx b/src/components/widgets/OpenMailButton.tsx index dbc1715..83476b9 100644 --- a/src/components/widgets/OpenMailButton.tsx +++ b/src/components/widgets/OpenMailButton.tsx @@ -12,14 +12,14 @@ export interface OpenMailButtonProps { } export default function OpenMailButton({domain}: OpenMailButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const userAgent = new UAParser() if (userAgent.getOS().name === "Android" && APP_LINK_MAP[domain]) { return ( ) } diff --git a/src/components/widgets/StringPoolField/AddNewDialog.tsx b/src/components/widgets/StringPoolField/AddNewDialog.tsx index 9ef899f..1c830e6 100644 --- a/src/components/widgets/StringPoolField/AddNewDialog.tsx +++ b/src/components/widgets/StringPoolField/AddNewDialog.tsx @@ -28,22 +28,22 @@ export default function AddNewDialog({ open = false, onClose, }: StringPoolFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation(["components", "common"]) const [value, setValue] = useState("") return ( - {t("components.StringPoolField.forms.addNew.title")} + {t("StringPoolField.forms.addNew.title")} - {t("components.StringPoolField.forms.addNew.description")} + {t("StringPoolField.forms.addNew.description")} setValue(e.target.value)} - label={t("components.StringPoolField.forms.addNew.label")} + label={t("StringPoolField.forms.addNew.label")} name="addNew" fullWidth autoFocus @@ -53,14 +53,14 @@ export default function AddNewDialog({ diff --git a/src/components/widgets/StringPoolField/StringPoolField.tsx b/src/components/widgets/StringPoolField/StringPoolField.tsx index 1263769..7f47860 100644 --- a/src/components/widgets/StringPoolField/StringPoolField.tsx +++ b/src/components/widgets/StringPoolField/StringPoolField.tsx @@ -51,7 +51,7 @@ export default function StringPoolField({ fullWidth, ...props }: StringPoolFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const reversedPoolsMap = useMemo( () => Object.fromEntries(Object.entries(pools).map(([key, value]) => [value, key])), @@ -155,9 +155,7 @@ export default function StringPoolField({ - + )} diff --git a/src/components/widgets/TimedButton.tsx b/src/components/widgets/TimedButton.tsx index bdcd711..0579de8 100644 --- a/src/components/widgets/TimedButton.tsx +++ b/src/components/widgets/TimedButton.tsx @@ -18,7 +18,7 @@ export default function TimedButton({ disabled: parentDisabled = false, ...props }: TimedButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const [startDate, resetInterval] = useIntervalUpdate(1000) @@ -35,9 +35,7 @@ export default function TimedButton({ }} > {children} - {secondsLeft > 0 && ( - {t("components.TimedButton.remainingTime", {count: secondsLeft})} - )} + {secondsLeft > 0 && {t("TimedButton.remainingTime", {count: secondsLeft})}} ) } diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx index 3d52a46..152a1ae 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ResendMailButton.tsx @@ -19,8 +19,8 @@ export default function ResendMailButton({ email, sameRequestToken, }: ResendMailButtonProps): ReactElement { + const {t} = useTranslation("components") const settings = useLoaderData() as ServerSettings - const {t} = useTranslation() const mutation = useMutation(() => resendEmailLoginCode({ @@ -37,7 +37,7 @@ export default function ResendMailButton({ startIcon={} onClick={() => mutate()} > - {t("components.ResendMailButton.label")} + {t("ResendMailButton.label")} diff --git a/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx b/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx index a6426dc..81f1451 100644 --- a/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx +++ b/src/route-widgets/SignupRoute/YouGotMail/ResendMailButton.tsx @@ -19,7 +19,7 @@ export default function ResendMailButton({ email, onEmailAlreadyVerified, }: ResendMailButtonProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("components") const settings = useLoaderData() as ServerSettings const mutation = useMutation( @@ -41,7 +41,7 @@ export default function ResendMailButton({ startIcon={} onClick={() => mutate()} > - {t("components.ResendMailButton.label")} + {t("ResendMailButton.label")} From 126eb58c9683727d96e476b3dabbd2e6a88fef4b Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 11:33:09 +0100 Subject: [PATCH 20/22] refactor: Improve i18n for extension --- public/locales/en-US/extension.json | 12 ++++++++++++ .../PasswordShareConfirmationDialog.tsx | 18 +++++++----------- 2 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 public/locales/en-US/extension.json diff --git a/public/locales/en-US/extension.json b/public/locales/en-US/extension.json new file mode 100644 index 0000000..c8f1efe --- /dev/null +++ b/public/locales/en-US/extension.json @@ -0,0 +1,12 @@ +{ + "sharePassword": { + "title": "Share Password?", + "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", + "warning": "THIS WILL SHARE YOUR PASSWORD WITH THE EXTENSION. ALL YOUR DATA CAN BE DECRYPTED USING IT. ONLY CONTINUE IF YOU TRUST THE EXTENSION AND IF YOU INITIATED THIS REQUEST.", + + "sharePassword": "Share Password", + "doNotShare": "Do not share", + "decideLater": "Decide later", + "doNotAskAgain": "Do not ask again" + } +} diff --git a/src/components/AuthContext/PasswordShareConfirmationDialog.tsx b/src/components/AuthContext/PasswordShareConfirmationDialog.tsx index b1a92f2..bd43330 100644 --- a/src/components/AuthContext/PasswordShareConfirmationDialog.tsx +++ b/src/components/AuthContext/PasswordShareConfirmationDialog.tsx @@ -25,32 +25,28 @@ export default function PasswordShareConfirmationDialog({ onShare, onClose, }: PasswordShareConfirmationDialogProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("extension") return ( onClose(false)} maxWidth="sm" fullWidth={false}> - {t("components.passwordShareConfirmationDialog.title")} + {t("sharePassword.title")} - - {t("components.passwordShareConfirmationDialog.description")} - + {t("sharePassword.description")} - - {t("components.passwordShareConfirmationDialog.warning")} - + {t("sharePassword.warning")} From f289a2340dcffad8c019854600f08d9496c7164f Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 11:38:04 +0100 Subject: [PATCH 21/22] refactor: Fix i18n --- public/locales/en-US/alias-notes.json | 5 ++++- .../AliasDetailRoute/AddWebsiteField.tsx | 20 +++++++++---------- .../AliasPercentageAmount.tsx | 4 ++-- .../RandomAliasGenerator.tsx | 8 +++----- .../AdminUserPicker.tsx | 4 ++-- .../Settings2FARoute/Delete2FA.tsx | 2 +- 6 files changed, 21 insertions(+), 22 deletions(-) diff --git a/public/locales/en-US/alias-notes.json b/public/locales/en-US/alias-notes.json index 70a2bd5..8dca49e 100644 --- a/public/locales/en-US/alias-notes.json +++ b/public/locales/en-US/alias-notes.json @@ -24,7 +24,10 @@ "label": "Websites", "emptyText": "You haven't used this alias on any site yet.", "placeholder": "https://example.com", - "helperText": "Add a website to this alias. Used to autofill." + "helperText": "Add a website to this alias. Used to autofill.", + "errors": { + "invalid": "This URL is invalid." + } } } } diff --git a/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx b/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx index 52d08e4..42d2fc4 100644 --- a/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx +++ b/src/route-widgets/AliasDetailRoute/AddWebsiteField.tsx @@ -18,14 +18,14 @@ interface WebsiteForm { url: string } -const WEBSITE_SCHEMA = yup.object().shape({ - url: yup.string().matches(URL_REGEX, "This URL is invalid."), -}) - export default function AddWebsiteField({onAdd, isLoading}: AddWebsiteFieldProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("alias-notes") + + const schema = yup.object().shape({ + url: yup.string().matches(URL_REGEX, t("form.websites.error.invalid") as string), + }) const websiteFormik = useFormik({ - validationSchema: WEBSITE_SCHEMA, + validationSchema: schema, initialValues: { url: "", }, @@ -58,10 +58,8 @@ export default function AddWebsiteField({onAdd, isLoading}: AddWebsiteFieldProps {(websiteFormik.touched.url && websiteFormik.errors.url) || - t("routes.AliasDetailRoute.sections.notes.form.websites.helperText")} + t("form.websites.helperText")} diff --git a/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx b/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx index 117a1da..6780bc1 100644 --- a/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx +++ b/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx @@ -14,14 +14,14 @@ export default function AliasesPercentageAmount({ length, percentage, }: AliasPercentageAmountProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-global-settings") const amount = Math.floor(Math.pow(characters.length, length) * percentage) return ( - {t("routes.AdminRoute.settings.randomAliasesIncreaseExplanation", { + {t("randomAliasesIncreaseExplanation", { originalLength: length, increasedLength: length + 1, amount, diff --git a/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx b/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx index 37cb4b6..d2be605 100644 --- a/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx +++ b/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx @@ -18,7 +18,7 @@ export default function RandomAliasGenerator({ length, }: RandomAliasGeneratorProps): ReactElement { const serverSettings = useLoaderData() as ServerSettings - const {t} = useTranslation() + const {t} = useTranslation("admin-global-settings") const theme = useTheme() const generateLocal = useCallback( @@ -39,7 +39,7 @@ export default function RandomAliasGenerator({ return ( - {t("routes.AdminRoute.settings.randomAliasesPreview.title")} + {t("randomAliasesPreview.title")} @@ -51,9 +51,7 @@ export default function RandomAliasGenerator({ - - {t("routes.AdminRoute.settings.randomAliasesPreview.helperText")} - + {t("randomAliasesPreview.helperText")} ) } diff --git a/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx b/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx index a33e68f..b857356 100644 --- a/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx +++ b/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx @@ -17,7 +17,7 @@ export default function AdminUserPicker({ onPick, alreadyPicked, }: AdminUserPickerProps): ReactElement { - const {t} = useTranslation() + const {t} = useTranslation("admin-reserved-aliases") const meUser = useUser() const {data: {users: availableUsers} = {}} = useQuery( ["getAdminUsers"], @@ -54,7 +54,7 @@ export default function AdminUserPicker({ {users.map(user => ( {user.id === meUser?.id - ? t("routes.AdminRoute.forms.reservedAliases.fields.users.me", { + ? t("fields.users.me", { email: user.email.address, }) : user.email.address} diff --git a/src/route-widgets/Settings2FARoute/Delete2FA.tsx b/src/route-widgets/Settings2FARoute/Delete2FA.tsx index 146554d..b1c3ea9 100644 --- a/src/route-widgets/Settings2FARoute/Delete2FA.tsx +++ b/src/route-widgets/Settings2FARoute/Delete2FA.tsx @@ -21,7 +21,7 @@ export default function Delete2FA({onSuccess}: Delete2FAProps): ReactElement { const {showSuccess, showError} = useErrorSuccessSnacks() const {mutate} = useMutation(delete2FA, { onSuccess: () => { - showSuccess(t("routes.SettingsRoute.2fa.delete.success")) + showSuccess(t("delete.success")) onSuccess() }, onError: showError, From 5c1b21bae376e14a9d2213f2278d87529077d2f9 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 5 Mar 2023 11:54:17 +0100 Subject: [PATCH 22/22] feat: Add German language (not completed yet) --- .../locales/de-DE/admin-global-settings.json | 71 +++ .../locales/de-DE/admin-reserved-aliases.json | 44 ++ public/locales/de-DE/admin.json | 12 + public/locales/de-DE/alias-notes.json | 33 ++ public/locales/de-DE/aliases.json | 84 ++++ public/locales/de-DE/common.json | 89 ++++ public/locales/de-DE/complete-account.json | 16 + public/locales/de-DE/components.json | 33 ++ public/locales/de-DE/decryption.json | 21 + public/locales/de-DE/extension.json | 12 + public/locales/de-DE/login.json | 35 ++ public/locales/de-DE/logout.json | 4 + public/locales/de-DE/recover-2fa.json | 10 + .../locales/de-DE/relay-service-detected.json | 6 + public/locales/de-DE/reports.json | 68 +++ public/locales/de-DE/settings-2fa.json | 33 ++ .../locales/de-DE/settings-preferences.json | 5 + public/locales/de-DE/settings.json | 7 + public/locales/de-DE/signup.json | 18 + public/locales/de-DE/translation.json | 419 +----------------- public/locales/de-DE/verify-email.json | 7 + 21 files changed, 630 insertions(+), 397 deletions(-) create mode 100644 public/locales/de-DE/admin-global-settings.json create mode 100644 public/locales/de-DE/admin-reserved-aliases.json create mode 100644 public/locales/de-DE/admin.json create mode 100644 public/locales/de-DE/alias-notes.json create mode 100644 public/locales/de-DE/aliases.json create mode 100644 public/locales/de-DE/common.json create mode 100644 public/locales/de-DE/complete-account.json create mode 100644 public/locales/de-DE/components.json create mode 100644 public/locales/de-DE/decryption.json create mode 100644 public/locales/de-DE/extension.json create mode 100644 public/locales/de-DE/login.json create mode 100644 public/locales/de-DE/logout.json create mode 100644 public/locales/de-DE/recover-2fa.json create mode 100644 public/locales/de-DE/relay-service-detected.json create mode 100644 public/locales/de-DE/reports.json create mode 100644 public/locales/de-DE/settings-2fa.json create mode 100644 public/locales/de-DE/settings-preferences.json create mode 100644 public/locales/de-DE/settings.json create mode 100644 public/locales/de-DE/signup.json create mode 100644 public/locales/de-DE/verify-email.json diff --git a/public/locales/de-DE/admin-global-settings.json b/public/locales/de-DE/admin-global-settings.json new file mode 100644 index 0000000..155606c --- /dev/null +++ b/public/locales/de-DE/admin-global-settings.json @@ -0,0 +1,71 @@ +{ + "title": "Global Settings", + "description": "Configure global settings for your instance.", + "updatedSuccessfullyMessage": "Settings have been saved successfully!", + "randomAliasesPreview": { + "title": "Random aliases will look like this", + "helperText": "This is just a preview. Those are not real aliases." + }, + "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.", + "resetLabel": "Reset to defaults", + "disabled": { + "title": "Global settings are disabled", + "description": "Global settings have been disabled. You can enable them in the configuration file." + }, + "fields": { + "randomEmailIdMinLength": { + "label": "Minimum random alias ID length", + "description": "The minimum length for randomly generated emails. The server will automatically increase the length if required so." + }, + "randomEmailIdChars": { + "label": "Random alias character pool", + "description": "Characters that are used to generate random emails." + }, + "randomEmailLengthIncreaseOnPercentage": { + "label": "Percentage of used aliases", + "description": "If the percentage of used random email IDs is higher than this value, the length of the random email ID will be increased. This is used to prevent spammers from guessing the email ID." + }, + "customEmailSuffixLength": { + "label": "Custom email suffix length", + "description": "The length of the custom email suffix." + }, + "customEmailSuffixChars": { + "label": "Custom email suffix character pool", + "description": "Characters that are used to generate custom email suffixes." + }, + "imageProxyStorageLifeTimeInHours": { + "label": "Image proxy storage lifetime", + "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.", + "unit_one": "hour", + "unit_other": "hours" + }, + "enableImageProxy": { + "label": "Enable image proxy", + "description": "If enabled, images will be proxied through the server. This enhances your user's privacy as their ip address will not be leaked." + }, + "enableImageProxyStorage": { + "label": "Enable image proxy storage", + "description": "If enabled, images will be stored on the server and forwarded to the user. This makes email tracking nearly impossible as every message will be marked as read instantly, which is obviously not true as a user is typically not able to view an email in just a few seconds after it has been sent. This will only affect new images." + }, + "userEmailEnableDisposableEmails": { + "label": "Enable disposable emails for new accounts", + "description": "If enabled, users will be able to use disposable emails when creating a new account. This will only affect new accounts." + }, + "userEmailEnableOtherRelays": { + "label": "Enable other relays for new accounts", + "description": "If enabled, users will be able to use other relays (such as SimpleLogin or DuckDuckGo's Email Tracking Protection) when creating a new account. This will only affect new accounts." + }, + "allowStatistics": { + "label": "Allow statistics", + "description": "If enabled, your instance will collect anonymous statistics and share them. They will only be stored locally on this instance but made public." + }, + "allowAliasDeletion": { + "label": "Allow alias deletion", + "description": "If enabled, users will be able to delete their aliases." + }, + "maxAliasesPerUser": { + "label": "Maximum aliases per user", + "description": "The maximum number of aliases a user can create. 0 means unlimited. Existing aliases will not be affected." + } + } +} diff --git a/public/locales/de-DE/admin-reserved-aliases.json b/public/locales/de-DE/admin-reserved-aliases.json new file mode 100644 index 0000000..21e9f96 --- /dev/null +++ b/public/locales/de-DE/admin-reserved-aliases.json @@ -0,0 +1,44 @@ +{ + "title": "Reserved Aliases", + "detailsTitle": "Reserved Alias Details", + "pageActions": { + "search": { + "placeholder": "Search for aliases" + } + }, + "actions": { + "create": { + "label": "Create new Reserved Alias" + }, + "delete": { + "label": "Delete Reserved Alias", + "description": "Are you sure you want to delete this reserved alias?", + "continueActionLabel": "Delete Reserved Alias" + } + }, + "userAmount_one": "Forwards to one user", + "userAmount_other": "Forwards to {{count}} users", + "emptyState": { + "title": "Create your first reserved alias", + "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com." + }, + "fields": { + "active": { + "label": "Active" + }, + "users": { + "label": "Users", + "me": "{{email}} (Me)" + } + }, + "createNew": { + "title": "Reserved Aliases", + "description": "Define what alias should forward to whom.", + "continueActionLabel": "Create Reserved Alias", + "explanation": { + "step1": "User from outside", + "step2": "Sends mail to", + "step4": "KleckRelay forwards to" + } + } +} diff --git a/public/locales/de-DE/admin.json b/public/locales/de-DE/admin.json new file mode 100644 index 0000000..fc3bebf --- /dev/null +++ b/public/locales/de-DE/admin.json @@ -0,0 +1,12 @@ +{ + "title": "Seite konfigurieren", + "routes": { + "reservedAliases": "Reservierte Aliase", + "settings": "Einstellungen" + }, + "serverStatus": { + "noRecentReports": "Es scheint, als gäbe es Probleme mit deinem Server. Cleanup Jobs wurden in den letzten Tagen nicht mehr ausgeführt. Der letzte Bereicht war am {{date}}.", + "error": "Es gab einen Fehler beim Ausführen des Cleanup Jobs {{relativeDescription}}. Bitte überprüfe die Logs für weitere Informationen.", + "success": "Alles okay mit deinem Server! Der letzte Cleanup Job {{relativeDescription}} wurde erfolgreich ausgeführt." + } +} diff --git a/public/locales/de-DE/alias-notes.json b/public/locales/de-DE/alias-notes.json new file mode 100644 index 0000000..8dca49e --- /dev/null +++ b/public/locales/de-DE/alias-notes.json @@ -0,0 +1,33 @@ +{ + "title": "Notes", + "form": { + "createdAt": { + "label": "Created at", + "empty": "Unavailable" + }, + "creationContext": { + "label": "Creation Context", + "values": { + "web": "Created on this instance", + "extension": "Created in the extension", + "extension-inline": "Created using the extension" + } + }, + "createdOn": { + "label": "Created on" + }, + "personalNotes": { + "label": "Personal Notes", + "helperText": "You can enter personal notes for this alias here. Notes are encrypted." + }, + "websites": { + "label": "Websites", + "emptyText": "You haven't used this alias on any site yet.", + "placeholder": "https://example.com", + "helperText": "Add a website to this alias. Used to autofill.", + "errors": { + "invalid": "This URL is invalid." + } + } + } +} diff --git a/public/locales/de-DE/aliases.json b/public/locales/de-DE/aliases.json new file mode 100644 index 0000000..832a2f7 --- /dev/null +++ b/public/locales/de-DE/aliases.json @@ -0,0 +1,84 @@ +{ + "title": "Aliases", + "detailsTitle": "Alias Details", + "isInCopyMode": "You are in copy mode. Click on an alias to copy it to your clipboard.", + "emptyState": { + "title": "Welcome to your Aliases!", + "description": "Create your first Alias to get started." + }, + "pageActions": { + "search": { + "placeholder": "Search for names" + }, + "searchFilter": { + "active": "Active", + "inactive": "Inactive" + }, + "typeFilter": { + "custom": "Custom made", + "random": "Randomly generated" + } + }, + "actions": { + "createRandomAlias": { + "title": "Create Random Alias" + }, + "createCustomAlias": { + "title": "Create Custom Alias", + "description": "You can define your own custom alias. Note that a random suffix will be added at the end to avoid duplicates.", + "continueActionLabel": "Create Alias" + }, + "delete": { + "label": "Delete Alias", + "description": "Are you sure you want to delete this alias?", + "continueActionLabel": "Delete Alias" + } + }, + "aliasTypeExplanation": { + "random": "This is a randomly generated alias", + "custom": "This is a custom-made alias" + }, + "settings": { + "title": "Settings", + "description": "These settings apply to this alias only. You can either set a value manually or refer to your default settings. Note that this does change in behavior. When you set a value to refer to your default setting, the alias will always use the latest value. So when you change your default setting, the alias will automatically use the new value.", + "continueActionLabel": "Save Settings", + "fields": { + "removeTrackers": { + "label": "Remove Trackers", + "helperText": "Remove single-pixel image trackers as well as url trackers." + }, + "createMailReport": { + "label": "Create Mail Reports", + "helperText": "Create reports of emails sent to aliases. Reports are end-to-end encrypted. Only you can access them." + }, + "proxyImages": { + "label": "Proxy Images", + "helperText": "Proxies images in your emails through this KleckRelay instance. This adds an extra layer of privacy. Images are loaded immediately after we receive the email. They then will be stored for some time (cache time). During that time, the image will be served from us. This means the sender has no idea you have opened the mail. After the cache time, the image is loaded from the sender, but it will be forwarded by us. This means the sender will not be able to access your IP address nor your browser data." + }, + "imageProxyFormat": { + "label": "Image File Type", + "values": { + "jpeg": "JPEG", + "png": "PNG", + "webp": "WEBP" + } + }, + "proxyUserAgent": { + "label": "Proxy User Agent", + "helperText": "An User Agent is a identifier each browser and email client sends when retrieving files, such as images. You can specify here what user agent you would like to be used when we forward it. User Agents are kept up-to-date.", + "values": { + "apple-mail": "Apple Mail", + "google-mail": "Google Mail", + "outlook-windows": "Outlook / Windows", + "outlook-macos": "Outlook / MacOS", + "firefox": "Firefox Browser", + "chrome": "Chrome Browser" + } + }, + "expandUrlShorteners": { + "label": "Expand URL Shorteners", + "helperText": "Expand shortened URLs (for example bit.ly) to their original URL. This way those services can't track you." + } + } + } +} diff --git a/public/locales/de-DE/common.json b/public/locales/de-DE/common.json new file mode 100644 index 0000000..4c0504a --- /dev/null +++ b/public/locales/de-DE/common.json @@ -0,0 +1,89 @@ +{ + "fields": { + "email": { + "label": "Email", + "placeholder": "johndoe@example.com", + "errors": { + "disposable": "Disposable email addresses are not allowed." + } + }, + "2faCode": { + "label": "Code", + "placeholder": "123456", + "errors": { + "shouldOnlyBeDigits": "The code should only contain digits." + } + }, + "recoveryCode": { + "label": "Recovery Code" + }, + "password": { + "label": "Password", + "placeholder": "********", + "errors": { + "invalid": "Password is invalid." + } + }, + "passwordConfirmation": { + "label": "Confirm Password", + "placeholder": "********", + "errors": { + "mismatch": "Passwords do not match." + } + }, + "customAliasLocal": { + "label": "Address", + "placeholder": "awesome-fish" + }, + "local": { + "label": "Address" + }, + "search": { + "label": "Search" + } + }, + "messages": { + "errors": { + "unknown": "An unknown error occurred.", + "copyFailed": "Copying to clipboard did not work. Please copy the text manually." + }, + "alias": { + "addressCopied": "Address has been copied to your clipboard!", + "created": "Alias has been created successfully!", + "deleted": "Alias has been deleted!", + "updated": "Alias has been updated successfully!", + "changedToEnabled": "Alias has been enabled", + "changedToDisabled": "Alias has been disabled" + }, + "report": { + "deleted": "Report has been deleted!" + } + }, + "general": { + "cancelLabel": "Cancel", + "yesLabel": "Yes", + "noLabel": "No", + "continueLabel": "Continue", + "unavailableValue": "Unavailable", + "experimentalFeatureExplanation": "This is an experimental feature.", + "saveLabel": "Save", + "resetLabel": "Reset", + "loading": "Loading..." + }, + "noSearchResults": { + "title": "Nothing found", + "description": "We couldn't find anything for your search query. Try again with a different query." + }, + "navigation": { + "overview": "Overview", + "aliases": "Aliases", + "reports": "Reports", + "settings": "Settings", + "admin": "Admin" + }, + "routes": { + "signup": "Sign up", + "login": "Log in", + "logout": "Log out" + } +} diff --git a/public/locales/de-DE/complete-account.json b/public/locales/de-DE/complete-account.json new file mode 100644 index 0000000..8bc7792 --- /dev/null +++ b/public/locales/de-DE/complete-account.json @@ -0,0 +1,16 @@ +{ + "forms": { + "askForGeneration": { + "title": "Generate Email Reports?", + "description": "Would you like to create fully encrypted email reports for your mails? Only you will be able to access them. Not even we can decrypt them." + }, + "enterPassword": { + "title": "Set up your password", + "description": "Please enter a safe password so that we can encrypt your data." + } + }, + "alreadyCompleted": { + "title": "Encryption already enabled", + "description": "You already have encryption enabled. Changing passwords is currently not supported." + } +} diff --git a/public/locales/de-DE/components.json b/public/locales/de-DE/components.json new file mode 100644 index 0000000..c735d6d --- /dev/null +++ b/public/locales/de-DE/components.json @@ -0,0 +1,33 @@ +{ + "ResendMailButton": { + "label": "Resend Mail" + }, + "OpenMailButton": { + "label": "Open Mail" + }, + "TimedButton": { + "remainingTime_one": "({{count}})", + "remainingTime_other": "({{count}})" + }, + "ErrorLoadingDataMessage": { + "tryAgain": "Try Again" + }, + "LockNavigationContextProvider": { + "title": "Are you sure you want to leave?", + "description": "You have unsaved changes. If you leave, your changes will be lost.", + "continueLabel": "Leave" + }, + "StringPoolField": { + "addCustom": { + "label": "Add custom" + }, + "forms": { + "addNew": { + "title": "Add new value", + "description": "Enter your characters you would like to include", + "label": "Characters", + "submit": "Add" + } + } + } +} diff --git a/public/locales/de-DE/decryption.json b/public/locales/de-DE/decryption.json new file mode 100644 index 0000000..5a2147e --- /dev/null +++ b/public/locales/de-DE/decryption.json @@ -0,0 +1,21 @@ +{ + "actions": { + "enterDecryptionPassword": { + "title": "Decrypt Reports", + "description": "Please enter your password so that your reports can de decrypted.", + "cancelActionLabel": "Decrypt later" + }, + "passwordMissing": { + "unavailable": { + "title": "Encryption required", + "description": "You need to set up encryption to use this feature.", + "continueActionLabel": "Set up encryption" + }, + "passwordRequired": { + "title": "Password required", + "description": "Your decryption password is required to view this section.", + "continueActionLabel": "Enter password" + } + } + } +} diff --git a/public/locales/de-DE/extension.json b/public/locales/de-DE/extension.json new file mode 100644 index 0000000..c8f1efe --- /dev/null +++ b/public/locales/de-DE/extension.json @@ -0,0 +1,12 @@ +{ + "sharePassword": { + "title": "Share Password?", + "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", + "warning": "THIS WILL SHARE YOUR PASSWORD WITH THE EXTENSION. ALL YOUR DATA CAN BE DECRYPTED USING IT. ONLY CONTINUE IF YOU TRUST THE EXTENSION AND IF YOU INITIATED THIS REQUEST.", + + "sharePassword": "Share Password", + "doNotShare": "Do not share", + "decideLater": "Decide later", + "doNotAskAgain": "Do not ask again" + } +} diff --git a/public/locales/de-DE/login.json b/public/locales/de-DE/login.json new file mode 100644 index 0000000..899bd47 --- /dev/null +++ b/public/locales/de-DE/login.json @@ -0,0 +1,35 @@ +{ + "title": "Log in", + "forms": { + "email": { + "description": "We will send you a code to log in", + "continueActionLabel": "Send code" + }, + "confirmCode": { + "title": "You got mail!", + "description": "We sent you a code to your email. Enter it below to login", + "continueActionLabel": "Log in", + "allowLoginFromDifferentDevices": "Allow login from different devices", + "expiringSoonWarning": "Your code will expire in less than a minute.", + "fields": { + "code": { + "label": "Verification Code", + "errors": { + "invalidChars": "Invalid verification code" + } + } + } + }, + "confirmFromDifferentDevice": { + "title": "Login failed", + "description": "You could not be logged in. This could either be because you are not allowed to login from different devices or the verification code is invalid or expired." + }, + "otp": { + "title": "Two-Factor Authentication", + "description": "Enter the code from your authenticator app", + "isUnavailable": "Your OTP verification time expired or you exceeded the maximum number of attempts. Please log in again.", + "codesLostActionLabel": "I lost my codes", + "continueActionLabel": "Log in" + } + } +} diff --git a/public/locales/de-DE/logout.json b/public/locales/de-DE/logout.json new file mode 100644 index 0000000..ce11cd5 --- /dev/null +++ b/public/locales/de-DE/logout.json @@ -0,0 +1,4 @@ +{ + "title": "Log out", + "description": "We are logging you out..." +} diff --git a/public/locales/de-DE/recover-2fa.json b/public/locales/de-DE/recover-2fa.json new file mode 100644 index 0000000..66897eb --- /dev/null +++ b/public/locales/de-DE/recover-2fa.json @@ -0,0 +1,10 @@ +{ + "title": "Recover Two-Factor Authentication", + "description": "We are very sorry if you lost your codes. Please enter a recovery code to continue. Note that this will disable two-factor authentication for your account. You can enable it again in the settings.", + "continueActionLabel": "Disable 2FA", + "events": { + "unauthorized": "Please make sure to log in first and then reset your two-factor authentication on its screen.", + "canLogInNow": "Two-factor authentication has been disabled. You can log in now.", + "loggedIn": "Two-factor authentication has been disabled. You are logged in now." + } +} diff --git a/public/locales/de-DE/relay-service-detected.json b/public/locales/de-DE/relay-service-detected.json new file mode 100644 index 0000000..02ec783 --- /dev/null +++ b/public/locales/de-DE/relay-service-detected.json @@ -0,0 +1,6 @@ +{ + "title": "Email relay service detected", + "description": "We detected that you are using an email relay service to sign up. This KleckRelay instance does not support relaying to another email relay service. You can either choose a different instance or sign up with a different email address.", + "detectedExplanation": "Detected email relay:", + "closeActionLabel": "Got it" +} diff --git a/public/locales/de-DE/reports.json b/public/locales/de-DE/reports.json new file mode 100644 index 0000000..954a704 --- /dev/null +++ b/public/locales/de-DE/reports.json @@ -0,0 +1,68 @@ +{ + "title": "Reports", + "detailsTitle": "Report Details", + "emptyState": { + "title": "Welcome to your Reports!", + "description": "Here you will find your email reports. Currently, you don't have any reports. Wait until you receive an email." + }, + "pageActions": { + "sort": { + "label": "Sorting", + "values": { + "List": "List reports by their date", + "GroupByAlias": "Group reports by their alias" + } + } + }, + "emailMeta": { + "flow": "{{from}} -> {{to}}", + "emptySubject": "" + }, + "actions": { + "delete": { + "label": "Delete Report", + "description": "Are you sure you want to delete this report?", + "continueActionLabel": "Delete Report" + } + }, + "sections": { + "information": { + "title": "Email Information", + "form": { + "from": { + "label": "From" + }, + "to": { + "label": "To" + }, + "subject": { + "label": "Subject" + } + } + }, + "trackers": { + "title": "Trackers", + "results": { + "imageTrackers": { + "text_zero": "No image trackers found", + "text_one": "Removed 1 image tracker", + "text_other": "Removed {{count}} image trackers" + }, + "proxiedImages": { + "text_zero": "No images found", + "text_one": "Forwarding 1 image", + "text_other": "Forwarding {{count}} images", + "status": { + "isStored": "Stored on Server", + "isProxying": "Being forwarded" + } + }, + "expandedUrls": { + "text_zero": "No shortened URLs found", + "text_one": "Expanded 1 URL", + "text_other": "Expanded {{count}} URLs" + } + } + } + } +} diff --git a/public/locales/de-DE/settings-2fa.json b/public/locales/de-DE/settings-2fa.json new file mode 100644 index 0000000..fb4e637 --- /dev/null +++ b/public/locales/de-DE/settings-2fa.json @@ -0,0 +1,33 @@ +{ + "title": "Two-Factor-Authentication", + "alreadyEnabled": "You have successfully enabled 2FA!", + "setup": { + "description": "Enable 2FA to add an extra layer of security to your account. Each time you log in, you will need to enter a code generated from your authenticator app. This makes it harder for an attacker to hack into your account as they would need to have access to your phone.", + "setupLabel": "Enable 2FA", + "continueActionLabel": "Enable 2FA", + "codeExpired": "The verification time for your current Two-Factor-Authentication code has expired. A new code has been generated.", + "recoveryCodes": { + "title": "Note down your recovery codes", + "description": "These codes are used to recover your account if you lose access to your authenticator app. Note them down and store them in a safe place. You will not be able to view them again. Do not store them in your password manager. IF YOU LOSE YOUR RECOVERY CODES, YOU WILL LOSE ACCESS TO YOUR ACCOUNT. WE WILL NOT BE ABLE TO HELP YOU.", + "continueActionLabel": "I have noted down my recovery codes" + }, + "success": "You have successfully enabled 2FA!" + }, + "delete": { + "title": "Disable 2FA", + "steps": { + "askType": { + "code": "I have my 2FA code", + "recoveryCode": "I have a recovery code" + }, + "askCode": { + "label": "Code" + }, + "askRecoveryCode": { + "label": "Recovery Code" + } + }, + "continueActionLabel": "Disable 2FA", + "success": "You have successfully disabled 2FA!" + } +} diff --git a/public/locales/de-DE/settings-preferences.json b/public/locales/de-DE/settings-preferences.json new file mode 100644 index 0000000..fe30a4e --- /dev/null +++ b/public/locales/de-DE/settings-preferences.json @@ -0,0 +1,5 @@ +{ + "title": "Alias Preferences", + "description": "Select default values for your aliases. This only affects aliases you haven't set a custom value for.", + "continueActionLabel": "Save preferences" +} diff --git a/public/locales/de-DE/settings.json b/public/locales/de-DE/settings.json new file mode 100644 index 0000000..7ab1855 --- /dev/null +++ b/public/locales/de-DE/settings.json @@ -0,0 +1,7 @@ +{ + "title": "Settings", + "actions": { + "enable2fa": "Two-Factor-Authentication", + "aliasPreferences": "Alias Preferences" + } +} diff --git a/public/locales/de-DE/signup.json b/public/locales/de-DE/signup.json new file mode 100644 index 0000000..40e654c --- /dev/null +++ b/public/locales/de-DE/signup.json @@ -0,0 +1,18 @@ +{ + "forms": { + "email": { + "title": "Sign up", + "description": "We only need your email and you are ready to go!", + "continueActionLabel": "Continue" + }, + "mailVerification": { + "title": "You got mail!", + "description": "We sent you an email with a link to confirm your email address. Please check your inbox and click on the link to continue.", + "editEmail": { + "title": "Edit email address?", + "description": "Would you like to return to the previous step and edit your email address?", + "continueActionLabel": "Yes, edit email" + } + } + } +} diff --git a/public/locales/de-DE/translation.json b/public/locales/de-DE/translation.json index 583cbb8..a05b8e5 100644 --- a/public/locales/de-DE/translation.json +++ b/public/locales/de-DE/translation.json @@ -1,410 +1,35 @@ { "general": { - "cancelLabel": "Abbrechen", + "cancelLabel": "Cancel", "emptyValue": "-", - "emptyUnavailableValue": "Nicht verfügbar", + "emptyUnavailableValue": "Unavailable", + "saveLabel": "Save", - "defaultValueSelection": "Standard <{{value}}>", + "defaultValueSelection": "Default <{{value}}>", "defaultValueSelectionRaw": "<{{value}}>", "booleanSelection": { - "true": "Ja", - "false": "Nein" + "true": "Yes", + "false": "No" }, - "defaultError": "Ein Fehler ist aufgetreten.", - "defaultSuccess": "Erfolgreich übernommen!", - "loading": "Lädt...", - "actionNotUndoable": "Diese Aktion kann nicht rückgängig gemacht werden!", - "copyError": "Konnte nicht in Zwischenable kopieren. Bitte kopiere den Text manuell.", - "experimentalFeature": "Diese Funktion ist experimentell und kann Fehler verursachen." - }, - - "routes": { - "OverviewRoute": { - "title": "Überblick", - "description": "Nicht viel zu sehen hier, bisher." - }, - "LoginRoute": { - "forms": { - "email": { - "title": "Anmelden", - "description": "Wir senden dir einen Verifizierungscode an deine E-Mail Adresse.", - "continueAction": "Code senden", - "form": { - "email": { - "label": "E-Mail", - "placeholder": "maxmustermann@example.com" - } - } - }, - "confirmCode": { - "title": "Du hast Mail!", - "description": "Wir haben einen Code an deine E-Mail gesendet. Gib ihn hier ein, um dich anzumelden.", - "continueAction": "Anmelden", - "allowLoginFromDifferentDevices": "Anmelden von anderen Geräten erlauben", - "expiringSoon": "Dein Code läuft in weniger als einer Minute ab.", - "form": { - "code": { - "label": "Verifizierungscode", - "errors": { - "invalidChars": "Ungültiger Verifizierungscode" - } - } - } - }, - "confirmFromDifferentDevice": { - "title": "Anmelden fehlgeschlagen", - "description": "Du konntest nicht angemeldet werden. Dies könnte daran legen, dass du Anmeldeungen aus anderen Geräten nicht aktiviert hast oder der Verifizierungscode inkorrekt oder abgelaufen ist." - } - } - }, - "SignupRoute": { - "forms": { - "email": { - "title": "Registrieren", - "description": "Wir brauchen nur deine E-Mail und du kannst direkt loslegen!", - "continueAction": "Weiter", - "form": { - "email": { - "label": "E-Mail", - "placeholder": "maxmustermann@example.com" - } - } - }, - "mailVerification": { - "title": "Du hast Mail!", - "description": "Wir haben dir eine E-Mail mit einem Link geschickt, um deinen Account zu verifizieren. Bitte überprüfe deine E-Mails und klicke auf den Link, um fortzufahren.", - "editEmail": { - "title": "E-Mail ändern?", - "description": "Möchtest du einen Schritt zurückgehen um deine E-Mail zu ändern?", - "continueAction": "Ja, E-Mail ändern" - } - } - } - }, - "VerifyEmailRoute": { - "title": "Bestätige deine E-Mail", - "isLoading": "Deine E-Mail wird bestätigt...", - "isCodeInvalid": "Der Verifizierungscode ist ungültig oder abgelaufen.", - "errors": { - "code": { - "invalid": "Dieser Verifizierungscode ist ungültig" - } - } - }, - "CompleteAccountRoute": { - "forms": { - "generateReports": { - "title": "E-Mail-Berichte aktivieren?", - "description": "Möchtest du vollständig verschlüsselte Berichte für deine E-Mails erstellen lassen? Nur du wirst sie entschlüsseln können. Selbst wir können sie nicht entschlüsseln.", - "continueAction": "Ja", - "cancelAction": "Nein" - }, - "password": { - "title": "Passwort festlegen", - "description": "Bitte gib ein sicheres Passwort ein, damit wir deine Berichte verschlüsseln können.", - "continueAction": "Weiter", - "form": { - "password": { - "label": "Passwort", - "placeholder": "********" - }, - "passwordConfirm": { - "label": "Passwort bestätigen", - "placeholder": "Gib dein Passwort erneut ein", - "mustMatchHelperText": "Passwörter stimmen nicht überein." - } - } - }, - "available": { - "title": "Verschlüsselung bereits eingestellt", - "description": "Du hast die Verschlüsselung bereits eingestellt. Passwörter können momentan noch nicht geändert werden." - } - } - }, - "AliasesRoute": { - "title": "Aliase", - "isInCopyMode": "Du bist im Kopier-Modus. Klicke auf einen Alias um ihn in deine Zwischenablage zu kopieren.", - "emptyState": { - "title": "Willkommen zu deinen Aliases!", - "description": "Erstelle dein erstes Alias, um loszulegen." - }, - "pageActions": { - "search": { - "label": "Suche", - "placeholder": "Suche nach Namen" - } - }, - "actions": { - "createRandomAlias": { - "label": "Zufälliges Alias erstellen" - }, - "createCustomAlias": { - "label": "Eigenes Alias erstellen", - "description": "Du kannst dein eigenes Alias erstellen. Beachte das ein zufälliges Suffix angehangen wird, um Duplikate zu vermeiden.", - "continueAction": "Alias erstellen", - "form": { - "address": { - "label": "Adresse", - "placeholder": "awesome-fish" - } - } - } - } - }, - "AliasDetailRoute": { - "title": "Alias-Details", - "sections": { - "settings": { - "title": "Einstellungen", - "description": "Diese Einstellungen gelten nur für dieses Alias. Du kannst entweder einen manuellen Wert einstellen oder auf deine Standard-Werte verweisen. Beachte das dieser Wert das Verhalten ändert. Wenn du auf einen Standard-Wert verweist, verwendet dein Alias immer den aktuellsten Wert. Wenn du also deine Standard-Werte änderst, übernimmt dein Alias diese Änderungen." - }, - "notes": { - "title": "Notizen", - "form": { - "createdAt": { - "label": "Erstellungsdatum", - "empty": "Nicht verfügbar" - }, - "creationContext": { - "label": "Kontext", - "web": { - "label": "Auf dieser Instanz erstellt" - }, - "extension": { - "label": "In der Erweiterung erstellt" - }, - "extension-inline": { - "label": "Mit der Erweiterung erstellt" - } - }, - "createdOn": { - "label": "Erstellt auf" - }, - "personalNotes": { - "label": "Persönliche Notizen", - "empty": "-", - "helperText": "Hier kannst du persönliche Notizen für dieses Alias eingeben. Notizen sind verschlüsselt." - }, - "websites": { - "label": "Webseiten", - "emptyText": "Du hast dieses Alias auf keiner Webseite bisher genutzt.", - "placeholder": "https://example.com", - "helperText": "Füge eine Webseite zu diesem Alias hinzu. Wird verwendet um automatisch E-Mail-Felder auszufüllen." - } - } - } - } - }, - "ReportsRoute": { - "title": "Berichte", - "emptyState": { - "title": "Willkommen zu deinen Berichten!", - "description": "Hier kannst du deine E-Mail-Berichte finden. Momentan sind noch keine Berichte verfügbar. Warte, bis du eine E-Mail erhalten hast." - }, - "pageActions": { - "sort": { - "List": "Berichte anhand ihrer Daten auflisten", - "GroupByAlias": "Berichte nach Alias gruppieren" - } - } - }, - "ReportDetailRoute": { - "title": "Bericht-Details", - "actions": { - "delete": { - "label": "Bericht löschen", - "description": "Bist du dir sicher, dass du diesen Bericht löschen möchtest?", - "continueAction": "Bericht löschen" - } - }, - "sections": { - "information": { - "title": "Email-Informationen", - "form": { - "from": { - "label": "Von" - }, - "to": { - "label": "Zu" - }, - "subject": { - "label": "Betreff" - } - } - }, - "trackers": { - "title": "Tracker", - "results": { - "imageTrackers": { - "text_zero": "Keine Bild-Tracker gefunden", - "text_one": "Ein Bild-Tracker entfernt", - "text_other": "{{count}} Bild-Tracker entfernt" - }, - "proxiedImages": { - "text_zero": "Keine Bilder gefunden", - "text_one": "Ein Bild wird weitergeleitet", - "text_other": "{{count}} Bilder werden weitergeleitet", - "status": { - "isStored": "Auf Server gespeichert", - "isProxying": "Wird weitergeleitet" - } - }, - "expandedUrls": { - "text_zero": "Keine gekürzten URLs gefunden", - "text_one": "Eine URL entkürzt", - "text_other": "{{count}} URLs entkürzt" - } - } - } - } - }, - "SettingsRoute": { - "title": "Einstellungen", - "forms": { - "aliasPreferences": { - "title": "Alias-Präferenzen", - "description": "Wähle die Standard-Werte für deine Aliase aus. Dies betrifft nur Aliase, bei denen du keinen manuellen Wert gesetzt hast.", - "saveAction": "Präferenzen speichern" - } - } - }, - "LogoutRoute": { - "title": "Abmelden", - "description": "Wir sind dich am abmelden..." - } + "defaultError": "An error occurred.", + "defaultSuccess": "Success!", + "loading": "Loading...", + "actionNotUndoable": "This action cannot be undone!", + "copyError": "Copying to clipboard did not work. Please copy the text manually.", + "experimentalFeature": "This is an experimental feature.", + "deletedSuccessfully": "Deleted successfully!", + "appError": "We are sorry but there was an error. Please try again later." }, "components": { - "NavigationButton": { - "overview": "Überblick", - "aliases": "Aliase", - "reports": "Berichte", - "settings": "Einstellungen" - }, - "AuthenticateRoute": { - "signup": "Registrieren", - "login": "Anmelden" - }, - "AuthenticatedRoute": { - "logout": "Abmelden" - }, - "EnterDecryptionPassword": { - "title": "Berichte entschlüsseln", - "description": "Bitte gib dein Passwort ein, damit deine Berichte entschlüsselt werden können.", - "cancelAction": "Später entschlüsseln", - "continueAction": "Weiter", - "form": { - "password": { - "label": "Passwort", - "placeholder": "********", - "errors": { - "invalidPassword": "Das Passwort ist ungültig" - } - } - } - }, - "ResendMailButton": { - "label": "E-Mail erneut senden" - }, - "OpenMailButton": { - "label": "E-Mail öffnen" - }, - "DecryptionPasswordMissingAlert": { - "unavailable": { - "title": "Verschlüsselung benötigt", - "description": "Du musst die Verschlüsselung aktivieren, um dieses Feature nutzen zu können.", - "continueAction": "Verschlüsselung aktivieren" - }, - "passwordRequired": { - "title": "Passwort benötigt", - "description": "Dein Passwort wird benötigt, um dieses Feature nutzen zu können.", - "continueAction": "Passwort eingeben" - } - }, - "TimedButton": { - "remainingTime_one": "({{count}})", - "remainingTime_other": "({{count}})" - }, - "ErrorLoadingDataMessage": { - "tryAgain": "Neu laden" - }, - "AliasTypeIndicator": { - "random": "Dies ist ein zufällig-generiertes Alias", - "custom": "Dies ist ein benutzerdefiniertes Alias" - }, - "NoSearchResults": { - "title": "Keine Ergebnisse gefunden", - "description": "Wir konnten keine Ergebnisse für diese Suche finden. Versuche es mit einem anderen Suchbegriff." - }, - "LockNavigationContextProvider": { - "title": "Möchtest du wirklich die Seite verlassen?", - "description": "Du hast Änderungen, welche noch nicht gespeichert wurden. Wenn du jetzt diese Seite verlässt, gehen deine Änderungen verloren.", - "continueLabel": "Verlassen" - } - }, - - "relations": { - "alias": { - "mutations": { - "success": { - "aliasCreation": "Alias wurde erfolgreich erstellt!", - "aliasUpdated": "Alias wurde erfolgreich upgedatet!", - "notesUpdated": "Notizen wurden erfolgreich upgedated & verschlüsselt!", - "aliasChangedToEnabled": "Alias wurde aktiviert", - "aliasChangedToDisabled": "Alias wurde deaktiviert", - "addressCopiedToClipboard": "E-Mail-Adresse wurde in deine Zwischenablage kopiert!" - } - }, - "settings": { - "removeTrackers": { - "label": "Tracker entfernen", - "helperText": "Entferne Einzelpixel-Tracker und URL-Tracker" - }, - "createMailReports": { - "label": "E-Mail-Berichte erstellen", - "helperText": "Erstelle Berichte von E-Mails, die an Aliase gesendet werden. Berichte sind Ende-zu-Ende verschlüsselt. Nur du kannst sie entschlüsseln." - }, - "proxyImages": { - "label": "Bilder weiterleiten", - "helperText": "Leitet Bilder durch diese KleckRelay-Instanz weiter. Dies stellt einen weiteren Schutz deiner Privatspähre dar. Bilder werden direkt runtergeladen nachdem wir eine E-Mail erhalten haben. Diese werden dann für eine gewisse Zeit auf dem Server gespeichert. Während dieser Zeit wird das Bild von uns an dich gesendet. Dies bedeutet, dass der Absender keine Chance haben wird, herauszufinden, dass du diese E-Mail geöffnet hast. Nach der Zeit wird das Bild vom Absender geladen, aber durch uns weitergeleitet. Dies bedeutet, dass der Absender weder auf deine IP-Adresse, noch auf deine Browserdaten zugreifen kann." - }, - "imageProxyFormat": { - "label": "Bild-Format", - "enumTexts": { - "jpeg": "JPEG", - "png": "PNG", - "webp": "WEBP" - } - }, - "proxyUserAgent": { - "label": "Bild-Weiterleitungs-User-Agent", - "helperText": "Ein User-Agent ist eine Kennzeichnung, die jeden Browser und E-Mail-Client identifiziert, wenn Dateien runtergeladen werden, so wie beispielsweise Bilder. Du kannst hier einstellen, welchen User-Agent du beim Weiterleiten verwenden möchtest. User-Agents werden aktuell gehalten.", - "enumTexts": { - "apple-mail": "Apple Mail", - "google-mail": "Google Mail", - "outlook-windows": "Outlook / Windows", - "outlook-macos": "Outlook / MacOS", - "firefox": "Firefox Browser", - "chrome": "Chrome Browser" - } - }, - "expandUrlShorteners": { - "label": "URL-Kürzer entkürzen", - "helperText": "Entkürzt URl-Kürzerer (wie zum Beispiel bit.ly) zu der Original-URL. Dadurch können dich diese Services nicht mehr tracken." - }, - "saveAction": "Einstellungen speichern" - } - }, - "report": { - "mutations": { - "success": { - "reportDeleted": "Bericht wurde gelöscht!" - } - }, - "emailMeta": { - "flow": "{{from}} -> {{to}}", - "emptySubject": "" - } + "passwordShareConfirmationDialog": { + "title": "Share Password?", + "description": "An extension is asking for your password. Do you want to share it? Only continue if you initiated this action.", + "warning": "THIS WILL SHARE YOUR PASSWORD WITH THE EXTENSION. ALL YOUR DATA CAN BE DECRYPTED USING IT. ONLY CONTINUE IF YOU TRUST THE EXTENSION AND IF YOU INITIATED THIS REQUEST.", + "continueAction": "Share Password", + "doNotShare": "Do not share", + "decideLater": "Decide later", + "doNotAskAgain": "Do not ask again" } } } diff --git a/public/locales/de-DE/verify-email.json b/public/locales/de-DE/verify-email.json new file mode 100644 index 0000000..98a10ef --- /dev/null +++ b/public/locales/de-DE/verify-email.json @@ -0,0 +1,7 @@ +{ + "title": "Verify your email", + "isLoading": "We are verifying your email address...", + "errors": { + "invalid": "The verification link is invalid or has expired." + } +}