diff --git a/apps/login/locales/de.json b/apps/login/locales/de.json index 36dc145120..f7b0d064ba 100644 --- a/apps/login/locales/de.json +++ b/apps/login/locales/de.json @@ -22,7 +22,8 @@ "loginname": { "title": "Willkommen zurück!", "description": "Geben Sie Ihre Anmeldedaten ein.", - "register": "Neuen Benutzer registrieren" + "register": "Neuen Benutzer registrieren", + "submit": "Weiter" }, "password": { "verify": { diff --git a/apps/login/locales/en.json b/apps/login/locales/en.json index 0b1cbeb472..6ce32d9833 100644 --- a/apps/login/locales/en.json +++ b/apps/login/locales/en.json @@ -22,7 +22,8 @@ "loginname": { "title": "Welcome back!", "description": "Enter your login data.", - "register": "Register new user" + "register": "Register new user", + "submit": "Continue" }, "password": { "verify": { diff --git a/apps/login/locales/es.json b/apps/login/locales/es.json index 5cd40f764a..b9a4140bce 100644 --- a/apps/login/locales/es.json +++ b/apps/login/locales/es.json @@ -22,7 +22,8 @@ "loginname": { "title": "¡Bienvenido de nuevo!", "description": "Introduce tus datos de acceso.", - "register": "Registrar nuevo usuario" + "register": "Registrar nuevo usuario", + "submit": "Continuar" }, "password": { "verify": { diff --git a/apps/login/locales/it.json b/apps/login/locales/it.json index a19aa91cfb..109ab15b52 100644 --- a/apps/login/locales/it.json +++ b/apps/login/locales/it.json @@ -22,7 +22,8 @@ "loginname": { "title": "Bentornato!", "description": "Inserisci i tuoi dati di accesso.", - "register": "Registrati come nuovo utente" + "register": "Registrati come nuovo utente", + "submit": "Continua" }, "password": { "verify": { diff --git a/apps/login/locales/pl.json b/apps/login/locales/pl.json index b97e7e4b47..0c664101ae 100644 --- a/apps/login/locales/pl.json +++ b/apps/login/locales/pl.json @@ -22,7 +22,8 @@ "loginname": { "title": "Witamy ponownie!", "description": "Wprowadź dane logowania.", - "register": "Zarejestruj nowego użytkownika" + "register": "Zarejestruj nowego użytkownika", + "submit": "Kontynuuj" }, "password": { "verify": { diff --git a/apps/login/locales/ru.json b/apps/login/locales/ru.json index 77ea8ba79e..c9fee46295 100644 --- a/apps/login/locales/ru.json +++ b/apps/login/locales/ru.json @@ -22,7 +22,8 @@ "loginname": { "title": "С возвращением!", "description": "Введите свои данные для входа.", - "register": "Зарегистрировать нового пользователя" + "register": "Зарегистрировать нового пользователя", + "submit": "Продолжить" }, "password": { "verify": { diff --git a/apps/login/locales/zh.json b/apps/login/locales/zh.json index 0ad9c7e056..1601d7b7bd 100644 --- a/apps/login/locales/zh.json +++ b/apps/login/locales/zh.json @@ -22,7 +22,8 @@ "loginname": { "title": "欢迎回来!", "description": "请输入您的登录信息。", - "register": "注册新用户" + "register": "注册新用户", + "submit": "继续" }, "password": { "verify": { diff --git a/apps/login/next.config.mjs b/apps/login/next.config.mjs index edf5e54595..dea34d603b 100755 --- a/apps/login/next.config.mjs +++ b/apps/login/next.config.mjs @@ -26,8 +26,6 @@ const secureHeaders = [ key: "X-XSS-Protection", value: "1; mode=block", }, - // img-src vercel.com needed for deploy button, - // script-src va.vercel-scripts.com for analytics/vercel scripts { key: "Content-Security-Policy", value: `${DEFAULT_CSP} frame-ancestors 'none'`, diff --git a/apps/login/package.json b/apps/login/package.json index b8afa2007f..bc52864e4d 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "scripts": { - "dev": "next dev --turbopack", + "dev": "next dev", "test": "concurrently --timings --kill-others-on-fail 'npm:test:unit' 'npm:test:integration'", "test:watch": "concurrently --kill-others 'npm:test:unit:watch' 'npm:test:integration:watch'", "test:unit": "vitest", @@ -45,10 +45,9 @@ "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", - "jose": "^5.3.0", "lucide-react": "0.469.0", "moment": "^2.29.4", - "next": "15.4.0-canary.3", + "next": "15.4.0-canary.86", "next-intl": "^3.25.1", "next-themes": "^0.2.1", "nice-grpc": "2.0.1", @@ -56,7 +55,6 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-hook-form": "7.39.5", - "swr": "^2.2.0", "tinycolor2": "1.4.2", "uuid": "^11.1.0" }, diff --git a/apps/login/src/app/(login)/accounts/page.tsx b/apps/login/src/app/(login)/accounts/page.tsx index 32c88d17bf..a1e99401e2 100644 --- a/apps/login/src/app/(login)/accounts/page.tsx +++ b/apps/login/src/app/(login)/accounts/page.tsx @@ -1,5 +1,6 @@ import { DynamicTheme } from "@/components/dynamic-theme"; import { SessionsList } from "@/components/sessions-list"; +import { Translated } from "@/components/translated"; import { getAllSessionCookieIds } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { @@ -9,7 +10,7 @@ import { } from "@/lib/zitadel"; import { UserPlusIcon } from "@heroicons/react/24/outline"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; @@ -33,7 +34,6 @@ export default async function Page(props: { }) { const searchParams = await props.searchParams; const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "accounts" }); const requestId = searchParams?.requestId; const organization = searchParams?.organization; @@ -71,8 +71,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

@@ -81,7 +85,9 @@ export default async function Page(props: {
- {t("addAnother")} + + +
diff --git a/apps/login/src/app/(login)/authenticator/set/page.tsx b/apps/login/src/app/(login)/authenticator/set/page.tsx index 95b89af92d..a339426c89 100644 --- a/apps/login/src/app/(login)/authenticator/set/page.tsx +++ b/apps/login/src/app/(login)/authenticator/set/page.tsx @@ -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"; @@ -17,7 +18,7 @@ import { listAuthenticationMethodTypes, } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; @@ -26,8 +27,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 +98,11 @@ export default async function Page(props: { !sessionWithData.factors || !sessionWithData.factors.user ) { - return {tError("unknownContext")}; + return ( + + + + ); } const branding = await getBrandingSettings({ @@ -165,9 +168,13 @@ export default async function Page(props: { return (
-

{t("title")}

+

+ +

-

{t("description")}

+

+ +

-

{t("linkWithIDP")}

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale }); const userCode = searchParams?.user_code; const requestId = searchParams?.requestId; const organization = searchParams?.organization; if (!userCode || !requestId) { - return
{t("error.noUserCode")}
; + return ( +
+ +
+ ); } const _headers = await headers(); @@ -34,7 +36,11 @@ export default async function Page(props: { }); if (!deviceAuthorizationRequest) { - return
{t("error.noDeviceRequest")}
; + return ( +
+ +
+ ); } let defaultOrganization; @@ -66,15 +72,19 @@ export default async function Page(props: {

- {t("device.request.title", { - appName: deviceAuthorizationRequest?.appName, - })} +

- {t("device.request.description", { - appName: deviceAuthorizationRequest?.appName, - })} +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "device" }); const userCode = searchParams?.user_code; const organization = searchParams?.organization; @@ -37,8 +35,12 @@ export default async function Page(props: { return (
-

{t("usercode.title")}

-

{t("usercode.description")}

+

+ +

+

+ +

diff --git a/apps/login/src/app/(login)/error.tsx b/apps/login/src/app/(login)/error.tsx index bee6516a59..d14150a4b4 100644 --- a/apps/login/src/app/(login)/error.tsx +++ b/apps/login/src/app/(login)/error.tsx @@ -2,7 +2,7 @@ import { Boundary } from "@/components/boundary"; import { Button } from "@/components/button"; -import { useTranslations } from "next-intl"; +import { Translated } from "@/components/translated"; import { useEffect } from "react"; export default function Error({ error, reset }: any) { @@ -10,8 +10,6 @@ export default function Error({ error, reset }: any) { console.log("logging error:", error); }, [error]); - const t = useTranslations("error"); - return (
@@ -19,7 +17,9 @@ export default function Error({ error, reset }: any) { Error: {error?.message}
- +
diff --git a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx index de6ad858d9..f2b7a19b91 100644 --- a/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/failure/page.tsx @@ -1,6 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { ChooseAuthenticatorToLogin } from "@/components/choose-authenticator-to-login"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { @@ -11,7 +12,6 @@ import { } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -19,8 +19,6 @@ export default async function Page(props: { params: Promise<{ provider: string }>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); const { organization, userId } = searchParams; @@ -77,8 +75,12 @@ export default async function Page(props: { return (
-

{t("loginError.title")}

- {t("loginError.description")} +

+ +

+ + + {userId && authMethods.length && ( <> diff --git a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx index bfbde8b252..ae9feff6b7 100644 --- a/apps/login/src/app/(login)/idp/[provider]/success/page.tsx +++ b/apps/login/src/app/(login)/idp/[provider]/success/page.tsx @@ -5,6 +5,7 @@ import { linkingFailed } from "@/components/idps/pages/linking-failed"; import { linkingSuccess } from "@/components/idps/pages/linking-success"; import { loginFailed } from "@/components/idps/pages/login-failed"; import { loginSuccess } from "@/components/idps/pages/login-success"; +import { Translated } from "@/components/translated"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { addHuman, @@ -27,7 +28,6 @@ import { AddHumanUserRequestSchema, UpdateHumanUserRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; @@ -73,8 +73,6 @@ export default async function Page(props: { }) { const params = await props.params; const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); let { id, token, requestId, organization, link } = searchParams; const { provider } = params; @@ -321,8 +319,12 @@ export default async function Page(props: { return (
-

{t("registerSuccess.title")}

-

{t("registerSuccess.description")}

+

+ +

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); const requestId = searchParams?.requestId; const organization = searchParams?.organization; @@ -33,8 +31,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

{identityProviders && ( >; }) { 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 (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "logout" }); const organization = searchParams?.organization; const postLogoutRedirectUri = searchParams?.post_logout_redirect_uri; @@ -67,8 +65,12 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

}) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "logout" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -33,8 +31,12 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("success.title")}

-

{t("success.description")}

+

+ +

+

+ +

); diff --git a/apps/login/src/app/(login)/mfa/page.tsx b/apps/login/src/app/(login)/mfa/page.tsx index c65d6d3058..5543cdf66f 100644 --- a/apps/login/src/app/(login)/mfa/page.tsx +++ b/apps/login/src/app/(login)/mfa/page.tsx @@ -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"; @@ -11,16 +12,12 @@ import { getSession, listAuthenticationMethodTypes, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { 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; @@ -90,9 +87,13 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

-

{t("verify.description")}

+

+ +

{sessionFactors && ( )} - {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} {sessionFactors ? ( ) : ( - {t("verify.noResults")} + + + )}
diff --git a/apps/login/src/app/(login)/mfa/set/page.tsx b/apps/login/src/app/(login)/mfa/set/page.tsx index c7f2fa6599..ebfa358d6d 100644 --- a/apps/login/src/app/(login)/mfa/set/page.tsx +++ b/apps/login/src/app/(login)/mfa/set/page.tsx @@ -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"; @@ -15,7 +16,6 @@ import { } from "@/lib/zitadel"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; function isSessionValid(session: Partial): { @@ -38,9 +38,6 @@ export default async function Page(props: { searchParams: Promise>; }) { 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; @@ -119,9 +116,13 @@ export default async function Page(props: { return (
-

{t("set.title")}

+

+ +

-

{t("set.description")}

+

+ +

{sessionWithData && ( )} - {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} - {!valid && {tError("sessionExpired")}} + {!valid && ( + + + + )} {isSessionValid(sessionWithData).valid && loginSettings && diff --git a/apps/login/src/app/(login)/otp/[method]/page.tsx b/apps/login/src/app/(login)/otp/[method]/page.tsx index ee58420c42..2d9daac64f 100644 --- a/apps/login/src/app/(login)/otp/[method]/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/page.tsx @@ -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"; @@ -10,7 +11,7 @@ import { getLoginSettings, getSession, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -20,8 +21,6 @@ export default async function Page(props: { const params = await props.params; 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); @@ -81,20 +80,30 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

{method === "time-based" && ( -

{t("verify.totpDescription")}

+

+ +

)} {method === "sms" && ( -

{t("verify.smsDescription")}

+

+ +

)} {method === "email" && ( -

{t("verify.emailDescription")}

+

+ +

)} {!session && (
- {tError("unknownContext")} + + +
)} diff --git a/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/apps/login/src/app/(login)/otp/[method]/set/page.tsx index d3fc4c89f7..f74093ce8e 100644 --- a/apps/login/src/app/(login)/otp/[method]/set/page.tsx +++ b/apps/login/src/app/(login)/otp/[method]/set/page.tsx @@ -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"; @@ -14,7 +15,6 @@ import { registerTOTP, } from "@/lib/zitadel"; import { RegisterTOTPResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -25,9 +25,6 @@ export default async function Page(props: { }) { const params = await props.params; 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; @@ -128,10 +125,14 @@ export default async function Page(props: { return (
-

{t("set.title")}

+

+ +

{!session && (
- {tError("unknownContext")} + + +
)} @@ -152,7 +153,12 @@ export default async function Page(props: { {totpResponse && "uri" in totpResponse && "secret" in totpResponse ? ( <> -

{t("set.totpRegisterDescription")}

+

+ +

- {t("set.submit")} +
diff --git a/apps/login/src/app/(login)/passkey/page.tsx b/apps/login/src/app/(login)/passkey/page.tsx index e24585e7e0..bef71986f3 100644 --- a/apps/login/src/app/(login)/passkey/page.tsx +++ b/apps/login/src/app/(login)/passkey/page.tsx @@ -1,21 +1,18 @@ 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"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise>; }) { 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; @@ -55,7 +52,9 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

{sessionFactors && ( )} -

{t("verify.description")}

+

+ +

- {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} {(loginName || sessionId) && ( >; }) { 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; @@ -37,7 +34,9 @@ export default async function Page(props: { return (
-

{t("set.title")}

+

+ +

{session && ( )} -

{t("set.description")}

+

+ +

- {t("set.info.description")} + - {t("set.info.link")} + {!session && (
- {tError("unknownContext")} + + +
)} diff --git a/apps/login/src/app/(login)/password/change/page.tsx b/apps/login/src/app/(login)/password/change/page.tsx index 05f8cd6a10..78ba88d282 100644 --- a/apps/login/src/app/(login)/password/change/page.tsx +++ b/apps/login/src/app/(login)/password/change/page.tsx @@ -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"; @@ -9,7 +10,6 @@ import { getLoginSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -19,9 +19,6 @@ export default async function Page(props: { const { serviceUrl } = getServiceUrlFromHeaders(_headers); 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; @@ -53,15 +50,21 @@ export default async function Page(props: {

- {sessionFactors?.factors?.user?.displayName ?? t("change.title")} + {sessionFactors?.factors?.user?.displayName ?? ( + + )}

-

{t("change.description")}

+

+ +

{/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
- {tError("unknownContext")} + + +
)} @@ -86,7 +89,9 @@ export default async function Page(props: { /> ) : (
- {tError("failedLoading")} + + +
)}
diff --git a/apps/login/src/app/(login)/password/page.tsx b/apps/login/src/app/(login)/password/page.tsx index 506454a275..56b0ee4473 100644 --- a/apps/login/src/app/(login)/password/page.tsx +++ b/apps/login/src/app/(login)/password/page.tsx @@ -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>; }) { 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: {

- {sessionFactors?.factors?.user?.displayName ?? t("verify.title")} + {sessionFactors?.factors?.user?.displayName ?? ( + + )}

-

{t("verify.description")}

+

+ +

{/* show error only if usernames should be shown to be unknown */} {(!sessionFactors || !loginName) && !loginSettings?.ignoreUnknownUsernames && (
- {tError("unknownContext")} + + +
)} diff --git a/apps/login/src/app/(login)/password/set/page.tsx b/apps/login/src/app/(login)/password/set/page.tsx index 26e065438c..b717fd5d96 100644 --- a/apps/login/src/app/(login)/password/set/page.tsx +++ b/apps/login/src/app/(login)/password/set/page.tsx @@ -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"; @@ -12,7 +13,7 @@ import { } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -20,8 +21,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; @@ -73,13 +72,21 @@ export default async function Page(props: { return (
-

{session?.factors?.user?.displayName ?? t("set.title")}

-

{t("set.description")}

+

+ {session?.factors?.user?.displayName ?? ( + + )} +

+

+ +

{/* show error only if usernames should be shown to be unknown */} {loginName && !session && !loginSettings?.ignoreUnknownUsernames && (
- {tError("unknownContext")} + + +
)} @@ -99,7 +106,11 @@ export default async function Page(props: { > ) : null} - {!initial && {t("set.codeSent")}} + {!initial && ( + + + + )} {passwordComplexity && (loginName ?? user?.preferredLoginName) && @@ -115,7 +126,9 @@ export default async function Page(props: { /> ) : (
- {tError("failedLoading")} + + +
)}
diff --git a/apps/login/src/app/(login)/register/page.tsx b/apps/login/src/app/(login)/register/page.tsx index be872b4dc7..aa83ad1ead 100644 --- a/apps/login/src/app/(login)/register/page.tsx +++ b/apps/login/src/app/(login)/register/page.tsx @@ -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, @@ -13,7 +14,7 @@ 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 { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -21,8 +22,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; @@ -70,8 +69,12 @@ export default async function Page(props: { return (
-

{t("disabled.title")}

-

{t("disabled.description")}

+

+ +

+

+ +

); @@ -80,10 +83,18 @@ export default async function Page(props: { return (
-

{t("title")}

-

{t("description")}

+

+ +

+

+ +

- {!organization && {tError("unknownContext")}} + {!organization && ( + + + + )} {legal && passwordComplexitySettings && @@ -107,7 +118,9 @@ export default async function Page(props: { {loginSettings?.allowExternalIdp && !!identityProviders.length && ( <>
-

{t("orUseIDP")}

+

+ +

>; }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "register" }); let { firstname, lastname, email, organization, requestId } = searchParams; @@ -57,15 +55,23 @@ export default async function Page(props: { return missingData ? (
-

{t("missingdata.title")}

-

{t("missingdata.description")}

+

+ +

+

+ +

) : loginSettings?.allowRegister && loginSettings.allowUsernamePassword ? (
-

{t("password.title")}

-

{t("description")}

+

+ +

+

+ +

{legal && passwordComplexitySettings && (
-

{t("disabled.title")}

-

{t("disabled.description")}

+

+ +

+

+ +

); diff --git a/apps/login/src/app/(login)/signedin/page.tsx b/apps/login/src/app/(login)/signedin/page.tsx index dd19096244..5b2ed5fbf4 100644 --- a/apps/login/src/app/(login)/signedin/page.tsx +++ b/apps/login/src/app/(login)/signedin/page.tsx @@ -1,6 +1,7 @@ import { Alert, AlertType } from "@/components/alert"; import { Button, ButtonVariants } from "@/components/button"; import { DynamicTheme } from "@/components/dynamic-theme"; +import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getMostRecentCookieWithLoginname, @@ -14,7 +15,6 @@ import { getLoginSettings, getSession, } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; @@ -37,8 +37,6 @@ async function loadSessionById( export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "signedin" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -66,8 +64,12 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("error.title")}

-

{t("error.description")}

+

+ +

+

+ +

{err.message}
@@ -94,9 +96,15 @@ export default async function Page(props: { searchParams: Promise }) {

- {t("title", { user: sessionFactors?.factors?.user?.displayName })} +

-

{t("description")}

+

+ +

}) { className="self-end" variant={ButtonVariants.Primary} > - {t("continue")} +
diff --git a/apps/login/src/app/(login)/u2f/page.tsx b/apps/login/src/app/(login)/u2f/page.tsx index b16dc88f4b..7fba7be1be 100644 --- a/apps/login/src/app/(login)/u2f/page.tsx +++ b/apps/login/src/app/(login)/u2f/page.tsx @@ -1,12 +1,13 @@ 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"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { @@ -14,8 +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, requestId, sessionId, organization } = searchParams; @@ -59,7 +58,9 @@ export default async function Page(props: { return (
-

{t("verify.title")}

+

+ +

{sessionFactors && ( )} -

{t("verify.description")}

+

+ +

- {!(loginName || sessionId) && {tError("unknownContext")}} + {!(loginName || sessionId) && ( + + + + )} {(loginName || sessionId) && (
-

{t("set.title")}

+

+ +

{sessionFactors && ( )} -

{t("set.description")}

+

+ {" "} + +

{!sessionFactors && (
- {tError("unknownContext")} + + +
)} diff --git a/apps/login/src/app/(login)/verify/page.tsx b/apps/login/src/app/(login)/verify/page.tsx index 7634ff063a..a61d4e608c 100644 --- a/apps/login/src/app/(login)/verify/page.tsx +++ b/apps/login/src/app/(login)/verify/page.tsx @@ -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"; @@ -7,14 +8,12 @@ import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getLocale, getTranslations } from "next-intl/server"; +import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise }) { 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; @@ -121,23 +120,26 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("verify.title")}

-

{t("verify.description")}

+

+ +

+

+ +

{!id && ( - <> -

{t("verify.title")}

-

{t("verify.description")}

- -
- {tError("unknownContext")} -
- +
+ + + +
)} {id && send && (
- {t("verify.codeSent")} + + +
)} diff --git a/apps/login/src/app/(login)/verify/success/page.tsx b/apps/login/src/app/(login)/verify/success/page.tsx index 1668e2e3fd..a0df0327c4 100644 --- a/apps/login/src/app/(login)/verify/success/page.tsx +++ b/apps/login/src/app/(login)/verify/success/page.tsx @@ -1,4 +1,5 @@ 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"; @@ -8,13 +9,10 @@ import { getUserByID, } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { headers } from "next/headers"; export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "verify" }); const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -65,8 +63,12 @@ export default async function Page(props: { searchParams: Promise }) { return (
-

{t("successTitle")}

-

{t("successDescription")}

+

+ +

+

+ +

{sessionFactors ? ( void; }) { - const t = useTranslations("error"); - return ( // global-error must include html and body tags @@ -25,7 +23,9 @@ export default function GlobalError({ Error: {error?.message}
- +
diff --git a/apps/login/src/components/authentication-method-radio.tsx b/apps/login/src/components/authentication-method-radio.tsx index 1b2af2d167..c3b273ab46 100644 --- a/apps/login/src/components/authentication-method-radio.tsx +++ b/apps/login/src/components/authentication-method-radio.tsx @@ -1,7 +1,7 @@ "use client"; import { RadioGroup } from "@headlessui/react"; -import { useTranslations } from "next-intl"; +import { Translated } from "./translated"; export enum AuthenticationMethod { Passkey = "passkey", @@ -20,8 +20,6 @@ export function AuthenticationMethodRadio({ selected: any; selectionChanged: (value: any) => void; }) { - const t = useTranslations("register"); - return (
@@ -80,7 +78,18 @@ export function AuthenticationMethodRadio({ as="p" className={`font-medium ${checked ? "" : ""}`} > - {t(`methods.${method}`)} + {method === AuthenticationMethod.Passkey && ( + + )} + {method === AuthenticationMethod.Password && ( + + )}
diff --git a/apps/login/src/components/back-button.tsx b/apps/login/src/components/back-button.tsx index fe348af9c4..31d4a880ad 100644 --- a/apps/login/src/components/back-button.tsx +++ b/apps/login/src/components/back-button.tsx @@ -1,11 +1,10 @@ "use client"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { Button, ButtonVariants } from "./button"; +import { Translated } from "./translated"; export function BackButton() { - const t = useTranslations("common"); const router = useRouter(); return ( ); } diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index 54aab7b3ca..00513d8dda 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -13,7 +13,6 @@ import { import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -23,6 +22,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PasswordComplexity } from "./password-complexity"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -46,7 +46,6 @@ export function ChangePasswordForm({ requestId, organization, }: Props) { - const t = useTranslations("password"); const router = useRouter(); const { register, handleSubmit, watch, formState } = useForm({ @@ -203,8 +202,8 @@ export function ChangePasswordForm({ onClick={handleSubmit(submitChange)} data-testid="submit-button" > - {loading && } - {t("change.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/choose-authenticator-to-login.tsx b/apps/login/src/components/choose-authenticator-to-login.tsx index f7e3cdf8f8..0f5dd79134 100644 --- a/apps/login/src/components/choose-authenticator-to-login.tsx +++ b/apps/login/src/components/choose-authenticator-to-login.tsx @@ -3,8 +3,8 @@ import { PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { PASSKEYS, PASSWORD } from "./auth-methods"; +import { Translated } from "./translated"; type Props = { authMethods: AuthenticationMethodType[]; @@ -17,13 +17,13 @@ export function ChooseAuthenticatorToLogin({ params, loginSettings, }: Props) { - const t = useTranslations("idp"); - return ( <> {authMethods.includes(AuthenticationMethodType.PASSWORD) && loginSettings?.allowUsernamePassword && ( -
Choose an alternative method to login
+
+ +
)}
{authMethods.includes(AuthenticationMethodType.PASSWORD) && diff --git a/apps/login/src/components/choose-authenticator-to-setup.tsx b/apps/login/src/components/choose-authenticator-to-setup.tsx index 9075e3286e..4aa4de720a 100644 --- a/apps/login/src/components/choose-authenticator-to-setup.tsx +++ b/apps/login/src/components/choose-authenticator-to-setup.tsx @@ -3,9 +3,9 @@ import { PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { Alert, AlertType } from "./alert"; import { PASSKEYS, PASSWORD } from "./auth-methods"; +import { Translated } from "./translated"; type Props = { authMethods: AuthenticationMethodType[]; @@ -18,16 +18,23 @@ export function ChooseAuthenticatorToSetup({ params, loginSettings, }: Props) { - const t = useTranslations("authenticator"); - if (authMethods.length !== 0) { - return {t("allSetup")}; + return ( + + + + ); } else { return ( <> {loginSettings.passkeysType == PasskeysType.NOT_ALLOWED && !loginSettings.allowUsernamePassword && ( - {t("noMethodsAvailable")} + + + )}
diff --git a/apps/login/src/components/choose-second-factor-to-setup.tsx b/apps/login/src/components/choose-second-factor-to-setup.tsx index e56379e147..edd0ae2b61 100644 --- a/apps/login/src/components/choose-second-factor-to-setup.tsx +++ b/apps/login/src/components/choose-second-factor-to-setup.tsx @@ -6,9 +6,9 @@ import { SecondFactorType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { EMAIL, SMS, TOTP, U2F } from "./auth-methods"; +import { Translated } from "./translated"; type Props = { userId: string; @@ -37,7 +37,6 @@ export function ChooseSecondFactorToSetup({ emailVerified, force, }: Props) { - const t = useTranslations("mfa"); const router = useRouter(); const params = new URLSearchParams({}); @@ -112,7 +111,7 @@ export function ChooseSecondFactorToSetup({ type="button" data-testid="reset-button" > - {t("set.skip")} + )} diff --git a/apps/login/src/components/consent.tsx b/apps/login/src/components/consent.tsx index d3d30b3113..e60ed2901b 100644 --- a/apps/login/src/components/consent.tsx +++ b/apps/login/src/components/consent.tsx @@ -8,6 +8,7 @@ import { useState } from "react"; import { Alert } from "./alert"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; export function ConsentScreen({ scope, @@ -50,7 +51,7 @@ export function ConsentScreen({
    {scopes?.length === 0 && ( - {t("device.scope.openid")} + )} {scopes?.map((s) => { @@ -73,7 +74,11 @@ export function ConsentScreen({

- {t("device.request.disclaimer", { appName: appName })} +

{error && ( @@ -91,7 +96,7 @@ export function ConsentScreen({ data-testid="deny-button" > {loading && } - {t("device.request.deny")} + @@ -102,7 +107,7 @@ export function ConsentScreen({ className="self-end" variant={ButtonVariants.Primary} > - {t("device.request.submit")} +
diff --git a/apps/login/src/components/device-code-form.tsx b/apps/login/src/components/device-code-form.tsx index e09adb1147..a1efc07207 100644 --- a/apps/login/src/components/device-code-form.tsx +++ b/apps/login/src/components/device-code-form.tsx @@ -2,7 +2,6 @@ import { Alert } from "@/components/alert"; import { getDeviceAuthorizationRequest } from "@/lib/server/oidc"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -10,14 +9,13 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { userCode: string; }; export function DeviceCodeForm({ userCode }: { userCode?: string }) { - const t = useTranslations("verify"); - const router = useRouter(); const { register, handleSubmit, formState } = useForm({ @@ -87,8 +85,8 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) { onClick={handleSubmit(submitCodeAndContinue)} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/idps/pages/complete-idp.tsx b/apps/login/src/components/idps/pages/complete-idp.tsx index e1ed5e7401..2061a28e3e 100644 --- a/apps/login/src/components/idps/pages/complete-idp.tsx +++ b/apps/login/src/components/idps/pages/complete-idp.tsx @@ -1,8 +1,8 @@ import { RegisterFormIDPIncomplete } from "@/components/register-form-idp-incomplete"; import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; import { AddHumanUserRequest } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; +import { Translated } from "../../translated"; export async function completeIDP({ idpUserId, @@ -26,14 +26,15 @@ export async function completeIDP({ idpIntentToken: string; }; }) { - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); - return (
-

{t("completeRegister.title")}

-

{t("completeRegister.description")}

+

+ +

+

+ +

-

{t("linkingError.title")}

-

{t("linkingError.description")}

+

+ +

+

+ +

{error && (
{{error}} diff --git a/apps/login/src/components/idps/pages/linking-success.tsx b/apps/login/src/components/idps/pages/linking-success.tsx index f4faa8e1bf..8d41cd8c32 100644 --- a/apps/login/src/components/idps/pages/linking-success.tsx +++ b/apps/login/src/components/idps/pages/linking-success.tsx @@ -1,7 +1,7 @@ import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; import { IdpSignin } from "../../idp-signin"; +import { Translated } from "../../translated"; export async function linkingSuccess( userId: string, @@ -9,14 +9,15 @@ export async function linkingSuccess( requestId?: string, branding?: BrandingSettings, ) { - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); - return (
-

{t("linkingSuccess.title")}

-

{t("linkingSuccess.description")}

+

+ +

+

+ +

-

{t("loginError.title")}

-

{t("loginError.description")}

+

+ +

+

+ +

{error && (
{{error}} diff --git a/apps/login/src/components/idps/pages/login-success.tsx b/apps/login/src/components/idps/pages/login-success.tsx index 6c884873f1..6beec160a9 100644 --- a/apps/login/src/components/idps/pages/login-success.tsx +++ b/apps/login/src/components/idps/pages/login-success.tsx @@ -1,7 +1,7 @@ import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb"; -import { getLocale, getTranslations } from "next-intl/server"; import { DynamicTheme } from "../../dynamic-theme"; import { IdpSignin } from "../../idp-signin"; +import { Translated } from "../../translated"; export async function loginSuccess( userId: string, @@ -9,14 +9,15 @@ export async function loginSuccess( requestId?: string, branding?: BrandingSettings, ) { - const locale = getLocale(); - const t = await getTranslations({ locale, namespace: "idp" }); - return (
-

{t("loginSuccess.title")}

-

{t("loginSuccess.description")}

+

+ +

+

+ +

(function SignInWithApple(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -24,7 +23,13 @@ export const SignInWithApple = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithApple")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/apps/login/src/components/idps/sign-in-with-azure-ad.tsx b/apps/login/src/components/idps/sign-in-with-azure-ad.tsx index a3a4c82272..3cd33708b6 100644 --- a/apps/login/src/components/idps/sign-in-with-azure-ad.tsx +++ b/apps/login/src/components/idps/sign-in-with-azure-ad.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithAzureAd = forwardRef< @@ -9,7 +9,6 @@ export const SignInWithAzureAd = forwardRef< SignInWithIdentityProviderProps >(function SignInWithAzureAd(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -30,7 +29,13 @@ export const SignInWithAzureAd = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithAzureAD")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/apps/login/src/components/idps/sign-in-with-github.tsx b/apps/login/src/components/idps/sign-in-with-github.tsx index 45108d17f7..8800e66c3d 100644 --- a/apps/login/src/components/idps/sign-in-with-github.tsx +++ b/apps/login/src/components/idps/sign-in-with-github.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; function GitHubLogo() { @@ -42,7 +42,6 @@ export const SignInWithGithub = forwardRef< SignInWithIdentityProviderProps >(function SignInWithGithub(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -52,7 +51,13 @@ export const SignInWithGithub = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithGithub")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/apps/login/src/components/idps/sign-in-with-gitlab.tsx b/apps/login/src/components/idps/sign-in-with-gitlab.tsx index 8a1ed7d349..00f3712a90 100644 --- a/apps/login/src/components/idps/sign-in-with-gitlab.tsx +++ b/apps/login/src/components/idps/sign-in-with-gitlab.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGitlab = forwardRef< @@ -9,7 +9,6 @@ export const SignInWithGitlab = forwardRef< SignInWithIdentityProviderProps >(function SignInWithGitlab(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -41,7 +40,13 @@ export const SignInWithGitlab = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithGitlab")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/apps/login/src/components/idps/sign-in-with-google.tsx b/apps/login/src/components/idps/sign-in-with-google.tsx index 6162ef4a96..4759ad69c9 100644 --- a/apps/login/src/components/idps/sign-in-with-google.tsx +++ b/apps/login/src/components/idps/sign-in-with-google.tsx @@ -1,7 +1,7 @@ "use client"; -import { useTranslations } from "next-intl"; import { forwardRef } from "react"; +import { Translated } from "../translated"; import { BaseButton, SignInWithIdentityProviderProps } from "./base-button"; export const SignInWithGoogle = forwardRef< @@ -9,7 +9,6 @@ export const SignInWithGoogle = forwardRef< SignInWithIdentityProviderProps >(function SignInWithGoogle(props, ref) { const { children, name, ...restProps } = props; - const t = useTranslations("idp"); return ( @@ -54,7 +53,13 @@ export const SignInWithGoogle = forwardRef< {children ? ( children ) : ( - {name ? name : t("signInWithGoogle")} + + {name ? ( + name + ) : ( + + )} + )} ); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index f8500f6909..4ad6cced6a 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -6,7 +6,6 @@ import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; @@ -15,6 +14,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; // either loginName or sessionId must be provided type Props = { @@ -42,8 +42,6 @@ export function LoginOTP({ code, loginSettings, }: Props) { - const t = useTranslations("otp"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -223,7 +221,7 @@ export function LoginOTP({
- {t("verify.noCodeReceived")} +
@@ -277,8 +275,8 @@ export function LoginOTP({ })} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/login-passkey.tsx b/apps/login/src/components/login-passkey.tsx index b3f0b1212f..0b7ecd5a9e 100644 --- a/apps/login/src/components/login-passkey.tsx +++ b/apps/login/src/components/login-passkey.tsx @@ -9,13 +9,13 @@ import { UserVerificationRequirement, } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { Alert } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; // either loginName or sessionId must be provided type Props = { @@ -35,8 +35,6 @@ export function LoginPasskey({ organization, login = true, }: Props) { - const t = useTranslations("passkey"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -234,7 +232,7 @@ export function LoginPasskey({ }} data-testid="password-button" > - {t("verify.usePassword")} + ) : ( @@ -273,8 +271,8 @@ export function LoginPasskey({ }} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 17461644d8..c65a4049f8 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -4,7 +4,6 @@ import { resetPassword, sendPassword } from "@/lib/server/password"; import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -13,6 +12,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { password: string; @@ -35,8 +35,6 @@ export function PasswordForm({ promptPasswordless, isAlternative, }: Props) { - const t = useTranslations("password"); - const { register, handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -136,7 +134,7 @@ export function PasswordForm({ disabled={loading} data-testid="reset-button" > - {t("verify.resetPassword")} + )} @@ -173,8 +171,8 @@ export function PasswordForm({ onClick={handleSubmit(submitPassword)} data-testid="submit-button" > - {loading && } - {t("verify.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/privacy-policy-checkboxes.tsx b/apps/login/src/components/privacy-policy-checkboxes.tsx index 5ac0340bcb..4ab0e33222 100644 --- a/apps/login/src/components/privacy-policy-checkboxes.tsx +++ b/apps/login/src/components/privacy-policy-checkboxes.tsx @@ -1,9 +1,9 @@ "use client"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; -import { useTranslations } from "next-intl"; import Link from "next/link"; import { useState } from "react"; import { Checkbox } from "./checkbox"; +import { Translated } from "./translated"; type Props = { legal: LegalAndSupportSettings; @@ -16,7 +16,6 @@ type AcceptanceState = { }; export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { - const t = useTranslations("register"); const [acceptanceState, setAcceptanceState] = useState({ tosAccepted: false, privacyPolicyAccepted: false, @@ -25,7 +24,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { return ( <>

- {t("agreeTo")} + {legal?.helpLink && ( @@ -66,7 +65,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {

- {t("termsOfService")} +

@@ -95,7 +94,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { className="underline" target="_blank" > - {t("privacyPolicy")} +

diff --git a/apps/login/src/components/register-form-idp-incomplete.tsx b/apps/login/src/components/register-form-idp-incomplete.tsx index 6194b34052..b8a7765c9c 100644 --- a/apps/login/src/components/register-form-idp-incomplete.tsx +++ b/apps/login/src/components/register-form-idp-incomplete.tsx @@ -1,7 +1,6 @@ "use client"; import { registerUserAndLinkToIDP } from "@/lib/server/register"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -10,6 +9,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -45,8 +45,6 @@ export function RegisterFormIDPIncomplete({ idpId, idpUserName, }: Props) { - const t = useTranslations("register"); - const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -149,8 +147,8 @@ export function RegisterFormIDPIncomplete({ onClick={handleSubmit(submitAndRegister)} data-testid="submit-button" > - {loading && } - {t("submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index c581131c8c..6217bbcbb9 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -6,7 +6,6 @@ import { LoginSettings, PasskeysType, } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -21,6 +20,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PrivacyPolicyCheckboxes } from "./privacy-policy-checkboxes"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -51,8 +51,6 @@ export function RegisterForm({ loginSettings, idpCount = 0, }: Props) { - const t = useTranslations("register"); - const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -173,7 +171,7 @@ export function RegisterForm({ loginSettings.passkeysType == PasskeysType.ALLOWED && ( <>

- {t("selectMethod")} +

@@ -184,12 +182,16 @@ export function RegisterForm({
)} - {!loginSettings?.allowUsernamePassword && - loginSettings?.passkeysType != PasskeysType.ALLOWED && + loginSettings?.passkeysType !== PasskeysType.ALLOWED && (!loginSettings?.allowExternalIdp || !idpCount) && (
- {t("noMethodAvailableWarning")} + + +
)} @@ -217,7 +219,7 @@ export function RegisterForm({ data-testid="submit-button" > {loading && } - {t("submit")} +
diff --git a/apps/login/src/components/register-passkey.tsx b/apps/login/src/components/register-passkey.tsx index 8687312bbc..e21e1acdbb 100644 --- a/apps/login/src/components/register-passkey.tsx +++ b/apps/login/src/components/register-passkey.tsx @@ -5,7 +5,6 @@ import { registerPasskeyLink, verifyPasskeyRegistration, } from "@/lib/server/passkeys"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useForm } from "react-hook-form"; @@ -13,6 +12,7 @@ import { Alert } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = {}; @@ -29,8 +29,6 @@ export function RegisterPasskey({ organization, requestId, }: Props) { - const t = useTranslations("passkey"); - const { handleSubmit, formState } = useForm({ mode: "onBlur", }); @@ -198,7 +196,7 @@ export function RegisterPasskey({ continueAndLogin(); }} > - {t("set.skip")} + ) : ( @@ -213,8 +211,8 @@ export function RegisterPasskey({ onClick={handleSubmit(submitRegisterAndContinue)} data-testid="submit-button" > - {loading && } - {t("set.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/register-u2f.tsx b/apps/login/src/components/register-u2f.tsx index 753eae017d..e72bf1fc69 100644 --- a/apps/login/src/components/register-u2f.tsx +++ b/apps/login/src/components/register-u2f.tsx @@ -5,13 +5,13 @@ import { getNextUrl } from "@/lib/client"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Alert } from "./alert"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Props = { loginName?: string; @@ -30,8 +30,6 @@ export function RegisterU2f({ checkAfter, loginSettings, }: Props) { - const t = useTranslations("u2f"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); @@ -218,8 +216,8 @@ export function RegisterU2f({ onClick={submitRegisterAndContinue} data-testid="submit-button" > - {loading && } - {t("set.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/session-clear-item.tsx b/apps/login/src/components/session-clear-item.tsx index 8bff33fee9..81930b11b3 100644 --- a/apps/login/src/components/session-clear-item.tsx +++ b/apps/login/src/components/session-clear-item.tsx @@ -4,11 +4,12 @@ import { clearSession } from "@/lib/server/session"; import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import moment from "moment"; -import { useLocale, useTranslations } from "next-intl"; +import { useLocale } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { Avatar } from "./avatar"; import { isSessionValid } from "./session-item"; +import { Translated } from "./translated"; export function SessionClearItem({ session, @@ -17,8 +18,6 @@ export function SessionClearItem({ session: Session; reload: () => void; }) { - const t = useTranslations("logout"); - const currentLocale = useLocale(); moment.locale(currentLocale === "zh" ? "zh-cn" : currentLocale); @@ -70,10 +69,13 @@ export function SessionClearItem({ {valid ? ( - {verifiedAt && - t("verfiedAt", { - time: moment(timestampDate(verifiedAt)).fromNow(), - })} + {verifiedAt && ( + + )} ) : ( verifiedAt && ( @@ -89,7 +91,7 @@ export function SessionClearItem({
- {t("clear")} +
{valid ? ( diff --git a/apps/login/src/components/sessions-clear-list.tsx b/apps/login/src/components/sessions-clear-list.tsx index fe7a67f746..5989948725 100644 --- a/apps/login/src/components/sessions-clear-list.tsx +++ b/apps/login/src/components/sessions-clear-list.tsx @@ -3,11 +3,11 @@ import { clearSession } from "@/lib/server/session"; import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { useTranslations } from "next-intl"; import { redirect, useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { Alert, AlertType } from "./alert"; import { SessionClearItem } from "./session-clear-item"; +import { Translated } from "./translated"; type Props = { sessions: Session[]; @@ -22,7 +22,6 @@ export function SessionsClearList({ postLogoutRedirectUri, organization, }: Props) { - const t = useTranslations("logout"); const [list, setList] = useState(sessions); const router = useRouter(); @@ -97,10 +96,14 @@ export function SessionsClearList({ ); })} {list.length === 0 && ( - {t("noResults")} + + + )}
) : ( - {t("noResults")} + + + ); } diff --git a/apps/login/src/components/sessions-list.tsx b/apps/login/src/components/sessions-list.tsx index 50f621a62d..a3a1f8ed94 100644 --- a/apps/login/src/components/sessions-list.tsx +++ b/apps/login/src/components/sessions-list.tsx @@ -2,10 +2,10 @@ import { timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { useTranslations } from "next-intl"; import { useState } from "react"; import { Alert } from "./alert"; import { SessionItem } from "./session-item"; +import { Translated } from "./translated"; type Props = { sessions: Session[]; @@ -13,7 +13,6 @@ type Props = { }; export function SessionsList({ sessions, requestId }: Props) { - const t = useTranslations("accounts"); const [list, setList] = useState(sessions); return sessions ? (
@@ -44,6 +43,8 @@ export function SessionsList({ sessions, requestId }: Props) { })}
) : ( - {t("noResults")} + + + ); } diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 08f5c7c4ef..2c3db8dbf2 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -14,7 +14,6 @@ import { import { create } from "@zitadel/client"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -24,6 +23,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PasswordComplexity } from "./password-complexity"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -52,8 +52,6 @@ export function SetPasswordForm({ code, codeRequired, }: Props) { - const t = useTranslations("password"); - const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -195,7 +193,7 @@ export function SetPasswordForm({
- {t("set.noCodeReceived")} +
@@ -279,8 +277,8 @@ export function SetPasswordForm({ onClick={handleSubmit(submitPassword)} data-testid="submit-button" > - {loading && } - {t("set.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx index 3e48c649c1..7660e60753 100644 --- a/apps/login/src/components/set-register-password-form.tsx +++ b/apps/login/src/components/set-register-password-form.tsx @@ -8,7 +8,6 @@ import { } from "@/helpers/validators"; import { registerUser } from "@/lib/server/register"; import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { FieldValues, useForm } from "react-hook-form"; @@ -18,6 +17,7 @@ import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { PasswordComplexity } from "./password-complexity"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = | { @@ -43,8 +43,6 @@ export function SetRegisterPasswordForm({ organization, requestId, }: Props) { - const t = useTranslations("register"); - const { register, handleSubmit, watch, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -163,8 +161,8 @@ export function SetRegisterPasswordForm({ onClick={handleSubmit(submitRegister)} data-testid="submit-button" > - {loading && } - {t("password.submit")} + {loading && }{" "} +
diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index b5c81d8645..ea40fffbf0 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -3,7 +3,6 @@ import { getNextUrl } from "@/lib/client"; import { verifyTOTP } from "@/lib/server/verify"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { QRCodeSVG } from "qrcode.react"; @@ -14,6 +13,7 @@ import { Button, ButtonVariants } from "./button"; import { CopyToClipboard } from "./copy-to-clipboard"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { code: string; @@ -39,8 +39,6 @@ export function TotpRegister({ checkAfter, loginSettings, }: Props) { - const t = useTranslations("otp"); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); @@ -148,7 +146,7 @@ export function TotpRegister({ data-testid="submit-button" > {loading && } - {t("set.submit")} +
diff --git a/apps/login/src/components/translated.tsx b/apps/login/src/components/translated.tsx new file mode 100644 index 0000000000..807ea18e8f --- /dev/null +++ b/apps/login/src/components/translated.tsx @@ -0,0 +1,23 @@ +import { useTranslations } from "next-intl"; + +export function Translated({ + i18nKey, + children, + namespace, + data, + ...props +}: { + i18nKey: string; + children?: React.ReactNode; + namespace?: string; + data?: any; +} & React.HTMLAttributes) { + const t = useTranslations(namespace); + const helperKey = `${namespace ? `${namespace}.` : ""}${i18nKey}`; + + return ( + + {t(i18nKey, data)} + + ); +} diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index 6801f6b274..b16092bd9e 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -2,7 +2,6 @@ import { sendLoginname } from "@/lib/server/loginname"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { ReactNode, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -11,6 +10,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { loginName: string; @@ -37,7 +37,6 @@ export function UsernameForm({ allowRegister, children, }: Props) { - const t = useTranslations("loginname"); const { register, handleSubmit, formState } = useForm({ mode: "onBlur", defaultValues: { @@ -127,7 +126,7 @@ export function UsernameForm({ disabled={loading} data-testid="register-button" > - {t("register")} + )}
@@ -152,7 +151,7 @@ export function UsernameForm({ onClick={handleSubmit((e) => submitLoginName(e, organization))} > {loading && } - continue +
diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 0933f598dd..dac4c91314 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -2,7 +2,6 @@ import { Alert, AlertType } from "@/components/alert"; import { resendVerification, sendVerification } from "@/lib/server/verify"; -import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -10,6 +9,7 @@ import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; import { Spinner } from "./spinner"; +import { Translated } from "./translated"; type Inputs = { code: string; @@ -32,8 +32,6 @@ export function VerifyForm({ code, isInvite, }: Props) { - const t = useTranslations("verify"); - const router = useRouter(); const { register, handleSubmit, formState } = useForm({ @@ -117,7 +115,7 @@ export function VerifyForm({
- {t("verify.noCodeReceived")} +
@@ -161,7 +159,7 @@ export function VerifyForm({ data-testid="submit-button" > {loading && } - {t("verify.submit")} +
diff --git a/apps/login/src/i18n/request.ts b/apps/login/src/i18n/request.ts index 59c9da42cc..271d370f7c 100644 --- a/apps/login/src/i18n/request.ts +++ b/apps/login/src/i18n/request.ts @@ -1,4 +1,7 @@ import { LANGS, LANGUAGE_COOKIE_NAME, LANGUAGE_HEADER_NAME } from "@/lib/i18n"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; +import { getHostedLoginTranslation } from "@/lib/zitadel"; +import { JsonObject } from "@zitadel/client"; import deepmerge from "deepmerge"; import { getRequestConfig } from "next-intl/server"; import { cookies, headers } from "next/headers"; @@ -9,6 +12,26 @@ export default getRequestConfig(async () => { let locale: string = fallback; + const _headers = await headers(); + const { serviceUrl } = getServiceUrlFromHeaders(_headers); + + const i18nOrganization = _headers.get("x-zitadel-i18n-organization") || ""; // You may need to set this header in middleware + console.log("i18nOrganization:", i18nOrganization); + let translations: JsonObject | {} = {}; + try { + const i18nJSON = await getHostedLoginTranslation({ + serviceUrl, + locale, + organization: i18nOrganization, + }); + + if (i18nJSON) { + translations = i18nJSON; + } + } catch (error) { + console.warn("Error fetching custom translations:", error); + } + const languageHeader = await (await headers()).get(LANGUAGE_HEADER_NAME); if (languageHeader) { const headerLocale = languageHeader.split(",")[0].split("-")[0]; // Extract the language code @@ -24,12 +47,13 @@ export default getRequestConfig(async () => { } } - const userMessages = (await import(`../../locales/${locale}.json`)).default; + const customMessages = translations; + const localeMessages = (await import(`../../locales/${locale}.json`)).default; const fallbackMessages = (await import(`../../locales/${fallback}.json`)) .default; return { locale, - messages: deepmerge(fallbackMessages, userMessages), + messages: deepmerge.all([fallbackMessages, localeMessages, customMessages]), }; }); diff --git a/apps/login/src/lib/zitadel.ts b/apps/login/src/lib/zitadel.ts index 8cc4efe0bc..e148e7c0be 100644 --- a/apps/login/src/lib/zitadel.ts +++ b/apps/login/src/lib/zitadel.ts @@ -59,6 +59,42 @@ async function cacheWrapper(callback: Promise) { return callback; } +export async function getHostedLoginTranslation({ + serviceUrl, + organization, + locale, +}: { + serviceUrl: string; + organization?: string; + locale?: string; +}) { + const settingsService: Client = + await createServiceForHost(SettingsService, serviceUrl); + + const callback = settingsService + .getHostedLoginTranslation( + { + level: organization + ? { + case: "organizationId", + value: organization, + } + : { + case: "instance", + value: true, + }, + locale: locale, + }, + {}, + ) + .then((resp) => { + console.log(resp); + return resp.translations ? resp.translations : undefined; + }); + + return useCache ? cacheWrapper(callback) : callback; +} + export async function getBrandingSettings({ serviceUrl, organization, diff --git a/apps/login/src/middleware.ts b/apps/login/src/middleware.ts index 4d66d0ab39..fa287e2bdd 100644 --- a/apps/login/src/middleware.ts +++ b/apps/login/src/middleware.ts @@ -10,21 +10,51 @@ export const config = { "/oidc/:path*", "/idps/callback/:path*", "/saml/:path*", + "/:path*", ], }; export async function middleware(request: NextRequest) { + // Add the original URL as a header to all requests + const requestHeaders = new Headers(request.headers); + + // Extract "organization" search param from the URL and set it as a header if available + const organization = request.nextUrl.searchParams.get("organization"); + if (organization) { + requestHeaders.set("x-zitadel-i18n-organization", organization); + } + + // Only run the rest of the logic for the original matcher paths + const matchedPaths = [ + "/.well-known/", + "/oauth/", + "/oidc/", + "/idps/callback/", + "/saml/", + ]; + + const isMatched = matchedPaths.some((prefix) => + request.nextUrl.pathname.startsWith(prefix), + ); + + if (!isMatched) { + // For all other routes, just add the header and continue + return NextResponse.next({ + request: { headers: requestHeaders }, + }); + } + // escape proxy if the environment is setup for multitenancy if (!process.env.ZITADEL_API_URL || !process.env.ZITADEL_SERVICE_USER_TOKEN) { - return NextResponse.next(); + return NextResponse.next({ + request: { headers: requestHeaders }, + }); } const _headers = await headers(); - const { serviceUrl } = getServiceUrlFromHeaders(_headers); // Call the /security route handler - // TODO check this on cloud run deployment const securityResponse = await fetch(`${request.nextUrl.origin}/security`); if (!securityResponse.ok) { @@ -32,7 +62,9 @@ export async function middleware(request: NextRequest) { "Failed to fetch security settings:", securityResponse.statusText, ); - return NextResponse.next(); // Fallback if the request fails + return NextResponse.next({ + request: { headers: requestHeaders }, + }); } const { settings: securitySettings } = await securityResponse.json(); @@ -41,13 +73,8 @@ export async function middleware(request: NextRequest) { .replace("https://", "") .replace("http://", ""); - const requestHeaders = new Headers(request.headers); - - // this is a workaround for the next.js server not forwarding the host header - // requestHeaders.set("x-zitadel-forwarded", `host="${request.nextUrl.host}"`); + // Add additional headers as before requestHeaders.set("x-zitadel-public-host", `${request.nextUrl.host}`); - - // this is a workaround for the next.js server not forwarding the host header requestHeaders.set("x-zitadel-instance-host", instanceHost); const responseHeaders = new Headers(); @@ -55,7 +82,6 @@ export async function middleware(request: NextRequest) { responseHeaders.set("Access-Control-Allow-Headers", "*"); if (securitySettings?.embeddedIframe?.enabled) { - securitySettings.embeddedIframe.allowedOrigins; responseHeaders.set( "Content-Security-Policy", `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46a448c2f1..fb7a635216 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: version: 0.5.7(tailwindcss@3.4.14) '@vercel/analytics': specifier: ^1.2.2 - version: 1.3.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) + version: 1.3.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -101,9 +101,6 @@ importers: deepmerge: specifier: ^4.3.1 version: 4.3.1 - jose: - specifier: ^5.3.0 - version: 5.8.0 lucide-react: specifier: 0.469.0 version: 0.469.0(react@19.1.0) @@ -111,14 +108,14 @@ importers: specifier: ^2.29.4 version: 2.30.1 next: - specifier: 15.4.0-canary.3 - version: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + specifier: 15.4.0-canary.86 + version: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) next-intl: specifier: ^3.25.1 - version: 3.25.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) + version: 3.26.5(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 0.2.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) nice-grpc: specifier: 2.0.1 version: 2.0.1 @@ -134,9 +131,6 @@ importers: react-hook-form: specifier: 7.39.5 version: 7.39.5(react@19.1.0) - swr: - specifier: ^2.2.0 - version: 2.2.5(react@19.1.0) tinycolor2: specifier: 1.4.2 version: 1.4.2 @@ -807,9 +801,6 @@ packages: '@formatjs/icu-skeleton-parser@1.8.8': resolution: {integrity: sha512-vHwK3piXwamFcx5YQdCdJxUQ1WdTl6ANclt5xba5zLGDv5Bsur7qz8AD7BevaKxITwpgDeU0u8My3AIibW9ywA==} - '@formatjs/intl-localematcher@0.5.4': - resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} - '@formatjs/intl-localematcher@0.5.8': resolution: {integrity: sha512-I+WDNWWJFZie+jkfkiK5Mp4hEDyRSEvmyfYadflOno/mmKJKcB17fEpEH0oJu/OWhhCJ8kJBDz2YMd/6cDl7Mg==} @@ -998,56 +989,56 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@15.4.0-canary.3': - resolution: {integrity: sha512-lu4pB2e3Z/d+B0rxEm9YuQMb57Hd96iJUBZgVlcRNemlIryr0GByu17kvN6nBk3JjbWL8h+MW90stpGzGdhbqg==} + '@next/env@15.4.0-canary.86': + resolution: {integrity: sha512-WPrEvwqHnjeLx05ncJvqizbBJJFlQGRbxzOnL/pZWKzo19auM9x5Se87P27+E/D/d6jJS801l+thF85lfobAZQ==} '@next/eslint-plugin-next@14.2.18': resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==} - '@next/swc-darwin-arm64@15.4.0-canary.3': - resolution: {integrity: sha512-w9u8IpwLb/JS7HzHLt24smP4FxIYMgciOtYNUCognO1xh1XZfqqjDIrRAXDuuYDPKrc1i2EvI24R5eDTz7EYMQ==} + '@next/swc-darwin-arm64@15.4.0-canary.86': + resolution: {integrity: sha512-1ofBmzjPkmoMdM+dXvybZ/Roq8HRo0sFzcwXk7/FJNOufuwyK+QKdSpLE7pHlPR7ZREqfEMj61ONO+gAK+zOJw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.4.0-canary.3': - resolution: {integrity: sha512-5pL1hBRw8h1XeArzWYjCDERtRFIfrMAz1Nq9m1np8FrTuHclE7xitKKfOJqqmBbO9dWtnZIfA8lZl9bdlNEUZg==} + '@next/swc-darwin-x64@15.4.0-canary.86': + resolution: {integrity: sha512-WCKSrllvwzYi4TgrSdgxKSOF2nhieeaWWOeGucn0OXy50uOAamr0HwP5OaIBCx3oRar4w66gvs4IrdTdMedeJA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.4.0-canary.3': - resolution: {integrity: sha512-vx6cU4jKoecF2QZw3CQqJrzb+D0WhNzHHoWUN8O+YKPnX0oG4wEtAQWSWisxKjNrU1U4TiraOql0nOQBUOKwaQ==} + '@next/swc-linux-arm64-gnu@15.4.0-canary.86': + resolution: {integrity: sha512-8qn7DJVNFjhEIDo2ts0YCsO7g+vJjPWh8Ur8lBK3XspeX0BPsF4s+YmgidrpzRXeIfoo2uYLkkXcy/57CVDblw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.4.0-canary.3': - resolution: {integrity: sha512-7ig1sQHRRgTrj4QHt5l8OT1z2SJnEAHbnEY9SDP2HilwQIfgOAOxveFDBR+f/8AMdAKhCTSeMyrZsivpC0xTUA==} + '@next/swc-linux-arm64-musl@15.4.0-canary.86': + resolution: {integrity: sha512-8MTn6N4Ja25neMLu2Bra1lqW9AWPqsYg0BVs5M/cxL0QkcN3mak/8LLX1vbzz7GigMGSA+NLwg+ol8lglfgIGA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.4.0-canary.3': - resolution: {integrity: sha512-fML6pzNX9i3DlrCOdE6A1TbVL0aIQkIDDCjrbn/f37hOn88god1OrVd/d4J4w1YqLKQWpmJPnUn6Bkn8qXqbRw==} + '@next/swc-linux-x64-gnu@15.4.0-canary.86': + resolution: {integrity: sha512-hIhzDwWDQHnH0M0Pzaqs1c5fa4+LHiLLEBuPJQvhBxQfH+Eh86DWiWHDCaoNiURvdRPg6uCuF2MjwptrMplEkg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.4.0-canary.3': - resolution: {integrity: sha512-87/JPkbr3fgvASdWW2qBVuaXwcjSxgy+CTllj2DgYB7e7BEzT7QJEdj0HJZljBjVbN5oT1FOKwhaVRgRWuwYLQ==} + '@next/swc-linux-x64-musl@15.4.0-canary.86': + resolution: {integrity: sha512-FG6SBuSeRWYMNu6tsfaZ4iDzv3BLxlpRncO2xvKKQPeUdDSQ0cehuHYnx8fRte8IOAJ3rlbRd6NXvrDarqu92Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.4.0-canary.3': - resolution: {integrity: sha512-cTZh72h3ZX8z0lhdVs5m38uyy83mW5r0jz6hKagysPT06uTdOAypK6CRqG5CJSN7RM0n7CkfcO6ExjDqhkDhRA==} + '@next/swc-win32-arm64-msvc@15.4.0-canary.86': + resolution: {integrity: sha512-3HvZo4VuyINrNYplRhvC8ILdKwi/vFDHOcTN/I4ru039TFpu2eO6VtXsLBdOdJjGslSSSBYkX+6yRrghihAZDA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.4.0-canary.3': - resolution: {integrity: sha512-8oZKOKRGad4EVZ94L5Sz2EP59khHIeKGKg+/z8r5mCbBtupLPTXmWjrXoi1R55hHRXJjbW2D5NwcPfJn/ltZ3Q==} + '@next/swc-win32-x64-msvc@15.4.0-canary.86': + resolution: {integrity: sha512-UO9JzGGj7GhtSJFdI0Bl0dkIIBfgbhXLsgNVmq9Z/CsUsQB6J9RS/BMhsxfVwhO+RETk13nFpNutMAhAwcuD8w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1824,10 +1815,6 @@ packages: peerDependencies: esbuild: '>=0.18' - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1856,9 +1843,6 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001680: - resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} - caniuse-lite@1.0.30001715: resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} @@ -2159,10 +2143,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -3389,11 +3369,11 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - next-intl@3.25.1: - resolution: {integrity: sha512-Z2dJWn5f/b1sb8EmuJcuDhbQTIp4RG1KBFAILgRt/y27W0ifU7Ll/os3liphUY4InyRH89uShTAk7ItAlpr0uA==} + next-intl@3.26.5: + resolution: {integrity: sha512-EQlCIfY0jOhRldiFxwSXG+ImwkQtDEfQeSOEQp6ieAGSLWGlgjdb/Ck/O7wMfC430ZHGeUKVKax8KGusTPKCgg==} peerDependencies: next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 next-themes@0.2.1: resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} @@ -3402,13 +3382,13 @@ packages: react: '*' react-dom: '*' - next@15.4.0-canary.3: - resolution: {integrity: sha512-OkwxAFNQeuE0vNL7tTwU+jm3nf3x3D5DHSmjRlFktsedGtxZiILZTq6UNExNaFBjttR+2Y6oGqRsFWXC4ob1Wg==} + next@15.4.0-canary.86: + resolution: {integrity: sha512-lGeO0sOvPZ7oFIklqRA863YzRL1bW+kT/OqU3N6RBquHldiucZwnZKQceZdn6WcHEFmWIHzZV+SMG1JEK7hZLg==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 + '@playwright/test': ^1.51.1 babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -4170,10 +4150,6 @@ packages: stream-combiner@0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -4276,11 +4252,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - swr@2.2.5: - resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 - symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -4560,15 +4531,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - use-intl@3.25.1: - resolution: {integrity: sha512-Xeyl0+BjlBf6fJr2h5W/CESZ2IQAH7jzXYK4c/ao+qR26jNPW3FXBLjg7eLRxdeI6QaLcYGLtH3WYhC9I0+6Yg==} + use-intl@3.26.5: + resolution: {integrity: sha512-OdsJnC/znPvHCHLQH/duvQNXnP1w0hPfS+tkSi3mAbfjYBGh4JnyfdwkQBfIVf7t8gs9eSX/CntxUMvtKdG2MQ==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 - - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5350,10 +5316,6 @@ snapshots: '@formatjs/ecma402-abstract': 2.2.4 tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.4': - dependencies: - tslib: 2.8.1 - '@formatjs/intl-localematcher@0.5.8': dependencies: tslib: 2.8.1 @@ -5538,34 +5500,34 @@ snapshots: - encoding - supports-color - '@next/env@15.4.0-canary.3': {} + '@next/env@15.4.0-canary.86': {} '@next/eslint-plugin-next@14.2.18': dependencies: glob: 10.3.10 - '@next/swc-darwin-arm64@15.4.0-canary.3': + '@next/swc-darwin-arm64@15.4.0-canary.86': optional: true - '@next/swc-darwin-x64@15.4.0-canary.3': + '@next/swc-darwin-x64@15.4.0-canary.86': optional: true - '@next/swc-linux-arm64-gnu@15.4.0-canary.3': + '@next/swc-linux-arm64-gnu@15.4.0-canary.86': optional: true - '@next/swc-linux-arm64-musl@15.4.0-canary.3': + '@next/swc-linux-arm64-musl@15.4.0-canary.86': optional: true - '@next/swc-linux-x64-gnu@15.4.0-canary.3': + '@next/swc-linux-x64-gnu@15.4.0-canary.86': optional: true - '@next/swc-linux-x64-musl@15.4.0-canary.3': + '@next/swc-linux-x64-musl@15.4.0-canary.86': optional: true - '@next/swc-win32-arm64-msvc@15.4.0-canary.3': + '@next/swc-win32-arm64-msvc@15.4.0-canary.86': optional: true - '@next/swc-win32-x64-msvc@15.4.0-canary.3': + '@next/swc-win32-x64-msvc@15.4.0-canary.86': optional: true '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -6027,11 +5989,11 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vercel/analytics@1.3.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0)': + '@vercel/analytics@1.3.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0)': dependencies: server-only: 0.0.1 optionalDependencies: - next: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + next: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) react: 19.1.0 '@vercel/git-hooks@1.0.0': {} @@ -6099,7 +6061,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -6338,10 +6300,6 @@ snapshots: esbuild: 0.25.2 load-tsconfig: 0.2.5 - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - cac@6.7.14: {} cachedir@2.4.0: {} @@ -6368,8 +6326,6 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001680: {} - caniuse-lite@1.0.30001715: {} case-anything@2.1.13: {} @@ -6636,6 +6592,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.0: + dependencies: + ms: 2.1.3 + debug@4.4.0(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -6713,9 +6673,6 @@ snapshots: detect-libc@1.0.3: {} - detect-libc@2.0.3: - optional: true - detect-libc@2.0.4: {} didyoumean@1.2.2: {} @@ -7321,7 +7278,7 @@ snapshots: follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 for-each@0.3.3: dependencies: @@ -7581,7 +7538,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7594,14 +7551,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -7950,7 +7907,7 @@ snapshots: dependencies: chalk: 5.4.1 commander: 13.1.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 execa: 8.0.1 lilconfig: 3.1.3 listr2: 8.3.2 @@ -8134,40 +8091,38 @@ snapshots: negotiator@1.0.0: {} - next-intl@3.25.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0): + next-intl@3.26.5(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react@19.1.0): dependencies: - '@formatjs/intl-localematcher': 0.5.4 + '@formatjs/intl-localematcher': 0.5.8 negotiator: 1.0.0 - next: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + next: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) react: 19.1.0 - use-intl: 3.25.1(react@19.1.0) + use-intl: 3.26.5(react@19.1.0) - next-themes@0.2.1(next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next-themes@0.2.1(next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - next: 15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) + next: 15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - next@15.4.0-canary.3(@babel/core@7.26.10)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0): + next@15.4.0-canary.86(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.87.0): dependencies: - '@next/env': 15.4.0-canary.3 - '@swc/counter': 0.1.3 + '@next/env': 15.4.0-canary.86 '@swc/helpers': 0.5.15 - busboy: 1.6.0 - caniuse-lite: 1.0.30001680 + caniuse-lite: 1.0.30001715 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.4.0-canary.3 - '@next/swc-darwin-x64': 15.4.0-canary.3 - '@next/swc-linux-arm64-gnu': 15.4.0-canary.3 - '@next/swc-linux-arm64-musl': 15.4.0-canary.3 - '@next/swc-linux-x64-gnu': 15.4.0-canary.3 - '@next/swc-linux-x64-musl': 15.4.0-canary.3 - '@next/swc-win32-arm64-msvc': 15.4.0-canary.3 - '@next/swc-win32-x64-msvc': 15.4.0-canary.3 + '@next/swc-darwin-arm64': 15.4.0-canary.86 + '@next/swc-darwin-x64': 15.4.0-canary.86 + '@next/swc-linux-arm64-gnu': 15.4.0-canary.86 + '@next/swc-linux-arm64-musl': 15.4.0-canary.86 + '@next/swc-linux-x64-gnu': 15.4.0-canary.86 + '@next/swc-linux-x64-musl': 15.4.0-canary.86 + '@next/swc-win32-arm64-msvc': 15.4.0-canary.86 + '@next/swc-win32-x64-msvc': 15.4.0-canary.86 '@playwright/test': 1.52.0 sass: 1.87.0 sharp: 0.34.1 @@ -8737,7 +8692,7 @@ snapshots: sharp@0.34.1: dependencies: color: 4.2.3 - detect-libc: 2.0.3 + detect-libc: 2.0.4 semver: 7.7.1 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.1 @@ -8882,7 +8837,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.4.0(supports-color@5.5.0) + debug: 4.4.0 execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -8900,8 +8855,6 @@ snapshots: dependencies: duplexer: 0.1.2 - streamsearch@1.1.0: {} - string-argv@0.3.2: {} string-width@4.2.3: @@ -8990,12 +8943,10 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(@babel/core@7.26.10)(react@19.1.0): + styled-jsx@5.1.6(react@19.1.0): dependencies: client-only: 0.0.1 react: 19.1.0 - optionalDependencies: - '@babel/core': 7.26.10 sucrase@3.35.0: dependencies: @@ -9021,12 +8972,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swr@2.2.5(react@19.1.0): - dependencies: - client-only: 0.0.1 - react: 19.1.0 - use-sync-external-store: 1.2.2(react@19.1.0) - symbol-tree@3.2.4: {} tabbable@6.2.0: {} @@ -9309,16 +9254,12 @@ snapshots: dependencies: punycode: 2.3.1 - use-intl@3.25.1(react@19.1.0): + use-intl@3.26.5(react@19.1.0): dependencies: '@formatjs/fast-memoize': 2.2.3 intl-messageformat: 10.7.7 react: 19.1.0 - use-sync-external-store@1.2.2(react@19.1.0): - dependencies: - react: 19.1.0 - util-deprecate@1.0.2: {} uuid@11.1.0: {}