introduce translated component to give a hint on the used i18n key

This commit is contained in:
Max Peintner
2025-06-19 14:05:42 +02:00
parent d557cb51d7
commit 23062bcf2e
18 changed files with 118 additions and 44 deletions

View File

@@ -3,6 +3,7 @@ import { BackButton } from "@/components/back-button";
import { ChooseAuthenticatorToSetup } from "@/components/choose-authenticator-to-setup";
import { DynamicTheme } from "@/components/dynamic-theme";
import { SignInWithIdp } from "@/components/sign-in-with-idp";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
@@ -27,7 +28,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "authenticator" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, requestId, organization, sessionId } = searchParams;
@@ -99,7 +99,11 @@ export default async function Page(props: {
!sessionWithData.factors ||
!sessionWithData.factors.user
) {
return <Alert>{tError("unknownContext")}</Alert>;
return (
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
);
}
const branding = await getBrandingSettings({

View File

@@ -19,7 +19,9 @@ export default function Error({ error, reset }: any) {
<strong className="font-bold">Error:</strong> {error?.message}
</div>
<div>
<Button onClick={() => reset()}>{t("tryagain")}</Button>
<Button data-i18n-key="error.tryagain" onClick={() => reset()}>
{t("tryagain")}
</Button>
</div>
</div>
</Boundary>

View File

@@ -1,5 +1,6 @@
import { DynamicTheme } from "@/components/dynamic-theme";
import { SignInWithIdp } from "@/components/sign-in-with-idp";
import { Translated } from "@/components/translated";
import { UsernameForm } from "@/components/username-form";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import {
@@ -9,15 +10,12 @@ import {
getLoginSettings,
} from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { getLocale, getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "loginname" });
const loginName = searchParams?.loginName;
const requestId = searchParams?.requestId;
@@ -63,8 +61,12 @@ export default async function Page(props: {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>{t("title")}</h1>
<p className="ztdl-p">{t("description")}</p>
<h1 data-i18n-key="error.tryagain">
<Translated i18nKey="title" namespace="loginname" />
</h1>
<p className="ztdl-p">
<Translated i18nKey="description" namespace="loginname" />
</p>
<UsernameForm
loginName={loginName}

View File

@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
import { BackButton } from "@/components/back-button";
import { ChooseSecondFactor } from "@/components/choose-second-factor";
import { DynamicTheme } from "@/components/dynamic-theme";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
@@ -20,7 +21,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "mfa" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, requestId, organization, sessionId } = searchParams;
@@ -103,7 +103,11 @@ export default async function Page(props: {
></UserAvatar>
)}
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
{!(loginName || sessionId) && (
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
)}
{sessionFactors ? (
<ChooseSecondFactor

View File

@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
import { BackButton } from "@/components/back-button";
import { ChooseSecondFactorToSetup } from "@/components/choose-second-factor-to-setup";
import { DynamicTheme } from "@/components/dynamic-theme";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
@@ -40,7 +41,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "mfa" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, checkAfter, force, requestId, organization, sessionId } =
searchParams;
@@ -132,9 +132,17 @@ export default async function Page(props: {
></UserAvatar>
)}
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
{!(loginName || sessionId) && (
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
)}
{!valid && <Alert>{tError("sessionExpired")}</Alert>}
{!valid && (
<Alert>
<Translated i18nKey="sessionExpired" namespace="error" />
</Alert>
)}
{isSessionValid(sessionWithData).valid &&
loginSettings &&

View File

@@ -1,6 +1,7 @@
import { Alert } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { LoginOTP } from "@/components/login-otp";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
@@ -21,7 +22,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "otp" });
const tError = await getTranslations({ locale, namespace: "error" });
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
@@ -94,7 +94,9 @@ export default async function Page(props: {
{!session && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}

View File

@@ -3,6 +3,7 @@ import { BackButton } from "@/components/back-button";
import { Button, ButtonVariants } from "@/components/button";
import { DynamicTheme } from "@/components/dynamic-theme";
import { TotpRegister } from "@/components/totp-register";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
@@ -27,7 +28,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "otp" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, organization, sessionId, requestId, checkAfter } =
searchParams;
@@ -131,7 +131,9 @@ export default async function Page(props: {
<h1>{t("set.title")}</h1>
{!session && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}

View File

@@ -1,6 +1,7 @@
import { Alert } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { LoginPasskey } from "@/components/login-passkey";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
@@ -15,7 +16,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "passkey" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, altPassword, requestId, organization, sessionId } =
searchParams;
@@ -67,7 +67,11 @@ export default async function Page(props: {
)}
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
{!(loginName || sessionId) && (
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
)}
{(loginName || sessionId) && (
<LoginPasskey

View File

@@ -1,6 +1,7 @@
import { Alert, AlertType } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { RegisterPasskey } from "@/components/register-passkey";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
@@ -14,7 +15,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "passkey" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, prompt, organization, requestId, userId } = searchParams;
@@ -64,7 +64,9 @@ export default async function Page(props: {
{!session && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}

View File

@@ -1,6 +1,7 @@
import { Alert } from "@/components/alert";
import { ChangePasswordForm } from "@/components/change-password-form";
import { DynamicTheme } from "@/components/dynamic-theme";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
@@ -21,7 +22,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "password" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, organization, requestId } = searchParams;
@@ -61,7 +61,9 @@ export default async function Page(props: {
{(!sessionFactors || !loginName) &&
!loginSettings?.ignoreUnknownUsernames && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}
@@ -86,7 +88,9 @@ export default async function Page(props: {
/>
) : (
<div className="py-4">
<Alert>{tError("failedLoading")}</Alert>
<Alert>
<Translated i18nKey="failedLoading" namespace="error" />
</Alert>
</div>
)}
</div>

View File

@@ -1,6 +1,7 @@
import { Alert } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { PasswordForm } from "@/components/password-form";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
@@ -11,17 +12,12 @@ import {
} from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { getLocale, getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "password" });
const tError = await getTranslations({ locale, namespace: "error" });
let { loginName, organization, requestId, alt } = searchParams;
const _headers = await headers();
@@ -66,15 +62,21 @@ export default async function Page(props: {
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>
{sessionFactors?.factors?.user?.displayName ?? t("verify.title")}
{sessionFactors?.factors?.user?.displayName ?? (
<Translated i18nKey="verify.title" namespace="password" />
)}
</h1>
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
<p className="ztdl-p mb-6 block">
<Translated i18nKey="verify.description" namespace="password" />
</p>
{/* show error only if usernames should be shown to be unknown */}
{(!sessionFactors || !loginName) &&
!loginSettings?.ignoreUnknownUsernames && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}

View File

@@ -1,6 +1,7 @@
import { Alert, AlertType } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { SetPasswordForm } from "@/components/set-password-form";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
@@ -21,7 +22,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "password" });
const tError = await getTranslations({ locale, namespace: "error" });
const { userId, loginName, organization, requestId, code, initial } =
searchParams;
@@ -79,7 +79,9 @@ export default async function Page(props: {
{/* show error only if usernames should be shown to be unknown */}
{loginName && !session && !loginSettings?.ignoreUnknownUsernames && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}
@@ -115,7 +117,9 @@ export default async function Page(props: {
/>
) : (
<div className="py-4">
<Alert>{tError("failedLoading")}</Alert>
<Alert>
<Translated i18nKey="failedLoading" namespace="error" />
</Alert>
</div>
)}
</div>

View File

@@ -2,6 +2,7 @@ import { Alert } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { RegisterForm } from "@/components/register-form";
import { SignInWithIdp } from "@/components/sign-in-with-idp";
import { Translated } from "@/components/translated";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import {
getActiveIdentityProviders,
@@ -22,7 +23,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "register" });
const tError = await getTranslations({ locale, namespace: "error" });
let { firstname, lastname, email, organization, requestId } = searchParams;
@@ -83,7 +83,11 @@ export default async function Page(props: {
<h1>{t("title")}</h1>
<p className="ztdl-p">{t("description")}</p>
{!organization && <Alert>{tError("unknownContext")}</Alert>}
{!organization && (
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
)}
{legal &&
passwordComplexitySettings &&

View File

@@ -1,6 +1,7 @@
import { Alert } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { LoginPasskey } from "@/components/login-passkey";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
@@ -15,7 +16,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "u2f" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, requestId, sessionId, organization } = searchParams;
@@ -71,7 +71,11 @@ export default async function Page(props: {
)}
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
{!(loginName || sessionId) && <Alert>{tError("unknownContext")}</Alert>}
{!(loginName || sessionId) && (
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
)}
{(loginName || sessionId) && (
<LoginPasskey

View File

@@ -1,6 +1,7 @@
import { Alert } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { RegisterU2f } from "@/components/register-u2f";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
@@ -14,7 +15,6 @@ export default async function Page(props: {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "u2f" });
const tError = await getTranslations({ locale, namespace: "error" });
const { loginName, organization, requestId, checkAfter } = searchParams;
@@ -51,7 +51,9 @@ export default async function Page(props: {
{!sessionFactors && (
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
)}

View File

@@ -1,5 +1,6 @@
import { Alert, AlertType } from "@/components/alert";
import { DynamicTheme } from "@/components/dynamic-theme";
import { Translated } from "@/components/translated";
import { UserAvatar } from "@/components/user-avatar";
import { VerifyForm } from "@/components/verify-form";
import { sendEmailCode, sendInviteEmailCode } from "@/lib/server/verify";
@@ -14,7 +15,6 @@ export default async function Page(props: { searchParams: Promise<any> }) {
const searchParams = await props.searchParams;
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "verify" });
const tError = await getTranslations({ locale, namespace: "error" });
const { userId, loginName, code, organization, requestId, invite, send } =
searchParams;
@@ -130,7 +130,9 @@ export default async function Page(props: { searchParams: Promise<any> }) {
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
<div className="py-4">
<Alert>{tError("unknownContext")}</Alert>
<Alert>
<Translated i18nKey="unknownContext" namespace="error" />
</Alert>
</div>
</>
)}

View File

@@ -25,7 +25,9 @@ export default function GlobalError({
<span className="font-bold">Error:</span> {error?.message}
</div>
<div>
<Button onClick={() => reset()}>{t("tryagain")}</Button>
<Button data-i18n-key="error.tryagain" onClick={() => reset()}>
{t("tryagain")}
</Button>
</div>
</div>
</Boundary>

View File

@@ -0,0 +1,20 @@
import { useTranslations } from "next-intl";
export function Translated({
i18nKey,
children,
namespace,
...props
}: {
i18nKey: string;
children?: React.ReactNode;
namespace?: string;
} & React.HTMLAttributes<HTMLSpanElement>) {
const t = useTranslations(namespace);
return (
<span data-i18n-key={i18nKey} {...props}>
{t(i18nKey)}
</span>
);
}