i18n: translate all public page bodies (EN + IT)

Internationalised the ~44 public pages with next-intl (the shell was
already translated). Each page now pulls its copy from a "pages.<slug>"
namespace via getTranslations (server) / useTranslations (client); the
EN + IT catalogs were authored by parallel agents and merged centrally,
with natural Italian (ICU plurals) and it backfilled from en for any
gap. request.ts gained getMessageFallback/onError so a missing key
degrades to the English value, never a raw key.

Behaviour unchanged (only display text moved to t() calls; queries,
actions, fields, ContentCard structure preserved). Verified on the prod
server: with NEXT_LOCALE=it, home/community/staff/news/shop/rankings all
render Italian copy, no raw-key leakage; English unchanged. The nav
language switcher toggles EN/IT live. tsc 0, vitest 49/49, next build 0.

Admin pages intentionally left in English (staff tooling).
This commit is contained in:
2026-06-28 20:40:06 +02:00
parent 1199176883
commit e004dfedaf
47 changed files with 1740 additions and 471 deletions
+13 -10
View File
@@ -1,3 +1,4 @@
import { getTranslations } from "next-intl/server";
import Link from "next/link";
import { ContentCard, EmptyState } from "@/components/public/ui";
import { prisma } from "@/lib/prisma";
@@ -35,6 +36,8 @@ function isExternal(url: string): boolean {
}
export default async function HelpCenterPage() {
const t = await getTranslations("pages.help");
let categories: HelpCategory[] = [];
try {
categories = await prisma.websiteHelpCenterCategories.findMany({
@@ -91,23 +94,23 @@ export default async function HelpCenterPage() {
<main style={{ display: "grid", gap: "1.5rem" }}>
<ContentCard
icon="❓"
title="Help Center"
subtitle="Find guides, support topics, and the hotel rules — everything you need to get help."
title={t("title")}
subtitle={t("subtitle")}
action={
<Link className="btn btn-outline" href="/help/tickets">
My tickets →
{t("myTickets")}
</Link>
}
/>
<ContentCard
icon="🧭"
title="Support topics"
subtitle="Browse guides and answers to common questions"
title={t("topicsTitle")}
subtitle={t("topicsSubtitle")}
padded={categories.length === 0}
>
{categories.length === 0 ? (
<EmptyState icon="🧭">No help topics yet.</EmptyState>
<EmptyState icon="🧭">{t("topicsEmpty")}</EmptyState>
) : (
<div className="card-grid sm-2 lg-3" style={{ padding: "1rem" }}>
{categories.map((c) => (
@@ -169,12 +172,12 @@ export default async function HelpCenterPage() {
<ContentCard
icon="📜"
title="Hotel rules"
subtitle="Please read these before hanging out in the hotel"
title={t("rulesTitle")}
subtitle={t("rulesSubtitle")}
padded={ruleCategories.length === 0}
>
{ruleCategories.length === 0 ? (
<EmptyState icon="📜">No rules published yet.</EmptyState>
<EmptyState icon="📜">{t("rulesEmpty")}</EmptyState>
) : (
<div style={{ display: "grid", gap: "1rem", padding: "1rem" }}>
{ruleCategories.map((cat) => {
@@ -200,7 +203,7 @@ export default async function HelpCenterPage() {
{catRules.length === 0 ? (
<p className="muted" style={{ marginBottom: 0 }}>
No rules in this category.
{t("categoryEmpty")}
</p>
) : (
<ul style={{ margin: "0.85rem 0 0", paddingLeft: "1.2rem" }}>