feat: add i18n

This commit is contained in:
Stavros
2025-03-19 19:36:48 +02:00
parent 3ccc831a1f
commit fd32e737a3
44 changed files with 633 additions and 60 deletions

View File

@@ -6,6 +6,7 @@ import { Layout } from "../components/layouts/layout";
import { ReactNode } from "react";
import { isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
import { Trans, useTranslation } from "react-i18next";
export const ContinuePage = () => {
const queryString = window.location.search;
@@ -14,6 +15,7 @@ export const ContinuePage = () => {
const { isLoggedIn } = useUserContext();
const { disableContinue } = useAppContext();
const { t } = useTranslation();
if (!isLoggedIn) {
return <Navigate to={`/login?redirect_uri=${redirectUri}`} />;
@@ -25,8 +27,8 @@ export const ContinuePage = () => {
const redirect = () => {
notifications.show({
title: "Redirecting",
message: "You should be redirected to the app soon",
title: t("continueRedirectingTitle"),
message: t("continueRedirectingSubtitle"),
color: "blue",
});
setTimeout(() => {
@@ -42,12 +44,9 @@ export const ContinuePage = () => {
return (
<ContinuePageLayout>
<Text size="xl" fw={700}>
Invalid Redirect
</Text>
<Text>
The redirect URL is invalid, please contact the app owner to fix the
issue.
{t("Invalid redirect")}
</Text>
<Text>{t("The redirect URL is invalid")}</Text>
</ContinuePageLayout>
);
}
@@ -57,9 +56,9 @@ export const ContinuePage = () => {
return (
<ContinuePageLayout>
<Text size="xl" fw={700}>
Redirecting
{t("continueRedirectingTitle")}
</Text>
<Text>You should be redirected to your app soon.</Text>
<Text>{t("continueRedirectingSubtitle")}</Text>
</ContinuePageLayout>
);
}
@@ -68,14 +67,17 @@ export const ContinuePage = () => {
return (
<ContinuePageLayout>
<Text size="xl" fw={700}>
Insecure Redirect
{t("continueInsecureRedirectTitle")}
</Text>
<Text>
Your are trying to redirect from <Code>https</Code> to{" "}
<Code>http</Code>, are you sure you want to continue?
<Trans
i18nKey="continueInsecureRedirectSubtitle"
t={t}
components={{ Code: <Code /> }}
/>
</Text>
<Button fullWidth mt="xl" color="yellow" onClick={redirect}>
Continue
{t("continueTitle")}
</Button>
</ContinuePageLayout>
);
@@ -84,11 +86,11 @@ export const ContinuePage = () => {
return (
<ContinuePageLayout>
<Text size="xl" fw={700}>
Continue
{t("continueTitle")}
</Text>
<Text>Click the button to continue to your app.</Text>
<Text>{t("continueSubtitle")}</Text>
<Button fullWidth mt="xl" onClick={redirect}>
Continue
{t("continueTitle")}
</Button>
</ContinuePageLayout>
);

View File

@@ -1,19 +1,18 @@
import { Button, Paper, Text } from "@mantine/core";
import { Layout } from "../components/layouts/layout";
import { useTranslation } from "react-i18next";
export const InternalServerError = () => {
const { t } = useTranslation();
return (
<Layout>
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
<Text size="xl" fw={700}>
Internal Server Error
</Text>
<Text>
An error occured on the server and it currently cannot serve your
request.
{t("internalErrorTitle")}
</Text>
<Text>{t("internalErrorSubtitle")}</Text>
<Button fullWidth mt="xl" onClick={() => window.location.replace("/")}>
Try again
{t("internalErrorButton")}
</Button>
</Paper>
</Layout>

View File

@@ -10,6 +10,7 @@ import { LoginFormValues } from "../schemas/login-schema";
import { LoginForm } from "../components/auth/login-forn";
import { isQueryValid } from "../utils/utils";
import { useAppContext } from "../context/app-context";
import { useTranslation } from "react-i18next";
export const LoginPage = () => {
const queryString = window.location.search;
@@ -18,6 +19,7 @@ export const LoginPage = () => {
const { isLoggedIn } = useUserContext();
const { configuredProviders, title, genericName } = useAppContext();
const { t } = useTranslation();
const oauthProviders = configuredProviders.filter(
(value) => value !== "username",
@@ -33,8 +35,8 @@ export const LoginPage = () => {
},
onError: () => {
notifications.show({
title: "Failed to login",
message: "Check your username and password",
title: t("loginFailTitle"),
message: t("loginFailSubtitle"),
color: "red",
});
},
@@ -45,8 +47,8 @@ export const LoginPage = () => {
}
notifications.show({
title: "Logged in",
message: "Welcome back!",
title: t("loginSuccessTitle"),
message: t("loginSuccessSubtitle"),
color: "green",
});
@@ -69,15 +71,15 @@ export const LoginPage = () => {
},
onError: () => {
notifications.show({
title: "Internal error",
message: "Failed to get OAuth URL",
title: t("loginOauthFailTitle"),
message: t("loginOauthFailSubtitle"),
color: "red",
});
},
onSuccess: (data) => {
notifications.show({
title: "Redirecting",
message: "Redirecting to your OAuth provider",
title: t("loginOauthSuccessTitle"),
message: t("loginOauthSuccessSubtitle"),
color: "blue",
});
setTimeout(() => {
@@ -97,7 +99,7 @@ export const LoginPage = () => {
{oauthProviders.length > 0 && (
<>
<Text size="lg" fw={500} ta="center">
Welcome back, login with
{t("loginTitle")}
</Text>
<OAuthButtons
oauthProviders={oauthProviders}
@@ -107,7 +109,7 @@ export const LoginPage = () => {
/>
{configuredProviders.includes("username") && (
<Divider
label="Or continue with password"
label={t("loginDivider")}
labelPosition="center"
my="lg"
/>

View File

@@ -7,10 +7,12 @@ import { Navigate } from "react-router";
import { Layout } from "../components/layouts/layout";
import { capitalize } from "../utils/utils";
import { useAppContext } from "../context/app-context";
import { Trans, useTranslation } from "react-i18next";
export const LogoutPage = () => {
const { isLoggedIn, username, oauth, provider } = useUserContext();
const { genericName } = useAppContext();
const { t } = useTranslation();
if (!isLoggedIn) {
return <Navigate to="/login" />;
@@ -22,15 +24,15 @@ export const LogoutPage = () => {
},
onError: () => {
notifications.show({
title: "Failed to logout",
message: "Please try again",
title: t("logoutFailTitle"),
message: t("logoutFailSubtitle"),
color: "red",
});
},
onSuccess: () => {
notifications.show({
title: "Logged out",
message: "Goodbye!",
title: t("logoutSuccessTitle"),
message: t("logoutSuccessSubtitle"),
color: "green",
});
setTimeout(() => {
@@ -43,13 +45,30 @@ export const LogoutPage = () => {
<Layout>
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
<Text size="xl" fw={700}>
Logout
{t("logoutTitle")}
</Text>
<Text>
You are currently logged in as <Code>{username}</Code>
{oauth &&
` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}
. Click the button below to log out.
{oauth ? (
<Trans
i18nKey="logoutOauthSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{
provider:
provider === "generic" ? genericName : capitalize(provider),
username: username,
}}
/>
) : (
<Trans
i18nKey="logoutUsernameSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{
username: username,
}}
/>
)}
</Text>
<Button
fullWidth
@@ -57,7 +76,7 @@ export const LogoutPage = () => {
onClick={() => logoutMutation.mutate()}
loading={logoutMutation.isLoading}
>
Logout
{t("logoutTitle")}
</Button>
</Paper>
</Layout>

View File

@@ -1,16 +1,18 @@
import { Button, Paper, Text } from "@mantine/core";
import { Layout } from "../components/layouts/layout";
import { useTranslation } from "react-i18next";
export const NotFoundPage = () => {
const { t } = useTranslation();
return (
<Layout>
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
<Text size="xl" fw={700}>
Not found
{t("notFoundTitle")}
</Text>
<Text>The page you are looking for does not exist.</Text>
<Text>{t("notFoundSubtitle")}</Text>
<Button fullWidth mt="xl" onClick={() => window.location.replace("/")}>
Go home
{t("notFoundButton")}
</Button>
</Paper>
</Layout>

View File

@@ -7,6 +7,7 @@ import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { notifications } from "@mantine/notifications";
import { useAppContext } from "../context/app-context";
import { useTranslation } from "react-i18next";
export const TotpPage = () => {
const queryString = window.location.search;
@@ -15,6 +16,7 @@ export const TotpPage = () => {
const { totpPending, isLoggedIn } = useUserContext();
const { title } = useAppContext();
const { t } = useTranslation();
if (isLoggedIn) {
return <Navigate to={`/logout`} />;
@@ -30,15 +32,15 @@ export const TotpPage = () => {
},
onError: () => {
notifications.show({
title: "Failed to verify code",
message: "Please try again",
title: t("totpFailTitle"),
message: t("totpFailSubtitle"),
color: "red",
});
},
onSuccess: () => {
notifications.show({
title: "Verified",
message: "Redirecting to your app",
title: t("totpSuccessTitle"),
message: t("totpSuccessSubtitle"),
color: "green",
});
setTimeout(() => {
@@ -52,7 +54,7 @@ export const TotpPage = () => {
<Title ta="center">{title}</Title>
<Paper shadow="md" p="xl" mt={30} radius="md" withBorder>
<Text size="lg" fw={500} mb="md" ta="center">
Enter your TOTP code
{t("totpTitle")}
</Text>
<TotpForm
isLoading={totpMutation.isLoading}

View File

@@ -2,6 +2,7 @@ import { Button, Code, Paper, Text } from "@mantine/core";
import { Layout } from "../components/layouts/layout";
import { Navigate } from "react-router";
import { isQueryValid } from "../utils/utils";
import { Trans, useTranslation } from "react-i18next";
export const UnauthorizedPage = () => {
const queryString = window.location.search;
@@ -9,6 +10,8 @@ export const UnauthorizedPage = () => {
const username = params.get("username") ?? "";
const resource = params.get("resource") ?? "";
const { t } = useTranslation();
if (!isQueryValid(username)) {
return <Navigate to="/" />;
}
@@ -17,16 +20,26 @@ export const UnauthorizedPage = () => {
<Layout>
<Paper shadow="md" p={30} mt={30} radius="md" withBorder>
<Text size="xl" fw={700}>
Unauthorized
{t("Unauthorized")}
</Text>
<Text>
The user with username <Code>{username}</Code> is not authorized to{" "}
{isQueryValid(resource) ? (
<span>
access the <Code>{resource}</Code> resource.
</span>
<Text>
<Trans
i18nKey="unauthorizedResourceSubtitle"
t={t}
components={{ Code: <Code /> }}
values={{ resource, username }}
/>
</Text>
) : (
"login."
<Text>
<Trans
i18nKey="unauthorizedLoginSubtitle"
t={t}
values={{ username }}
/>
</Text>
)}
</Text>
<Button
@@ -34,7 +47,7 @@ export const UnauthorizedPage = () => {
mt="xl"
onClick={() => window.location.replace("/login")}
>
Try again
{t("unauthorizedButton")}
</Button>
</Paper>
</Layout>