Files
netrunner-landing/app/[lang]/blog/page.tsx
T
nineap 32b4460b66 Локализует лендинг на 10 языков + RTL для арабского и персидского
Добавляет zh-CN, fa, ar, tr, uk, be, vi, uz словари (170 ключей, идентичная
вложенная структура) к существующим en/ru. lib/locales.ts — единый источник
списка локалей/дефолтной локали/RTL-набора вместо захардкоженного ["en","ru"]
в proxy.ts. language-switcher.tsx: переключение языка резало только
двухбуквенные коды регэкспом (/^\/[a-z]{2}/), из-за чего zh-CN никогда не
матчился и путь задваивался — заменено на honest-поиск по списку locales.

app/[lang]/layout.tsx — dir="rtl"/"ltr" на <html> по локали (SSR, в отличие
от реактивного клиентского переключения в netrunner-app). Header.tsx/
Footer.tsx — разбитое на три <span> имя бренда визуально переворачивалось
Flexbox'ом под dir=rtl ("VPNrunnerNet"), исправлено через dir="ltr" на
самом враппере.

BlogPreview/Faq/Pricing и обе blog-страницы: fallback на *_en-коллекцию
PocketBase, если для языка ещё нет переведённого контента (FAQ-ответы,
детали тарифов, посты блога не тянутся автоматически — это отдельный от
статических словарей CMS-контент), плюс i18n-ключи period1m/period6m/
period1y вместо захардкоженных ru/en-подписей периодов в Pricing.tsx.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 22:53:37 +07:00

98 lines
3.7 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 { pb } from "@/lib/pb";
import Link from "next/link";
import { Badge } from "@/components/ui/badge";
import { getDictionary } from "@/lib/dictionaries";
interface PBPost {
id: string;
title: string;
slug: string;
excerpt: string;
lang: string;
created: string;
published: boolean;
}
interface Props {
params: Promise<{ lang: string }>;
}
export default async function BlogIndexPage({ params }: Props) {
const { lang } = await params;
const dict = await getDictionary(lang); // Подключаем словарь
let records: PBPost[] = [];
// CMS-контент переведён только для части языков — коллекция `posts_<lang>`
// для новых языков ещё не заведена в PocketBase. Пока для них нет перевода,
// показываем английские записи, а не пустой архив.
for (const fallbackLang of Array.from(new Set([lang, "en"]))) {
try {
// Передаём кастомный fetch с next.revalidate, чтобы PocketBase SDK использовал
// нативное ISR-кэширование Next.js (обновление раз в час) вместо запроса на
// каждый рендер — иначе SDK делает обычный fetch без опций кэша Next.js.
records = await pb.collection(`posts_${fallbackLang}`).getFullList<PBPost>({
filter: "published = true",
sort: "-created",
fetch: (url, config) =>
fetch(url, { ...config, next: { revalidate: 3600 } }),
});
break;
} catch (err) {
console.error(`Failed to fetch blog posts (${fallbackLang}):`, err);
}
}
return (
<main className="min-h-screen pt-32 pb-20 container mx-auto max-w-5xl px-4">
<Badge
variant="outline"
className="mb-6 border-primary/30 text-primary uppercase tracking-widest"
>
{dict.blog.archive}
</Badge>
<h1 className="text-5xl md:text-6xl font-extrabold tracking-tighter mb-12">
{dict.blog.systemLogs}
</h1>
{records.length === 0 ? (
<div className="p-20 border border-dashed border-border/50 rounded-3xl text-center bg-card/10">
<p className="text-muted-foreground font-mono italic">
{dict.blog.emptySector}
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{records.map((post) => (
<Link
href={`/${lang}/blog/${post.slug}`}
key={post.id}
className="group block"
>
<article className="p-8 border border-border/50 rounded-2xl bg-card/20 hover:bg-card/40 hover:border-primary/50 transition-all duration-300 h-full flex flex-col">
<div className="text-xs text-primary font-mono mb-4 flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-primary rounded-full animate-pulse" />
{new Date(post.created).toLocaleDateString(lang || "en")}
</div>
<h2 className="text-2xl font-bold mb-3 group-hover:text-primary transition-colors duration-300">
{post.title}
</h2>
<p className="text-muted-foreground text-sm leading-relaxed line-clamp-3">
{post.excerpt}
</p>
<div className="mt-8 pt-4 border-t border-border/30 text-xs font-bold uppercase tracking-widest text-primary/50 group-hover:text-primary transition-colors">
{dict.blog.decrypt}
</div>
</article>
</Link>
))}
</div>
)}
</main>
);
}