fix(login): use translation title key prop to set page title (#10537)

This PR sets the page title to the same title as the respective pages
and introduces a default title ("Login with Zitadel").
Closes #10282 

# Which Problems Are Solved

Missing page title on pages.

# How the Problems Are Solved

Using the hosted translation service, we load and merge properties to
set the page title

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Max Peintner
2025-08-22 12:59:13 +02:00
committed by GitHub
parent 75fe4eb651
commit 772e9c5e3d
27 changed files with 155 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "Zurück"
"back": "Zurück",
"title": "Anmelden mit Zitadel"
},
"accounts": {
"title": "Konten",

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "Back"
"back": "Back",
"title": "Login with Zitadel"
},
"accounts": {
"title": "Accounts",

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "Atrás"
"back": "Atrás",
"title": "Iniciar sesión con Zitadel"
},
"accounts": {
"title": "Cuentas",

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "Indietro"
"back": "Indietro",
"title": "Accedi con Zitadel"
},
"accounts": {
"title": "Account",

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "Powrót"
"back": "Powrót",
"title": "Zaloguj się za pomocą Zitadel"
},
"accounts": {
"title": "Konta",

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "Назад"
"back": "Назад",
"title": "Войти с Zitadel"
},
"accounts": {
"title": "Аккаунты",

View File

@@ -1,6 +1,7 @@
{
"common": {
"back": "返回"
"back": "返回",
"title": "使用 Zitadel 登录"
},
"accounts": {
"title": "账户",

View File

@@ -10,10 +10,17 @@ import {
} from "@/lib/zitadel";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
// import { getLocale } from "next-intl/server";
import { headers } from "next/headers";
import Link from "next/link";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("accounts");
return { title: t('title')};
}
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
const cookieIds = await getAllSessionCookieIds();

View File

@@ -19,9 +19,16 @@ import {
} from "@/lib/zitadel";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
// import { getLocale } from "next-intl/server";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("authenticator");
return { title: t('title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -4,8 +4,15 @@ import { Translated } from "@/components/translated";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("device");
return { title: t('usercode.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -3,8 +3,15 @@ import { SignInWithIdp } from "@/components/sign-in-with-idp";
import { Translated } from "@/components/translated";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("idp");
return { title: t('title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -9,17 +9,25 @@ import * as Tooltip from "@radix-ui/react-tooltip";
import { Analytics } from "@vercel/analytics/react";
import { Lato } from "next/font/google";
import { ReactNode, Suspense } from "react";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
const lato = Lato({
weight: ["400", "700", "900"],
subsets: ["latin"],
});
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("common");
return { title: t('title')};
}
export default async function RootLayout({
children,
}: {
children: ReactNode;
}) {
return (
<html className={`${lato.className}`} suppressHydrationWarning>
<head />

View File

@@ -10,8 +10,15 @@ import {
getLoginSettings,
} from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("loginname");
return { title: t('title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -5,8 +5,15 @@ import { getAllSessionCookieIds } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { getBrandingSettings, getDefaultOrg, listSessions } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("logout");
return { title: t('title')};
}
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
const cookieIds = await getAllSessionCookieIds();

View File

@@ -12,8 +12,15 @@ import {
getSession,
listAuthenticationMethodTypes,
} from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("mfa");
return { title: t('verify.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -16,8 +16,15 @@ import {
} from "@/lib/zitadel";
import { Timestamp, timestampDate } from "@zitadel/client";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("mfa");
return { title: t('set.title')};
}
function isSessionValid(session: Partial<Session>): {
valid: boolean;
verifiedAt?: Timestamp;

View File

@@ -11,8 +11,15 @@ import {
getLoginSettings,
getSession,
} from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("otp");
return { title: t('verify.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
params: Promise<Record<string | number | symbol, string | undefined>>;

View File

@@ -7,8 +7,15 @@ import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getSession } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("passkey");
return { title: t('verify.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -6,8 +6,15 @@ import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("passkey");
return { title: t('set.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -10,8 +10,15 @@ import {
getLoginSettings,
getPasswordComplexitySettings,
} from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("password");
return { title: t('change.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -11,8 +11,15 @@ import {
getLoginSettings,
} from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("password");
return { title: t('verify.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -13,8 +13,15 @@ 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 { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("password");
return { title: t('set.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -14,8 +14,15 @@ 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 { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("register");
return { title: t('title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -15,9 +15,16 @@ import {
getLoginSettings,
getSession,
} from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
import Link from "next/link";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("signedin");
return { title: t('title', { user: '' })};
}
async function loadSessionById(
serviceUrl: string,
sessionId: string,

View File

@@ -7,8 +7,15 @@ import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getSession } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("u2f");
return { title: t('verify.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -6,8 +6,15 @@ import { UserAvatar } from "@/components/user-avatar";
import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("u2f");
return { title: t('set.title')};
}
export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {

View File

@@ -8,8 +8,15 @@ 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 { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("verify");
return { title: t('verify.title')};
}
export default async function Page(props: { searchParams: Promise<any> }) {
const searchParams = await props.searchParams;