AllAi/apps/web/app/[locale]/blog/[slug]/page.tsx
lapich_valya 2e5b4ed0fc переводы вкладок сверху слева
сделала переводы для всех вкладок "О нас", "Цены", "FAQ", "Блог" на русском, испанском и португальском
2025-11-16 01:44:42 +03:00

164 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}