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": { "common": {
"back": "Zurück" "back": "Zurück",
"title": "Anmelden mit Zitadel"
}, },
"accounts": { "accounts": {
"title": "Konten", "title": "Konten",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,17 @@ import {
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { UserPlusIcon } from "@heroicons/react/24/outline"; import { UserPlusIcon } from "@heroicons/react/24/outline";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; 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 { getLocale } from "next-intl/server";
import { headers } from "next/headers"; import { headers } from "next/headers";
import Link from "next/link"; 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 }) { async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
const cookieIds = await getAllSessionCookieIds(); const cookieIds = await getAllSessionCookieIds();

View File

@@ -19,9 +19,16 @@ import {
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
// import { getLocale } from "next-intl/server"; // import { getLocale } from "next-intl/server";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { redirect } from "next/navigation"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { getServiceUrlFromHeaders } from "@/lib/service-url";
import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel"; import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; 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"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { Translated } from "@/components/translated";
import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { Analytics } from "@vercel/analytics/react";
import { Lato } from "next/font/google"; import { Lato } from "next/font/google";
import { ReactNode, Suspense } from "react"; import { ReactNode, Suspense } from "react";
import type { Metadata } from "next";
import { getTranslations } from "next-intl/server";
const lato = Lato({ const lato = Lato({
weight: ["400", "700", "900"], weight: ["400", "700", "900"],
subsets: ["latin"], subsets: ["latin"],
}); });
export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations("common");
return { title: t('title')};
}
export default async function RootLayout({ export default async function RootLayout({
children, children,
}: { }: {
children: ReactNode; children: ReactNode;
}) { }) {
return ( return (
<html className={`${lato.className}`} suppressHydrationWarning> <html className={`${lato.className}`} suppressHydrationWarning>
<head /> <head />

View File

@@ -10,8 +10,15 @@ import {
getLoginSettings, getLoginSettings,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; 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"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { getServiceUrlFromHeaders } from "@/lib/service-url";
import { getBrandingSettings, getDefaultOrg, listSessions } from "@/lib/zitadel"; import { getBrandingSettings, getDefaultOrg, listSessions } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; 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"; 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 }) { async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
const cookieIds = await getAllSessionCookieIds(); const cookieIds = await getAllSessionCookieIds();

View File

@@ -12,8 +12,15 @@ import {
getSession, getSession,
listAuthenticationMethodTypes, listAuthenticationMethodTypes,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) { }) {

View File

@@ -16,8 +16,15 @@ import {
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Timestamp, timestampDate } from "@zitadel/client"; import { Timestamp, timestampDate } from "@zitadel/client";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; 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"; 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>): { function isSessionValid(session: Partial<Session>): {
valid: boolean; valid: boolean;
verifiedAt?: Timestamp; verifiedAt?: Timestamp;

View File

@@ -11,8 +11,15 @@ import {
getLoginSettings, getLoginSettings,
getSession, getSession,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
params: 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 { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session"; import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getBrandingSettings, getSession } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session"; import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel"; import { getBrandingSettings } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) { }) {

View File

@@ -10,8 +10,15 @@ import {
getLoginSettings, getLoginSettings,
getPasswordComplexitySettings, getPasswordComplexitySettings,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) { }) {

View File

@@ -11,8 +11,15 @@ import {
getLoginSettings, getLoginSettings,
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; 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"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) { }) {

View File

@@ -13,8 +13,15 @@ import {
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_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"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) { }) {

View File

@@ -14,8 +14,15 @@ import {
} from "@/lib/zitadel"; } from "@/lib/zitadel";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_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"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) { }) {

View File

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

View File

@@ -7,8 +7,15 @@ import { getSessionCookieById } from "@/lib/cookies";
import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session"; import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getSession } from "@/lib/zitadel"; import { getBrandingSettings, getSession } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { getServiceUrlFromHeaders } from "@/lib/service-url";
import { loadMostRecentSession } from "@/lib/session"; import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel"; import { getBrandingSettings } from "@/lib/zitadel";
import { Metadata } from "next";
import { getTranslations } from "next-intl/server";
import { headers } from "next/headers"; 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: { export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>; 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 { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { getBrandingSettings, getUserByID } from "@/lib/zitadel";
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_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"; 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> }) { export default async function Page(props: { searchParams: Promise<any> }) {
const searchParams = await props.searchParams; const searchParams = await props.searchParams;