сделала переводы для всех вкладок "О нас", "Цены", "FAQ", "Блог" на русском, испанском и португальском
164 lines
5.4 KiB
TypeScript
164 lines
5.4 KiB
TypeScript
import { notFound } from "next/navigation";
|
||
import type { Metadata } from "next";
|
||
import { loadDictionary } from "@allai/i18n/server";
|
||
import { locales, resolveLocale } from "@/config/i18n";
|
||
import { getBlogPosts } from "@/features/marketing/blogData";
|
||
import { absoluteUrl, buildCanonical, buildLocaleAlternates, buildOpenGraph, buildTwitterCard } from "@/seo/seoUtils";
|
||
|
||
const PATH_PREFIX = "/blog" as const;
|
||
|
||
type PageProps = {
|
||
params: { locale: string; slug: string };
|
||
};
|
||
|
||
export function generateStaticParams() {
|
||
return locales.flatMap((locale) => getBlogPosts(locale).map((post) => ({ locale, slug: post.slug })));
|
||
}
|
||
|
||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||
const locale = resolveLocale(params.locale);
|
||
|
||
if (!locales.includes(locale)) {
|
||
return {
|
||
title: "Article not found"
|
||
};
|
||
}
|
||
|
||
const dictionary = await loadDictionary(locale);
|
||
const post = getBlogPosts(locale).find((item) => item.slug === params.slug);
|
||
|
||
if (!post) {
|
||
const notFoundTitle =
|
||
locale === "ru"
|
||
? `Статья не найдена | ${dictionary.common.brandLong ?? dictionary.common.brandShort}`
|
||
: locale === "es"
|
||
? `Artículo no encontrado | ${dictionary.common.brandLong ?? dictionary.common.brandShort}`
|
||
: locale === "pt"
|
||
? `Artigo não encontrado | ${dictionary.common.brandLong ?? dictionary.common.brandShort}`
|
||
: `Article not found | ${dictionary.common.brandLong ?? dictionary.common.brandShort}`;
|
||
return { title: notFoundTitle };
|
||
}
|
||
|
||
const title = `${post.title} | ${dictionary.common.brandLong ?? dictionary.common.brandShort}`;
|
||
const description = post.excerpt;
|
||
const path = `${PATH_PREFIX}/${post.slug}`;
|
||
|
||
return {
|
||
title,
|
||
description,
|
||
alternates: {
|
||
canonical: buildCanonical(locale, path),
|
||
languages: buildLocaleAlternates(path)
|
||
},
|
||
openGraph: {
|
||
...buildOpenGraph({
|
||
locale,
|
||
title,
|
||
description,
|
||
path,
|
||
type: "article"
|
||
}),
|
||
publishedTime: post.publishedAt,
|
||
section: post.category
|
||
},
|
||
twitter: buildTwitterCard({
|
||
title,
|
||
description
|
||
})
|
||
};
|
||
}
|
||
|
||
export default async function BlogArticlePage({ params }: PageProps) {
|
||
const locale = resolveLocale(params.locale);
|
||
|
||
if (!locales.includes(locale)) {
|
||
notFound();
|
||
}
|
||
|
||
const dictionary = await loadDictionary(locale);
|
||
const post = getBlogPosts(locale).find((item) => item.slug === params.slug);
|
||
|
||
if (!post) {
|
||
notFound();
|
||
}
|
||
|
||
const canonical = buildCanonical(locale, `${PATH_PREFIX}/${post.slug}`);
|
||
const publisher = {
|
||
"@type": "Organization",
|
||
name: dictionary.common.brandLong ?? dictionary.common.brandShort,
|
||
logo: {
|
||
"@type": "ImageObject",
|
||
url: absoluteUrl("/favicon.ico")
|
||
}
|
||
};
|
||
|
||
const structuredData = {
|
||
"@context": "https://schema.org",
|
||
"@type": "BlogPosting",
|
||
headline: post.title,
|
||
description: post.excerpt,
|
||
author: publisher,
|
||
publisher,
|
||
datePublished: post.publishedAt,
|
||
dateModified: post.publishedAt,
|
||
articleSection: post.category,
|
||
mainEntityOfPage: canonical,
|
||
url: canonical,
|
||
inLanguage: locale,
|
||
wordCount: post.content.replace(/<[^>]+>/g, " ").trim().split(/\s+/).length
|
||
};
|
||
|
||
return (
|
||
<main style={{ maxWidth: 960, margin: "0 auto", padding: "72px 24px", display: "grid", gap: 32 }}>
|
||
<script
|
||
type="application/ld+json"
|
||
suppressHydrationWarning
|
||
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
||
/>
|
||
|
||
<header style={{ display: "grid", gap: 12 }}>
|
||
<span style={{ fontSize: 14, textTransform: "uppercase", color: "var(--color-muted)", fontWeight: 600 }}>
|
||
{post.category}
|
||
</span>
|
||
<h1 style={{ fontSize: 46, fontWeight: 700, margin: 0 }}>{post.title}</h1>
|
||
<div style={{ display: "flex", gap: 12, fontSize: 14, color: "var(--color-muted)" }}>
|
||
<span>{post.published}</span>
|
||
<span>{`\u2022`}</span>
|
||
<span>{post.readingTime}</span>
|
||
</div>
|
||
</header>
|
||
|
||
<article
|
||
style={{ color: "var(--color-foreground)", lineHeight: 1.75, fontSize: 18, display: "grid", gap: 24 }}
|
||
dangerouslySetInnerHTML={{ __html: post.content }}
|
||
/>
|
||
|
||
<footer style={{ borderTop: "1px solid var(--surface-border)", paddingTop: 24 }}>
|
||
<p style={{ color: "var(--color-muted)" }}>
|
||
{locale === "ru" ? (
|
||
<>
|
||
Понравилась статья? Смотрите другие материалы в <a href={`/${locale}/blog`}>блоге</a> или пишите нам на{" "}
|
||
<a href="mailto:hello@allai.studio">hello@allai.studio</a>.
|
||
</>
|
||
) : locale === "es" ? (
|
||
<>
|
||
¿Te gustó el artículo? Explora más ideas en el <a href={`/${locale}/blog`}>blog</a> o escríbenos a{" "}
|
||
<a href="mailto:hello@allai.studio">hello@allai.studio</a>.
|
||
</>
|
||
) : locale === "pt" ? (
|
||
<>
|
||
Gostou do artigo? Veja mais ideias no <a href={`/${locale}/blog`}>blog</a> ou escreva para{" "}
|
||
<a href="mailto:hello@allai.studio">hello@allai.studio</a>.
|
||
</>
|
||
) : (
|
||
<>
|
||
Enjoyed this article? Explore more insights on the <a href={`/${locale}/blog`}>blog</a> or reach out to us
|
||
at <a href="mailto:hello@allai.studio">hello@allai.studio</a>.
|
||
</>
|
||
)}
|
||
</p>
|
||
</footer>
|
||
</main>
|
||
);
|
||
}
|