feat(i18n): localize static elements, navbar switcher, and search palette

This commit is contained in:
Poyraz
2026-06-27 14:42:45 +03:00
parent d3c56713fb
commit 0d112c6568
7 changed files with 96 additions and 68 deletions
+5 -2
View File
@@ -1,6 +1,7 @@
import Link from "next/link"; import { Link } from "@/i18n/routing";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { Card, Typography } from "poyraz-ui/atoms"; import { Card, Typography } from "poyraz-ui/atoms";
import { useTranslations } from "next-intl";
export const CONTACT_LINKS = [ export const CONTACT_LINKS = [
{ {
@@ -27,6 +28,8 @@ export const CONTACT_LINKS = [
] as const; ] as const;
export function ContactContent() { export function ContactContent() {
const t = useTranslations("Contact");
return ( return (
<section className="grid gap-3 md:grid-cols-3"> <section className="grid gap-3 md:grid-cols-3">
{CONTACT_LINKS.map((item) => ( {CONTACT_LINKS.map((item) => (
@@ -42,7 +45,7 @@ export function ContactContent() {
<Icon icon={item.icon} width={18} height={18} /> <Icon icon={item.icon} width={18} height={18} />
</div> </div>
<Typography variant="large" className="mt-3 text-base leading-tight"> <Typography variant="large" className="mt-3 text-base leading-tight">
{item.label} {t(item.id)}
</Typography> </Typography>
<Typography variant="small" className="mt-1 text-muted-foreground"> <Typography variant="small" className="mt-1 text-muted-foreground">
{item.value} {item.value}
+14 -11
View File
@@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { Button, Card, Typography } from "poyraz-ui/atoms"; import { Button, Card, Typography } from "poyraz-ui/atoms";
import { Modal, ModalContent, ModalTitle } from "poyraz-ui/molecules"; import { Modal, ModalContent, ModalTitle } from "poyraz-ui/molecules";
import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed"; import { YoutubeLiteEmbed } from "@/components/youtube-lite-embed";
import { useTranslations } from "next-intl";
type ContentContentProps = { type ContentContentProps = {
youtubeLinks: readonly string[]; youtubeLinks: readonly string[];
@@ -11,6 +12,7 @@ type ContentContentProps = {
}; };
function PdfFirstPagePreview({ src, title }: { src: string; title: string }) { function PdfFirstPagePreview({ src, title }: { src: string; title: string }) {
const t = useTranslations("Content");
const canvasRef = useRef<HTMLCanvasElement | null>(null); const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [failed, setFailed] = useState(false); const [failed, setFailed] = useState(false);
@@ -86,7 +88,7 @@ function PdfFirstPagePreview({ src, title }: { src: string; title: string }) {
return ( return (
<div className="flex h-full w-full items-center justify-center bg-muted/20 p-2"> <div className="flex h-full w-full items-center justify-center bg-muted/20 p-2">
<Typography variant="small" className="text-muted-foreground"> <Typography variant="small" className="text-muted-foreground">
Önizleme yüklenemedi {t("pdfPreviewError")}
</Typography> </Typography>
</div> </div>
); );
@@ -107,6 +109,7 @@ export function ContentContent({
youtubeLinks, youtubeLinks,
pdfFiles, pdfFiles,
}: ContentContentProps) { }: ContentContentProps) {
const t = useTranslations("Content");
const [pdfModalOpen, setPdfModalOpen] = useState(false); const [pdfModalOpen, setPdfModalOpen] = useState(false);
const [activePdfIndex, setActivePdfIndex] = useState(0); const [activePdfIndex, setActivePdfIndex] = useState(0);
@@ -128,7 +131,7 @@ export function ContentContent({
<section className="flex h-full flex-col gap-3 overflow-y-auto"> <section className="flex h-full flex-col gap-3 overflow-y-auto">
<section className="space-y-2"> <section className="space-y-2">
<Typography variant="large" className="text-base"> <Typography variant="large" className="text-base">
Son YouTube Videoları {t("youtubeTitle")}
</Typography> </Typography>
<div className="grid gap-2 md:grid-cols-3"> <div className="grid gap-2 md:grid-cols-3">
{embeddedVideos.map((link) => ( {embeddedVideos.map((link) => (
@@ -136,7 +139,7 @@ export function ContentContent({
key={link} key={link}
className="overflow-hidden rounded-sm border-border p-0" className="overflow-hidden rounded-sm border-border p-0"
> >
<YoutubeLiteEmbed link={link} title="YouTube video oynatici" /> <YoutubeLiteEmbed link={link} title={t("youtubeEmbedTitle")} />
</Card> </Card>
))} ))}
</div> </div>
@@ -144,7 +147,7 @@ export function ContentContent({
<section className="space-y-2"> <section className="space-y-2">
<Typography variant="large" className="text-base"> <Typography variant="large" className="text-base">
LinkedIn PDF Notları {t("pdfTitle")}
</Typography> </Typography>
<div className="grid grid-cols-2 gap-2 lg:grid-cols-3"> <div className="grid grid-cols-2 gap-2 lg:grid-cols-3">
{pdfFiles.map((pdf, index) => ( {pdfFiles.map((pdf, index) => (
@@ -157,7 +160,7 @@ export function ContentContent({
<Card className="overflow-hidden rounded-sm border-border p-0 transition-colors hover:border-zinc-700"> <Card className="overflow-hidden rounded-sm border-border p-0 transition-colors hover:border-zinc-700">
<PdfFirstPagePreview <PdfFirstPagePreview
src={`/pdf/${pdf}`} src={`/pdf/${pdf}`}
title={`${pdf} önizleme`} title={t("pdfPreviewTitle", { name: pdf })}
/> />
</Card> </Card>
</button> </button>
@@ -168,9 +171,9 @@ export function ContentContent({
<Modal open={pdfModalOpen} onOpenChange={setPdfModalOpen}> <Modal open={pdfModalOpen} onOpenChange={setPdfModalOpen}>
<ModalContent size="xl" className="rounded-sm p-4"> <ModalContent size="xl" className="rounded-sm p-4">
<ModalTitle> <ModalTitle>
{activePdf ? activePdf.replace(/\.pdf$/i, "") : "PDF Notu"} {activePdf ? activePdf.replace(/\.pdf$/i, "") : t("pdfModalDefaultTitle")}
</ModalTitle> </ModalTitle>
<div className="mt-3 flex flex-wrap items-center justify-between gap-2"> <div className="mt-3 flex flex-wrap items-center justify-between gap-2">
<Typography variant="small" className="text-muted-foreground"> <Typography variant="small" className="text-muted-foreground">
{pdfFiles.length === 0 {pdfFiles.length === 0
@@ -187,7 +190,7 @@ export function ContentContent({
setActivePdfIndex((prev) => Math.max(0, prev - 1)) setActivePdfIndex((prev) => Math.max(0, prev - 1))
} }
> >
Önceki {t("prev")}
</Button> </Button>
<Button <Button
type="button" type="button"
@@ -200,11 +203,11 @@ export function ContentContent({
) )
} }
> >
Sonraki {t("next")}
</Button> </Button>
</div> </div>
</div> </div>
{activePdf ? ( {activePdf ? (
<div className="mt-3 h-[70dvh] overflow-hidden rounded-sm border border-border"> <div className="mt-3 h-[70dvh] overflow-hidden rounded-sm border border-border">
<iframe <iframe
@@ -216,7 +219,7 @@ export function ContentContent({
) : ( ) : (
<Card className="mt-3 rounded-sm border-border p-3"> <Card className="mt-3 rounded-sm border-border p-3">
<Typography variant="small" className="text-muted-foreground"> <Typography variant="small" className="text-muted-foreground">
`/public/pdf` içinde PDF bulunamadı. {t("pdfNotFound")}
</Typography> </Typography>
</Card> </Card>
)} )}
+6 -4
View File
@@ -5,12 +5,14 @@ import { useCallback, useEffect, useState } from "react";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { Typography } from "poyraz-ui/atoms"; import { Typography } from "poyraz-ui/atoms";
import { StaggerContainer, StaggerItem } from "@/components/motion-wrapper"; import { StaggerContainer, StaggerItem } from "@/components/motion-wrapper";
import { useTranslations } from "next-intl";
type GalleryContentProps = { type GalleryContentProps = {
images: string[]; images: string[];
}; };
export function GalleryContent({ images }: GalleryContentProps) { export function GalleryContent({ images }: GalleryContentProps) {
const t = useTranslations("Gallery");
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null); const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
// Keyboard navigation for lightbox // Keyboard navigation for lightbox
@@ -53,7 +55,7 @@ export function GalleryContent({ images }: GalleryContentProps) {
<section className="flex h-full items-center justify-center"> <section className="flex h-full items-center justify-center">
<div className="rounded-xl border border-dashed border-border py-20 px-10 text-center"> <div className="rounded-xl border border-dashed border-border py-20 px-10 text-center">
<Typography variant="muted"> <Typography variant="muted">
Henüz galeriye görsel eklenmemiş. {t("empty")}
</Typography> </Typography>
</div> </div>
</section> </section>
@@ -95,7 +97,7 @@ export function GalleryContent({ images }: GalleryContentProps) {
type="button" type="button"
className="absolute top-4 right-4 z-10 flex h-12 w-12 cursor-pointer items-center justify-center rounded-full bg-zinc-100 text-zinc-900 transition-colors hover:bg-zinc-200 shadow-sm border border-zinc-200" className="absolute top-4 right-4 z-10 flex h-12 w-12 cursor-pointer items-center justify-center rounded-full bg-zinc-100 text-zinc-900 transition-colors hover:bg-zinc-200 shadow-sm border border-zinc-200"
onClick={closeLightbox} onClick={closeLightbox}
aria-label="Kapat" aria-label={t("close")}
> >
<Icon icon="mdi:close" width={24} height={24} /> <Icon icon="mdi:close" width={24} height={24} />
</button> </button>
@@ -109,7 +111,7 @@ export function GalleryContent({ images }: GalleryContentProps) {
e.stopPropagation(); e.stopPropagation();
setLightboxIndex(lightboxIndex - 1); setLightboxIndex(lightboxIndex - 1);
}} }}
aria-label="Önceki" aria-label={t("prev")}
> >
<Icon icon="mdi:chevron-left" width={32} height={32} /> <Icon icon="mdi:chevron-left" width={32} height={32} />
</button> </button>
@@ -124,7 +126,7 @@ export function GalleryContent({ images }: GalleryContentProps) {
e.stopPropagation(); e.stopPropagation();
setLightboxIndex(lightboxIndex + 1); setLightboxIndex(lightboxIndex + 1);
}} }}
aria-label="Sonraki" aria-label={t("next")}
> >
<Icon icon="mdi:chevron-right" width={32} height={32} /> <Icon icon="mdi:chevron-right" width={32} height={32} />
</button> </button>
+3 -2
View File
@@ -4,6 +4,7 @@ import Image from "next/image";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { Card, Typography } from "poyraz-ui/atoms"; import { Card, Typography } from "poyraz-ui/atoms";
import { NewsCard } from "poyraz-ui/molecules"; import { NewsCard } from "poyraz-ui/molecules";
import { useTranslations } from "next-intl";
type HomeHeroProps = { type HomeHeroProps = {
news: { news: {
@@ -19,6 +20,7 @@ type HomeHeroProps = {
export function HomeHero({ news }: HomeHeroProps) { export function HomeHero({ news }: HomeHeroProps) {
const [frame, setFrame] = useState<1 | 2>(1); const [frame, setFrame] = useState<1 | 2>(1);
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null); const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const t = useTranslations("Home");
const handleMouseEnter = () => { const handleMouseEnter = () => {
if (intervalRef.current) return; if (intervalRef.current) return;
@@ -62,8 +64,7 @@ export function HomeHero({ news }: HomeHeroProps) {
</Typography> </Typography>
</div> </div>
<Typography variant="small" className="text-muted-foreground"> <Typography variant="small" className="text-muted-foreground">
Teknolojiyi merak eden bir genç. Yazılım geliştirme, yapay zeka ve {t("role")}
teknoloji dünyasındaki gelişmeleri takip ediyor.
</Typography> </Typography>
</div> </div>
+32 -26
View File
@@ -1,8 +1,8 @@
"use client"; "use client";
import Image from "next/image";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import Link from "next/link"; import { Link } from "@/i18n/routing";
import { useTranslations } from "next-intl";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { import {
Avatar, Avatar,
@@ -23,15 +23,6 @@ import {
type CategoryFilter = "all" | LinkDirectoryCategory; type CategoryFilter = "all" | LinkDirectoryCategory;
const FILTER_ITEMS: ReadonlyArray<{ id: CategoryFilter; label: string }> = [
{ id: "all", label: "Tüm kategoriler" },
...LINK_DIRECTORY_CATEGORIES,
];
const CATEGORY_LABELS = Object.fromEntries(
LINK_DIRECTORY_CATEGORIES.map((item) => [item.id, item.label]),
) as Record<(typeof LINK_DIRECTORY_CATEGORIES)[number]["id"], string>;
const CATEGORY_ORDER = { const CATEGORY_ORDER = {
resources: 0, resources: 0,
navigation: 1, navigation: 1,
@@ -67,9 +58,20 @@ function formatHref(href: string) {
} }
export function LinksContent() { export function LinksContent() {
const t = useTranslations("Links");
const tNav = useTranslations("Nav");
const [activeCategory, setActiveCategory] = useState<CategoryFilter>("all"); const [activeCategory, setActiveCategory] = useState<CategoryFilter>("all");
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const filterItems = useMemo(() => [
{ id: "all" as const, label: t("allCategories") },
...LINK_DIRECTORY_CATEGORIES.map((item) => ({
id: item.id,
label: t(`categories.${item.id}`),
})),
], [t]);
const filteredItems = useMemo(() => { const filteredItems = useMemo(() => {
const normalizedQuery = normalize(query.trim()); const normalizedQuery = normalize(query.trim());
const queryTokens = normalizedQuery.split(/\s+/).filter(Boolean); const queryTokens = normalizedQuery.split(/\s+/).filter(Boolean);
@@ -84,12 +86,17 @@ export function LinksContent() {
} }
const haystack = normalize( const haystack = normalize(
[item.label, item.href, CATEGORY_LABELS[item.category], ...item.keywords].join(" "), [
tNav.has(item.id) ? tNav(item.id) : item.label,
item.href,
t(`categories.${item.category}`),
...item.keywords,
].join(" "),
); );
return queryTokens.every((token) => haystack.includes(token)); return queryTokens.every((token) => haystack.includes(token));
}); });
}, [activeCategory, query]); }, [activeCategory, query, t, tNav]);
return ( return (
<section className="relative isolate min-h-dvh overflow-hidden px-4 py-8 sm:px-6 sm:py-10"> <section className="relative isolate min-h-dvh overflow-hidden px-4 py-8 sm:px-6 sm:py-10">
@@ -128,8 +135,7 @@ export function LinksContent() {
</div> </div>
<Typography variant="small" className="max-w-lg text-sm leading-6 text-muted-foreground"> <Typography variant="small" className="max-w-lg text-sm leading-6 text-muted-foreground">
Burada sosyal hesaplarım, portfolyo sayfalarım ve hızlı erişim linklerimin {t("desc")}
tamamı tek yerde duruyor. Direkt açıp paylaşabilirsin.
</Typography> </Typography>
</div> </div>
@@ -165,7 +171,7 @@ export function LinksContent() {
</span> </span>
<div className="min-w-0"> <div className="min-w-0">
<Typography variant="large" className="truncate text-base"> <Typography variant="large" className="truncate text-base">
{item.label} {tNav.has(item.id) ? tNav(item.id) : item.label}
</Typography> </Typography>
<Typography <Typography
variant="small" variant="small"
@@ -183,10 +189,10 @@ export function LinksContent() {
<div className="space-y-3 pt-1"> <div className="space-y-3 pt-1">
<div> <div>
<Typography variant="large" className="text-base"> <Typography variant="large" className="text-base">
Tüm Linkler {t("allLinks")}
</Typography> </Typography>
<Typography variant="small" className="text-muted-foreground"> <Typography variant="small" className="text-muted-foreground">
Sayfalar, kaynaklar ve sosyal profiller {t("allLinksDesc")}
</Typography> </Typography>
</div> </div>
@@ -196,10 +202,10 @@ export function LinksContent() {
onValueChange={(value) => setActiveCategory(value as CategoryFilter)} onValueChange={(value) => setActiveCategory(value as CategoryFilter)}
> >
<SelectTrigger className="h-11 rounded-2xl border-border bg-background px-4 text-sm"> <SelectTrigger className="h-11 rounded-2xl border-border bg-background px-4 text-sm">
<SelectValue placeholder="Kategori seç" /> <SelectValue placeholder={t("selectCategory")} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{FILTER_ITEMS.map((item) => ( {filterItems.map((item) => (
<SelectItem key={item.id} value={item.id}> <SelectItem key={item.id} value={item.id}>
{item.label} {item.label}
</SelectItem> </SelectItem>
@@ -210,8 +216,8 @@ export function LinksContent() {
<Input <Input
value={query} value={query}
onChange={(event) => setQuery(event.target.value)} onChange={(event) => setQuery(event.target.value)}
placeholder="Link ara... github, medium, /blog" placeholder={t("searchPlaceholder")}
aria-label="Linklerde arama" aria-label={t("allLinks")}
className="h-11 rounded-2xl" className="h-11 rounded-2xl"
/> />
</div> </div>
@@ -234,10 +240,10 @@ export function LinksContent() {
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<Typography variant="large" className="text-base leading-tight"> <Typography variant="large" className="text-base leading-tight">
{item.label} {tNav.has(item.id) ? tNav(item.id) : item.label}
</Typography> </Typography>
<span className="inline-flex rounded-full border border-border px-2.5 py-0.5 text-[11px] text-muted-foreground"> <span className="inline-flex rounded-full border border-border px-2.5 py-0.5 text-[11px] text-muted-foreground">
{CATEGORY_LABELS[item.category]} {t(`categories.${item.category}`)}
</span> </span>
</div> </div>
@@ -264,7 +270,7 @@ export function LinksContent() {
{filteredItems.length === 0 ? ( {filteredItems.length === 0 ? (
<Card className="rounded-2xl border-border px-4 py-5"> <Card className="rounded-2xl border-border px-4 py-5">
<Typography variant="small" className="text-muted-foreground"> <Typography variant="small" className="text-muted-foreground">
Seçili filtreye uygun link bulunamadı. {t("empty")}
</Typography> </Typography>
</Card> </Card>
) : null} ) : null}
+18 -8
View File
@@ -1,8 +1,9 @@
"use client"; "use client";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import { useRouter } from "next/navigation"; import { useRouter } from "@/i18n/routing";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useLocale, useTranslations } from "next-intl";
import { import {
CommandPalette, CommandPalette,
CommandPaletteContent, CommandPaletteContent,
@@ -15,7 +16,7 @@ import {
CommandPaletteSeparator, CommandPaletteSeparator,
} from "poyraz-ui/molecules"; } from "poyraz-ui/molecules";
import { import {
COMMAND_PALETTE_GROUPS, getCommandPaletteGroups,
type CommandPaletteItem as PaletteItem, type CommandPaletteItem as PaletteItem,
} from "@/lib/command-palette-links"; } from "@/lib/command-palette-links";
import { useKeyboardShortcutLabel } from "@/lib/use-keyboard-shortcut-label"; import { useKeyboardShortcutLabel } from "@/lib/use-keyboard-shortcut-label";
@@ -27,8 +28,17 @@ type SearchCommandProps = {
export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { export function SearchCommand({ open, onOpenChange }: SearchCommandProps) {
const router = useRouter(); const router = useRouter();
const locale = useLocale();
const tLinks = useTranslations("Links");
const tNav = useTranslations("Nav");
const t = useTranslations("SearchCommand");
const shortcut = useKeyboardShortcutLabel(); const shortcut = useKeyboardShortcutLabel();
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const groups = useMemo(() => {
return getCommandPaletteGroups(locale, tLinks, tNav);
}, [locale, tLinks, tNav]);
const handleOpenChange = useCallback((nextOpen: boolean) => { const handleOpenChange = useCallback((nextOpen: boolean) => {
if (!nextOpen) { if (!nextOpen) {
setQuery(""); setQuery("");
@@ -51,11 +61,11 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) {
const filteredGroups = useMemo(() => { const filteredGroups = useMemo(() => {
const value = query.trim().toLowerCase(); const value = query.trim().toLowerCase();
if (!value) return COMMAND_PALETTE_GROUPS; if (!value) return groups;
const tokens = value.split(/\s+/).filter(Boolean); const tokens = value.split(/\s+/).filter(Boolean);
return COMMAND_PALETTE_GROUPS.map((group) => ({ return groups.map((group) => ({
...group, ...group,
items: group.items.filter((item) => { items: group.items.filter((item) => {
const haystack = [ const haystack = [
@@ -70,7 +80,7 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) {
return tokens.every((token) => haystack.includes(token)); return tokens.every((token) => haystack.includes(token));
}), }),
})).filter((group) => group.items.length > 0); })).filter((group) => group.items.length > 0);
}, [query]); }, [query, groups]);
const handleCommandSelect = (item: PaletteItem) => { const handleCommandSelect = (item: PaletteItem) => {
handleOpenChange(false); handleOpenChange(false);
@@ -87,13 +97,13 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) {
<CommandPalette open={open} onOpenChange={handleOpenChange}> <CommandPalette open={open} onOpenChange={handleOpenChange}>
<CommandPaletteContent className=" rounded-none p-0 pt-3 w-72 sm:w-full sm:max-w-xl sm:rounded-sm sm:p-0 sm:pt-2"> <CommandPaletteContent className=" rounded-none p-0 pt-3 w-72 sm:w-full sm:max-w-xl sm:rounded-sm sm:p-0 sm:pt-2">
<CommandPaletteInput <CommandPaletteInput
placeholder="Sayfa ve bağlantılarda ara..." placeholder={t("placeholder")}
className="mt-2 pr-10" className="mt-2 pr-10"
onValueChange={setQuery} onValueChange={setQuery}
/> />
<CommandPaletteList> <CommandPaletteList>
{filteredGroups.length === 0 ? ( {filteredGroups.length === 0 ? (
<CommandPaletteEmpty>Sonuç bulunamadı.</CommandPaletteEmpty> <CommandPaletteEmpty>{t("empty")}</CommandPaletteEmpty>
) : ( ) : (
filteredGroups.map((group, index) => ( filteredGroups.map((group, index) => (
<div key={group.id}> <div key={group.id}>
@@ -115,7 +125,7 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) {
)} )}
</CommandPaletteList> </CommandPaletteList>
<CommandPaletteFooter className="text-xs text-muted-foreground"> <CommandPaletteFooter className="text-xs text-muted-foreground">
Hızlıca açmak için {shortcut} kullan {t("footer", { shortcut })}
</CommandPaletteFooter> </CommandPaletteFooter>
</CommandPaletteContent> </CommandPaletteContent>
</CommandPalette> </CommandPalette>
+18 -15
View File
@@ -2,8 +2,9 @@
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import Link from "next/link"; import { Link, usePathname } from "@/i18n/routing";
import { usePathname } from "next/navigation"; import { useTranslations } from "next-intl";
import { LanguageSwitcher } from "@/components/language-switcher";
import { useState } from "react"; import { useState } from "react";
import { import {
Avatar, Avatar,
@@ -45,6 +46,7 @@ export function SiteNavbar() {
const pathname = usePathname(); const pathname = usePathname();
const [searchOpen, setSearchOpen] = useState(false); const [searchOpen, setSearchOpen] = useState(false);
const shortcut = useKeyboardShortcutLabel(); const shortcut = useKeyboardShortcutLabel();
const t = useTranslations("Nav");
const isActiveLink = (href: string) => { const isActiveLink = (href: string) => {
if (href === "/") return pathname === "/"; if (href === "/") return pathname === "/";
@@ -60,13 +62,14 @@ export function SiteNavbar() {
href={item.href} href={item.href}
target={item.external ? "_blank" : undefined} target={item.external ? "_blank" : undefined}
rel={item.external ? "noreferrer" : undefined} rel={item.external ? "noreferrer" : undefined}
aria-label={item.label} aria-label={t.has(item.id) ? t(item.id) : item.label}
title={item.label} title={t.has(item.id) ? t(item.id) : item.label}
className="inline-flex h-8 w-8 items-center justify-center rounded-sm border border-border text-muted-foreground transition-colors hover:text-foreground" className="inline-flex h-8 w-8 items-center justify-center rounded-sm border border-border text-muted-foreground transition-colors hover:text-foreground"
> >
<Icon icon={item.icon} width={16} height={16} /> <Icon icon={item.icon} width={16} height={16} />
</Link> </Link>
))} ))}
<LanguageSwitcher />
</div> </div>
<header className="flex items-center justify-between gap-3 border-b border-border pb-4"> <header className="flex items-center justify-between gap-3 border-b border-border pb-4">
@@ -99,7 +102,7 @@ export function SiteNavbar() {
href={item.href} href={item.href}
className={getNavLinkClass(isActiveLink(item.href))} className={getNavLinkClass(isActiveLink(item.href))}
> >
{item.label} {t(item.id)}
</Link> </Link>
</li> </li>
))} ))}
@@ -115,11 +118,11 @@ export function SiteNavbar() {
type="button" type="button"
onClick={() => setSearchOpen(true)} onClick={() => setSearchOpen(true)}
className="inline-flex h-8 w-44 cursor-pointer items-center justify-between rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground sm:w-52" className="inline-flex h-8 w-44 cursor-pointer items-center justify-between rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground sm:w-52"
aria-label="Komut paletini aç" aria-label={t("search")}
> >
<span className="inline-flex items-center gap-2"> <span className="inline-flex items-center gap-2">
<Icon icon="mdi:magnify" width={16} height={16} /> <Icon icon="mdi:magnify" width={16} height={16} />
<span>Ara</span> <span>{t("search")}</span>
</span> </span>
<span className="text-xs text-muted-foreground">{shortcut}</span> <span className="text-xs text-muted-foreground">{shortcut}</span>
</button> </button>
@@ -127,10 +130,10 @@ export function SiteNavbar() {
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground"> <DropdownMenuTrigger className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground">
<Icon icon="mdi:share-variant" width={16} height={16} /> <Icon icon="mdi:share-variant" width={16} height={16} />
<span>Sosyal</span> <span>{t("social")}</span>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52 rounded-sm"> <DropdownMenuContent align="end" className="w-52 rounded-sm">
<DropdownMenuLabel>Sosyal Bağlantılar</DropdownMenuLabel> <DropdownMenuLabel>{t("socialLinks")}</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{SOCIAL_LINKS.map((item) => ( {SOCIAL_LINKS.map((item) => (
<DropdownMenuItem key={item.id} asChild> <DropdownMenuItem key={item.id} asChild>
@@ -153,21 +156,21 @@ export function SiteNavbar() {
<Sheet> <Sheet>
<SheetTrigger className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground"> <SheetTrigger className="inline-flex h-8 cursor-pointer items-center gap-2 rounded-sm border border-border px-2.5 text-sm text-muted-foreground transition-colors hover:text-foreground">
<Icon icon="mdi:menu" width={18} height={18} /> <Icon icon="mdi:menu" width={18} height={18} />
<span>Menü</span> <span>{t("menu")}</span>
</SheetTrigger> </SheetTrigger>
<SheetContent side="right" className="w-72 p-4"> <SheetContent side="right" className="w-72 p-4">
<SheetTitle className="sr-only">Mobil Menü</SheetTitle> <SheetTitle className="sr-only">{t("mobileMenu")}</SheetTitle>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<SheetClose asChild> <SheetClose asChild>
<button <button
type="button" type="button"
onClick={() => setSearchOpen(true)} onClick={() => setSearchOpen(true)}
className="inline-flex h-9 w-full cursor-pointer items-center justify-between rounded-sm border border-border px-3 text-sm text-muted-foreground transition-colors hover:text-foreground" className="inline-flex h-9 w-full cursor-pointer items-center justify-between rounded-sm border border-border px-3 text-sm text-muted-foreground transition-colors hover:text-foreground"
aria-label="Komut paletini aç" aria-label={t("search")}
> >
<span className="inline-flex items-center gap-2"> <span className="inline-flex items-center gap-2">
<Icon icon="mdi:magnify" width={16} height={16} /> <Icon icon="mdi:magnify" width={16} height={16} />
<span>Ara</span> <span>{t("search")}</span>
</span> </span>
<span className="text-xs text-muted-foreground/80"> <span className="text-xs text-muted-foreground/80">
{shortcut} {shortcut}
@@ -186,7 +189,7 @@ export function SiteNavbar() {
href={item.href} href={item.href}
className={getNavLinkClass(isActiveLink(item.href))} className={getNavLinkClass(isActiveLink(item.href))}
> >
{item.label} {t(item.id)}
</Link> </Link>
</SheetClose> </SheetClose>
</li> </li>