- Add full message catalogs and content for French (fr) and German (de) - Implement ASCII canonical slugs for French and German routes - Add language-specific search endpoints (/api/busca/fr, /api/busca/de) - Configure strict 404 responses for /fr/livro and /de/livro - Localize SEO, OpenGraph (fr_FR, de_DE), Twitter and JSON-LD schemas - Add localized code example comments across 14 examples for fr and de - Enable 'fr' and 'de' in ENABLED_LOCALES with root negotiation and redirect - Extend test suite in check-i18n, smoke-imagem and verifica-vazamentos Co-Authored-By: Antigravity <noreply@google.com>
98 lines
2.9 KiB
React
98 lines
2.9 KiB
React
import Link from "next/link";
|
|
|
|
/**
|
|
* Abertura padrão das páginas internas: trilha, rótulo, título, frase de apoio
|
|
* e ações. Mesma voz da /livro e da home, para que nenhuma página pareça
|
|
* de outro produto.
|
|
*/
|
|
export default function PageHero({
|
|
trail = [],
|
|
icon: Icon,
|
|
label,
|
|
title,
|
|
lede,
|
|
align = "left",
|
|
children,
|
|
isEn: isEnProp,
|
|
isEs: isEsProp,
|
|
isFr: isFrProp,
|
|
isDe: isDeProp,
|
|
locale: localeProp,
|
|
}) {
|
|
const center = align === "center";
|
|
const isEn =
|
|
isEnProp !== undefined
|
|
? isEnProp
|
|
: localeProp
|
|
? localeProp === "en"
|
|
: trail.some((t) => t.isEn || (t.href && t.href.startsWith("/en")));
|
|
const isEs =
|
|
isEsProp !== undefined
|
|
? isEsProp
|
|
: localeProp
|
|
? localeProp === "es"
|
|
: trail.some((t) => t.isEs || (t.href && t.href.startsWith("/es")));
|
|
const isFr =
|
|
isFrProp !== undefined
|
|
? isFrProp
|
|
: localeProp
|
|
? localeProp === "fr"
|
|
: trail.some((t) => t.isFr || (t.href && t.href.startsWith("/fr")));
|
|
const isDe =
|
|
isDeProp !== undefined
|
|
? isDeProp
|
|
: localeProp
|
|
? localeProp === "de"
|
|
: trail.some((t) => t.isDe || (t.href && t.href.startsWith("/de")));
|
|
|
|
const homeHref = isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/";
|
|
const homeLabel = isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início";
|
|
const breadcrumbAria = isEn
|
|
? "Breadcrumb navigation"
|
|
: isEs
|
|
? "Navegación por migas de pan"
|
|
: isFr
|
|
? "Fil d'Ariane"
|
|
: isDe
|
|
? "Brotkrumennavigation"
|
|
: "Trilha de navegação";
|
|
|
|
return (
|
|
<section className="shell pt-8 sm:pt-12">
|
|
{trail.length > 0 && (
|
|
<nav aria-label={breadcrumbAria} className="flex flex-wrap items-center gap-2 text-sm text-muted">
|
|
<Link href={homeHref} className="inline-flex min-h-11 min-w-11 items-center justify-center transition-colors hover:text-accent">
|
|
{homeLabel}
|
|
</Link>
|
|
{trail.map((item, i) => (
|
|
<span key={item.label} className="flex min-h-11 items-center gap-2">
|
|
<span aria-hidden="true" className="text-line-strong">/</span>
|
|
{item.href && i < trail.length - 1 ? (
|
|
<Link href={item.href} className="inline-flex min-h-11 min-w-11 items-center justify-center transition-colors hover:text-accent">
|
|
{item.label}
|
|
</Link>
|
|
) : (
|
|
<span aria-current="page" className="text-ink">
|
|
{item.label}
|
|
</span>
|
|
)}
|
|
</span>
|
|
))}
|
|
</nav>
|
|
)}
|
|
|
|
<div className={`${trail.length ? "mt-8 sm:mt-10" : ""} max-w-3xl ${center ? "mx-auto text-center" : ""}`}>
|
|
{label && (
|
|
<span className="chip chip-accent">
|
|
{Icon && <Icon className="h-3.5 w-3.5" aria-hidden="true" />}
|
|
{label}
|
|
</span>
|
|
)}
|
|
<h1 className={`page-title ${label ? "mt-5" : ""}`}>{title}</h1>
|
|
{lede && <p className={`lede mt-5 max-w-2xl ${center ? "mx-auto" : ""}`}>{lede}</p>}
|
|
{children && <div className={`mt-7 flex flex-wrap gap-3 ${center ? "justify-center" : ""}`}>{children}</div>}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|