From 241544cd08798cb7a6451d5f12682477555d97c8 Mon Sep 17 00:00:00 2001 From: dealerweb Date: Sat, 30 May 2026 13:51:21 +0200 Subject: [PATCH] Fix: correct and localize the description per locale The root (main)/layout renders ABOVE the [locale] segment, so next-intl's getLocale() returns the default locale there - emitting on every page regardless of UI language (e.g. /de) and a hardcoded English description. Both are strong "translate this page" triggers in Chrome. proxy.ts already exposes the nonce to server components via the x-middleware-request-* mechanism; expose the request pathname the same way as x-pathname, and have the root layout derive the locale from it (falling back to getLocale() when the path has no locale segment) for both and the localized meta_description (new key in all 17 locales; the English value is unchanged). --- app/(main)/layout.tsx | 25 ++++++++++++++++++++++--- locales/cs/common.json | 1 + locales/da/common.json | 1 + locales/de/common.json | 1 + locales/en/common.json | 1 + locales/es/common.json | 1 + locales/fr/common.json | 1 + locales/it/common.json | 1 + locales/ja/common.json | 1 + locales/ko/common.json | 1 + locales/lv/common.json | 1 + locales/nl/common.json | 1 + locales/pl/common.json | 1 + locales/pt/common.json | 1 + locales/ru/common.json | 1 + locales/tr/common.json | 1 + locales/uk/common.json | 1 + locales/zh/common.json | 1 + proxy.ts | 12 ++++++++---- 19 files changed, 47 insertions(+), 7 deletions(-) diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index ac5c3b42..dac3d3d3 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -1,13 +1,26 @@ import type { Metadata, Viewport } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import { headers } from "next/headers"; -import { getLocale } from "next-intl/server"; +import { getLocale, getTranslations } from "next-intl/server"; import { PWAInstallPrompt } from "@/components/pwa-install-prompt"; import { ServiceWorkerRegistration } from "@/components/service-worker-registration"; import { configManager } from "@/lib/admin/config-manager"; import { withBasePath } from "@/lib/browser-navigation"; +import { locales } from "@/i18n/routing"; import "../globals.css"; +// This layout renders and sits ABOVE the [locale] segment, so +// next-intl's getLocale() returns the default locale here - emitting +// on e.g. /de pages, which makes browsers offer to +// "translate this page". Recover the active locale from the request pathname +// (exposed by proxy.ts as x-pathname), falling back to getLocale() (cookie / +// Accept-Language) when the path carries no locale segment. +async function resolveRequestLocale(): Promise { + const pathname = (await headers()).get("x-pathname") || ""; + const seg = pathname.split("/").find((s) => (locales as readonly string[]).includes(s)); + return seg ?? (await getLocale()); +} + const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"], @@ -27,10 +40,16 @@ export const viewport: Viewport = { export async function generateMetadata(): Promise { await configManager.ensureLoaded(); const faviconUrl = configManager.get("faviconUrl", "/branding/Bulwark_Favicon.svg"); + // Localize the description to match the UI language; a hardcoded + // English description is another signal that makes Chrome offer to + // "translate this page". Resolve the locale from the request path, since this + // layout is above the [locale] segment (see resolveRequestLocale). + const locale = await resolveRequestLocale(); + const t = await getTranslations({ locale }); return { title: process.env.APP_NAME || process.env.NEXT_PUBLIC_APP_NAME || "Webmail", - description: "Minimalist webmail client using JMAP protocol", + description: t("meta_description"), appleWebApp: { capable: true, statusBarStyle: "black-translucent", @@ -48,7 +67,7 @@ export default async function RootLayout({ }: { children: React.ReactNode; }) { - const locale = await getLocale(); + const locale = await resolveRequestLocale(); const nonce = (await headers()).get("x-nonce") ?? ""; const parentOrigin = process.env.NEXT_PUBLIC_PARENT_ORIGIN || ""; diff --git a/locales/cs/common.json b/locales/cs/common.json index 6620a55b..1d52d740 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimalistický webmailový klient využívající protokol JMAP", "login": { "title": "Webmail", "username_label": "E-mail", diff --git a/locales/da/common.json b/locales/da/common.json index 2f455b42..0f316d4a 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimalistisk webmail-klient, der bruger JMAP-protokollen", "login": { "title": "Webmail", "username_label": "Email", diff --git a/locales/de/common.json b/locales/de/common.json index fa621a99..74b90d06 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimalistischer Webmail-Client mit JMAP-Protokoll", "login": { "title": "Webmail", "username_label": "E-Mail", diff --git a/locales/en/common.json b/locales/en/common.json index 1dbba593..ef8b8030 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimalist webmail client using JMAP protocol", "login": { "title": "Webmail", "username_label": "Email", diff --git a/locales/es/common.json b/locales/es/common.json index 7b83d66a..a436a5ea 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Cliente de correo web minimalista que usa el protocolo JMAP", "login": { "title": "Correo Web", "username_label": "Correo electrónico", diff --git a/locales/fr/common.json b/locales/fr/common.json index f9cb00ad..a24d1d65 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Client de messagerie web minimaliste utilisant le protocole JMAP", "login": { "title": "Webmail", "username_label": "Email", diff --git a/locales/it/common.json b/locales/it/common.json index 3c96458a..8794f7a5 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Client di posta web minimalista che utilizza il protocollo JMAP", "login": { "title": "Webmail", "username_label": "Email", diff --git a/locales/ja/common.json b/locales/ja/common.json index aa366688..4d4e7518 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -1,4 +1,5 @@ { + "meta_description": "JMAPプロトコルを使用するミニマルなウェブメールクライアント", "login": { "title": "ウェブメール", "username_label": "メールアドレス", diff --git a/locales/ko/common.json b/locales/ko/common.json index 79a238d7..031cc987 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -1,4 +1,5 @@ { + "meta_description": "JMAP 프로토콜을 사용하는 미니멀한 웹메일 클라이언트", "login": { "title": "웹메일", "username_label": "이메일", diff --git a/locales/lv/common.json b/locales/lv/common.json index 62a09509..c5718bee 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimālistisks tīmekļa pasta klients, kas izmanto JMAP protokolu", "login": { "title": "Tīmekļa pasts", "username_label": "E-pasta adrese", diff --git a/locales/nl/common.json b/locales/nl/common.json index d1208b36..601f0420 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimalistische webmailclient die het JMAP-protocol gebruikt", "login": { "title": "Webmail", "username_label": "E-mail", diff --git a/locales/pl/common.json b/locales/pl/common.json index ee550e28..ac3e37ce 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Minimalistyczny klient poczty internetowej wykorzystujący protokół JMAP", "login": { "title": "Webmail", "username_label": "E-mail", diff --git a/locales/pt/common.json b/locales/pt/common.json index 3f7ef129..d83bbfe8 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Cliente de webmail minimalista que usa o protocolo JMAP", "login": { "title": "Webmail", "username_label": "E-mail", diff --git a/locales/ru/common.json b/locales/ru/common.json index 747b269c..c772e73a 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Минималистичный веб-клиент электронной почты, использующий протокол JMAP", "login": { "title": "Веб-почта", "username_label": "Электронная почта", diff --git a/locales/tr/common.json b/locales/tr/common.json index 287522ab..ec052368 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -1,4 +1,5 @@ { + "meta_description": "JMAP protokolünü kullanan minimalist web posta istemcisi", "login": { "title": "Webmail", "username_label": "E-posta", diff --git a/locales/uk/common.json b/locales/uk/common.json index e08ee372..c25be2c1 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -1,4 +1,5 @@ { + "meta_description": "Мінімалістичний вебклієнт електронної пошти, що використовує протокол JMAP", "login": { "title": "Веб-пошта", "username_label": "Електронна пошта", diff --git a/locales/zh/common.json b/locales/zh/common.json index 00a7634f..835f5c5d 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -1,4 +1,5 @@ { + "meta_description": "使用 JMAP 协议的极简网页邮件客户端", "login": { "title": "网页邮箱", "username_label": "邮箱地址", diff --git a/proxy.ts b/proxy.ts index 26fc1dcb..5e97fbb5 100644 --- a/proxy.ts +++ b/proxy.ts @@ -143,11 +143,15 @@ export async function proxy(request: NextRequest) { const response = intlResponse ?? NextResponse.next(); const existing = response.headers.get("x-middleware-override-headers"); - response.headers.set( - "x-middleware-override-headers", - existing ? `${existing},x-nonce` : "x-nonce" - ); + // Expose the nonce AND the request pathname to server components as request + // headers. The root (main)/layout renders ABOVE the [locale] segment, + // so getLocale() can't resolve the active locale there and falls back to the + // default - emitting on e.g. /de pages, which makes browsers + // offer to "translate this page". The layout reads x-pathname to recover it. + const overrides = [existing, "x-nonce", "x-pathname"].filter(Boolean).join(","); + response.headers.set("x-middleware-override-headers", overrides); response.headers.set("x-middleware-request-x-nonce", nonce); + response.headers.set("x-middleware-request-x-pathname", pathname); response.headers.set("X-Content-Type-Options", "nosniff");