feat(blog): isolate articles by locale and add initial english posts

This commit is contained in:
Poyraz
2026-06-27 14:42:52 +03:00
parent ac20bd697c
commit 66639bf31c
10 changed files with 321 additions and 35 deletions
+11 -10
View File
@@ -1,9 +1,9 @@
"use client";
import { useState, useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Link, useRouter } from "@/i18n/routing";
import { Icon } from "@iconify/react";
import { useTranslations } from "next-intl";
import { Badge, Card, Typography } from "poyraz-ui/atoms";
import {
ArticleCard,
@@ -34,6 +34,7 @@ function buildHref(params: { page?: number; category?: string; search?: string }
export function BlogContent({ data }: BlogContentProps) {
const router = useRouter();
const t = useTranslations("Blog");
const [searchInput, setSearchInput] = useState(data.searchQuery);
const pageNumbers = Array.from({ length: data.totalPages }, (_, i) => i + 1);
const hasArticles = data.articles.length > 0;
@@ -59,7 +60,7 @@ export function BlogContent({ data }: BlogContentProps) {
{/* Kategoriler */}
<div className="flex flex-wrap items-center gap-2">
<Typography variant="small" className="mr-1 text-muted-foreground">
Kategori:
{t("categories")}:
</Typography>
{data.categories.map((category) => (
<Link
@@ -92,7 +93,7 @@ export function BlogContent({ data }: BlogContentProps) {
onKeyDown={(e) => {
if (e.key === "Enter") submitSearch(searchInput);
}}
placeholder="Başlık veya içerikte ara..."
placeholder={t("searchPlaceholder")}
className="w-full rounded-sm border border-border bg-background py-2 pr-10 pl-9 text-sm text-foreground placeholder:text-muted-foreground focus:border-red-600 focus:ring-1 focus:ring-red-600/30 focus:outline-none"
/>
{searchInput && (
@@ -115,7 +116,7 @@ export function BlogContent({ data }: BlogContentProps) {
{(data.searchQuery || data.selectedCategory !== "All") && (
<div className="flex items-center gap-2">
<Typography variant="small" className="text-muted-foreground">
{data.articles.length} sonuç
{data.articles.length} {t("results")}
{data.searchQuery ? ` · "${data.searchQuery}"` : ""}
{data.selectedCategory !== "All" ? ` · ${data.selectedCategory}` : ""}
</Typography>
@@ -125,7 +126,7 @@ export function BlogContent({ data }: BlogContentProps) {
className="inline-flex cursor-pointer items-center gap-1 rounded-sm border border-border px-2 py-0.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
>
<Icon icon="mdi:filter-remove-outline" width={14} height={14} />
Temizle
{t("clear")}
</button>
</div>
)}
@@ -160,10 +161,10 @@ export function BlogContent({ data }: BlogContentProps) {
/>
<Typography variant="p" className="text-muted-foreground">
{data.searchQuery
? `"${data.searchQuery}" aramasına uygun sonuç bulunamadı.`
? t("noSearch", { search: data.searchQuery })
: data.selectedCategory === "All"
? "Henüz blog yazısı bulunmuyor."
: `"${data.selectedCategory}" kategorisinde henüz blog yazısı bulunmuyor.`}
? t("empty")
: t("emptyCategory", { category: data.selectedCategory })}
</Typography>
{(data.searchQuery || data.selectedCategory !== "All") && (
<button
@@ -172,7 +173,7 @@ export function BlogContent({ data }: BlogContentProps) {
className="mt-3 inline-flex cursor-pointer items-center gap-1.5 rounded-sm border border-border px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
<Icon icon="mdi:filter-remove-outline" width={16} height={16} />
Filtreleri Temizle
{t("clearFilters")}
</button>
)}
</Card>
+12 -9
View File
@@ -1,7 +1,8 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
@@ -94,6 +95,7 @@ function extractText(children: React.ReactNode): string {
}
function MermaidBlock({ chart }: { chart: string }) {
const t = useTranslations("Blog");
const [svg, setSvg] = useState("");
const [error, setError] = useState("");
const idRef = useRef(`mermaid-${Math.random().toString(36).slice(2)}`);
@@ -117,7 +119,7 @@ function MermaidBlock({ chart }: { chart: string }) {
}
} catch {
if (mounted) {
setError("Mermaid diyagramı oluşturulamadı.");
setError(t("diagramError"));
}
}
};
@@ -126,7 +128,7 @@ function MermaidBlock({ chart }: { chart: string }) {
return () => {
mounted = false;
};
}, [chart]);
}, [chart, t]);
if (error) {
return (
@@ -142,7 +144,7 @@ function MermaidBlock({ chart }: { chart: string }) {
return (
<Card className="rounded-sm border-border p-3">
<Typography variant="small" className="text-muted-foreground">
Diyagram hazırlanıyor...
{t("diagramLoading")}
</Typography>
</Card>
);
@@ -162,6 +164,7 @@ const GISCUS_CATEGORY =
const GISCUS_CATEGORY_ID = process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID || "";
export function BlogDetailContent({ post }: BlogDetailContentProps) {
const t = useTranslations("Blog");
const progressBarRef = useRef<HTMLDivElement | null>(null);
const [tocOpen, setTocOpen] = useState(false);
@@ -199,7 +202,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
href="/blog"
className="inline-flex items-center rounded-sm border border-border px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
{"<- Blog'a dön"}
{t("backToBlog")}
</Link>
<header className="space-y-3">
@@ -270,7 +273,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
},
p: ({ node, children, ...props }) => {
const hasImg = node?.children?.some(
(c: any) => c.tagName === "img",
(c: unknown) => (c as { tagName?: string }).tagName === "img",
);
if (hasImg) {
return (
@@ -405,7 +408,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
{showGiscus && (
<section className="border-t border-border pt-6">
<Typography variant="h3" className="mb-4">
Yorumlar
{t("comments")}
</Typography>
<GiscusComments
repo={GISCUS_REPO}
@@ -430,7 +433,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
type="button"
onClick={() => setTocOpen(true)}
className="fixed right-4 bottom-6 z-40 flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-border bg-background shadow-lg transition-transform hover:scale-105 active:scale-95 lg:hidden"
aria-label="İçindekiler"
aria-label={t("toc")}
>
<Icon
icon="mdi:table-of-contents"
@@ -449,7 +452,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
onClick={(e) => e.stopPropagation()}
>
<div className="mb-3 flex items-center justify-between">
<Typography variant="large">İçindekiler</Typography>
<Typography variant="large">{t("toc")}</Typography>
<button
type="button"
onClick={closeToc}