From 718e0a622831845338bbc24f0e2f5ce7979413f3 Mon Sep 17 00:00:00 2001 From: mstfyldz Date: Wed, 3 Jun 2026 02:35:14 +0300 Subject: [PATCH] =?UTF-8?q?Build=20Kozmos=20QR=20menu=20site=20=E2=80=94?= =?UTF-8?q?=20full=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next.js 16 App Router, TR/EN i18n via [lang] routing and proxy.ts, Tailwind v4, minimal list menu style, sticky bottom tab bar with Intersection Observer, PWA manifest, WhatsApp fixed button. Co-Authored-By: Claude Sonnet 4.6 --- app/[lang]/dictionaries.ts | 16 +++ app/[lang]/layout.tsx | 42 ++++++ app/[lang]/page.tsx | 57 ++++++++ app/globals.css | 102 +++++++++++-- app/layout.tsx | 46 +++--- app/manifest.ts | 26 ++++ app/page.tsx | 66 +-------- components/BottomTabBar.tsx | 72 ++++++++++ components/CategorySection.tsx | 38 +++++ components/Header.tsx | 45 ++++++ components/MenuItemRow.tsx | 38 +++++ components/WhatsAppButton.tsx | 32 +++++ data/menu.ts | 253 +++++++++++++++++++++++++++++++++ dictionaries/en.json | 27 ++++ dictionaries/tr.json | 27 ++++ proxy.ts | 22 +++ 16 files changed, 811 insertions(+), 98 deletions(-) create mode 100644 app/[lang]/dictionaries.ts create mode 100644 app/[lang]/layout.tsx create mode 100644 app/[lang]/page.tsx create mode 100644 app/manifest.ts create mode 100644 components/BottomTabBar.tsx create mode 100644 components/CategorySection.tsx create mode 100644 components/Header.tsx create mode 100644 components/MenuItemRow.tsx create mode 100644 components/WhatsAppButton.tsx create mode 100644 data/menu.ts create mode 100644 dictionaries/en.json create mode 100644 dictionaries/tr.json create mode 100644 proxy.ts diff --git a/app/[lang]/dictionaries.ts b/app/[lang]/dictionaries.ts new file mode 100644 index 0000000..c51062a --- /dev/null +++ b/app/[lang]/dictionaries.ts @@ -0,0 +1,16 @@ +import 'server-only' + +const dictionaries = { + tr: () => import('../../dictionaries/tr.json').then((m) => m.default), + en: () => import('../../dictionaries/en.json').then((m) => m.default), +} + +export type Locale = keyof typeof dictionaries + +export function hasLocale(locale: string): locale is Locale { + return locale in dictionaries +} + +export async function getDictionary(locale: Locale) { + return dictionaries[locale]() +} diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx new file mode 100644 index 0000000..d374a76 --- /dev/null +++ b/app/[lang]/layout.tsx @@ -0,0 +1,42 @@ +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' +import { getDictionary, hasLocale } from './dictionaries' + +export async function generateStaticParams() { + return [{ lang: 'tr' }, { lang: 'en' }] +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string }> +}): Promise { + const { lang } = await params + if (!hasLocale(lang)) return {} + const dict = await getDictionary(lang) + return { + title: dict.meta.title, + description: dict.meta.description, + manifest: '/manifest.webmanifest', + appleWebApp: { + capable: true, + statusBarStyle: 'black-translucent', + title: 'Kozmos', + }, + other: { + 'theme-color': '#3D2B1F', + }, + } +} + +export default async function LangLayout({ + children, + params, +}: { + children: React.ReactNode + params: Promise<{ lang: string }> +}) { + const { lang } = await params + if (!hasLocale(lang)) notFound() + return <>{children} +} diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx new file mode 100644 index 0000000..56709f6 --- /dev/null +++ b/app/[lang]/page.tsx @@ -0,0 +1,57 @@ +import { notFound } from 'next/navigation' +import { getDictionary, hasLocale } from './dictionaries' +import { categories } from '@/data/menu' +import Header from '@/components/Header' +import CategorySection from '@/components/CategorySection' +import BottomTabBar from '@/components/BottomTabBar' +import WhatsAppButton from '@/components/WhatsAppButton' + +export default async function MenuPage({ + params, +}: { + params: Promise<{ lang: string }> +}) { + const { lang } = await params + if (!hasLocale(lang)) notFound() + + const dict = await getDictionary(lang) + + return ( + <> +
+ +
+ {/* Subtle top fade from header */} +
+ + + + + + ) +} diff --git a/app/globals.css b/app/globals.css index a2dc41e..72f29ff 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,100 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --cream: #F5F0E8; + --beige: #E8DCC8; + --beige-dark: #D4C8B0; + --teal: #4AAFA8; + --teal-dark: #3D9A94; + --green: #5C8A5C; + --coffee: #3D2B1F; + --coffee-light: #5A3D2D; + --muted: #8A7265; + --header-h: 64px; + --tabbar-h: 68px; } @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --color-cream: var(--cream); + --color-beige: var(--beige); + --color-beige-dark: var(--beige-dark); + --color-teal: var(--teal); + --color-teal-dark: var(--teal-dark); + --color-green: var(--green); + --color-coffee: var(--coffee); + --color-coffee-light: var(--coffee-light); + --color-muted: var(--muted); + --font-display: var(--font-playfair); + --font-body: var(--font-inter); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; +html { + scroll-behavior: smooth; +} + +/* Grain texture overlay */ +body::after { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + z-index: 9998; + opacity: 0.028; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='256' height='256' filter='url(%23n)'/%3E%3C/svg%3E"); + background-repeat: repeat; + background-size: 256px 256px; +} + +/* Category scroll offset for sticky header */ +.category-section { + scroll-margin-top: calc(var(--header-h) + 12px); +} + +/* Menu item fade-up animation */ +@keyframes fadeUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +.menu-item-row { + animation: fadeUp 0.35s ease both; + animation-delay: calc(var(--item-index, 0) * 55ms); +} + +/* Tab bar active indicator */ +.tab-active-dot { + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--teal); + margin: 0 auto; + transition: opacity 0.2s ease; +} + +/* Smooth tab color transition */ +.tab-item { + transition: color 0.2s ease; +} + +/* WhatsApp pulse ring */ +@keyframes pulse-ring { + 0% { + box-shadow: 0 0 0 0 rgba(37, 211, 102, 0.45); + } + 70% { + box-shadow: 0 0 0 12px rgba(37, 211, 102, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(37, 211, 102, 0); + } +} + +.wa-btn { + animation: pulse-ring 2.5s ease-out infinite; } diff --git a/app/layout.tsx b/app/layout.tsx index 976eb90..aaad9ce 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,33 +1,37 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; +import type { Metadata } from 'next' +import { Playfair_Display, Inter } from 'next/font/google' +import './globals.css' -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); +const playfair = Playfair_Display({ + subsets: ['latin'], + variable: '--font-playfair', + display: 'swap', + style: ['normal', 'italic'], + weight: ['400', '500', '600', '700'], +}) -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +const inter = Inter({ + subsets: ['latin'], + variable: '--font-inter', + display: 'swap', +}) export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", -}; + title: 'Kozmos Beach & More', + description: 'Kozmos Beach & More dijital menüsü', +} export default function RootLayout({ children, -}: Readonly<{ - children: React.ReactNode; -}>) { +}: { + children: React.ReactNode +}) { return ( - {children} + {children} - ); + ) } diff --git a/app/manifest.ts b/app/manifest.ts new file mode 100644 index 0000000..905f40a --- /dev/null +++ b/app/manifest.ts @@ -0,0 +1,26 @@ +import type { MetadataRoute } from 'next' + +export default function manifest(): MetadataRoute.Manifest { + return { + name: 'Kozmos Beach & More', + short_name: 'Kozmos', + description: 'Kozmos Beach & More dijital menüsü', + start_url: '/tr', + display: 'standalone', + orientation: 'portrait', + background_color: '#F5F0E8', + theme_color: '#3D2B1F', + icons: [ + { + src: '/icons/icon-192.png', + sizes: '192x192', + type: 'image/png', + }, + { + src: '/icons/icon-512.png', + sizes: '512x512', + type: 'image/png', + }, + ], + } +} diff --git a/app/page.tsx b/app/page.tsx index 3f36f7c..9a36af0 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,5 @@ -import Image from "next/image"; +import { redirect } from 'next/navigation' -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
- -
-
- ); +export default function RootPage() { + redirect('/tr') } diff --git a/components/BottomTabBar.tsx b/components/BottomTabBar.tsx new file mode 100644 index 0000000..4beb276 --- /dev/null +++ b/components/BottomTabBar.tsx @@ -0,0 +1,72 @@ +'use client' + +import { useEffect, useState } from 'react' +import type { Category } from '@/data/menu' + +type Props = { + categories: Category[] + lang: string +} + +export default function BottomTabBar({ categories, lang }: Props) { + const l = lang as 'tr' | 'en' + const [activeId, setActiveId] = useState(categories[0]?.id ?? '') + + useEffect(() => { + const observers: IntersectionObserver[] = [] + + categories.forEach((cat) => { + const el = document.getElementById(cat.id) + if (!el) return + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveId(cat.id) + } + }) + }, + { rootMargin: '-20% 0px -75% 0px', threshold: 0 } + ) + + observer.observe(el) + observers.push(observer) + }) + + return () => { + observers.forEach((obs) => obs.disconnect()) + } + }, [categories]) + + return ( + + ) +} diff --git a/components/CategorySection.tsx b/components/CategorySection.tsx new file mode 100644 index 0000000..acfa907 --- /dev/null +++ b/components/CategorySection.tsx @@ -0,0 +1,38 @@ +import type { Category } from '@/data/menu' +import MenuItemRow from './MenuItemRow' + +type Props = { + category: Category + lang: string + currency: string +} + +export default function CategorySection({ category, lang, currency }: Props) { + const l = lang as 'tr' | 'en' + return ( +
+ {/* Category heading */} +
+ {category.emoji} +

+ {category.label[l]} +

+ {/* Decorative line */} +
+
+ + {/* Items */} +
+ {category.items.map((item, i) => ( + + ))} +
+
+ ) +} diff --git a/components/Header.tsx b/components/Header.tsx new file mode 100644 index 0000000..77d90cf --- /dev/null +++ b/components/Header.tsx @@ -0,0 +1,45 @@ +import Link from 'next/link' + +type Dict = { + header: { tagline: string } + langSwitch: { current: string; other: string; otherHref: string } +} + +export default function Header({ dict }: { dict: Dict }) { + return ( +
+ {/* Thin teal accent line at bottom */} +
+ +
+ {/* Logo */} +
+ + KOZMOS + + + {dict.header.tagline} + +
+ + {/* Language switcher */} + + + {dict.langSwitch.current} + + · + + {dict.langSwitch.other} + + +
+
+ ) +} diff --git a/components/MenuItemRow.tsx b/components/MenuItemRow.tsx new file mode 100644 index 0000000..6840196 --- /dev/null +++ b/components/MenuItemRow.tsx @@ -0,0 +1,38 @@ +import type { MenuItem } from '@/data/menu' + +type Props = { + item: MenuItem + lang: string + currency: string + index: number +} + +export default function MenuItemRow({ item, lang, currency, index }: Props) { + const l = lang as 'tr' | 'en' + return ( +
+ {/* Emoji circle */} +
+ {item.emoji} +
+ + {/* Text */} +
+

+ {item.name[l]} +

+

+ {item.description[l]} +

+
+ + {/* Price */} + + {currency}{item.price} + +
+ ) +} diff --git a/components/WhatsAppButton.tsx b/components/WhatsAppButton.tsx new file mode 100644 index 0000000..7e04498 --- /dev/null +++ b/components/WhatsAppButton.tsx @@ -0,0 +1,32 @@ +import { WHATSAPP_NUMBER } from '@/data/menu' + +type Props = { + message: string + label: string +} + +export default function WhatsAppButton({ message, label }: Props) { + const href = `https://wa.me/${WHATSAPP_NUMBER}?text=${encodeURIComponent(message)}` + + return ( + + {/* WhatsApp icon */} + + + ) +} diff --git a/data/menu.ts b/data/menu.ts new file mode 100644 index 0000000..d1ec853 --- /dev/null +++ b/data/menu.ts @@ -0,0 +1,253 @@ +export type LocalizedString = { tr: string; en: string } + +export type MenuItem = { + id: string + emoji: string + name: LocalizedString + description: LocalizedString + price: number + highlight?: boolean +} + +export type Category = { + id: string + emoji: string + label: LocalizedString + items: MenuItem[] +} + +export const WHATSAPP_NUMBER = '905XXXXXXXXXX' + +export const categories: Category[] = [ + { + id: 'starters', + emoji: '🥗', + label: { tr: 'Başlangıçlar', en: 'Starters' }, + items: [ + { + id: 's1', + emoji: '🥗', + name: { tr: 'Akdeniz Salatası', en: 'Mediterranean Salad' }, + description: { tr: 'Domates, salatalık, zeytin, hellim peyniri', en: 'Tomato, cucumber, olives, halloumi' }, + price: 120, + }, + { + id: 's2', + emoji: '🧀', + name: { tr: 'Hellim Izgara', en: 'Grilled Halloumi' }, + description: { tr: 'Zeytinyağı, taze nane, limon', en: 'Olive oil, fresh mint, lemon' }, + price: 95, + }, + { + id: 's3', + emoji: '🫙', + name: { tr: 'Zeytinyağlı Meze Tabağı', en: 'Meze Platter' }, + description: { tr: 'Günlük hazır mezelerin seçimi', en: 'Daily selection of mezze' }, + price: 145, + }, + { + id: 's4', + emoji: '🍆', + name: { tr: 'Patlıcan Ezmesi', en: 'Smoked Eggplant Dip' }, + description: { tr: 'Közlenmiş patlıcan, sarımsak, zeytinyağı', en: 'Roasted eggplant, garlic, olive oil' }, + price: 85, + }, + ], + }, + { + id: 'mains', + emoji: '🍽️', + label: { tr: 'Ana Yemekler', en: 'Main Courses' }, + items: [ + { + id: 'm1', + emoji: '🐟', + name: { tr: 'Balık Izgara', en: 'Grilled Fish' }, + description: { tr: 'Günlük taze balık, sebze garnitürü', en: 'Daily fresh catch, vegetable garnish' }, + price: 285, + }, + { + id: 'm2', + emoji: '🐠', + name: { tr: 'Levrek Buğulama', en: 'Steamed Sea Bass' }, + description: { tr: 'Sebzeli buğulama, zeytinyağlı sos', en: 'Steamed with vegetables, olive oil sauce' }, + price: 295, + }, + { + id: 'm3', + emoji: '🐙', + name: { tr: 'Ahtapot Izgara', en: 'Grilled Octopus' }, + description: { tr: 'Kekikli marinad, kapari sosu', en: 'Thyme marinade, caper sauce' }, + price: 320, + }, + { + id: 'm4', + emoji: '🍢', + name: { tr: 'Tavuk Şiş', en: 'Chicken Skewers' }, + description: { tr: 'Baharatlı tavuk, közlenmiş biber', en: 'Spiced chicken, roasted peppers' }, + price: 175, + }, + { + id: 'm5', + emoji: '🥘', + name: { tr: 'Vegetaryen Güveç', en: 'Vegetarian Stew' }, + description: { tr: 'Mevsim sebzeler, nohut, domates sos', en: 'Seasonal vegetables, chickpeas, tomato sauce' }, + price: 155, + }, + ], + }, + { + id: 'pizzas', + emoji: '🍕', + label: { tr: 'Pizzalar', en: 'Pizzas' }, + items: [ + { + id: 'p1', + emoji: '🍕', + name: { tr: 'Margherita', en: 'Margherita' }, + description: { tr: 'San marzano domates, bufala mozarella, fesleğen', en: 'San marzano tomatoes, buffalo mozzarella, basil' }, + price: 165, + }, + { + id: 'p2', + emoji: '🍕', + name: { tr: 'Karışık Pizza', en: 'Mixed Pizza' }, + description: { tr: 'Salam, mantar, biber, mozarella', en: 'Salami, mushroom, pepper, mozzarella' }, + price: 185, + }, + { + id: 'p3', + emoji: '🦐', + name: { tr: 'Deniz Ürünleri', en: 'Seafood Pizza' }, + description: { tr: 'Karides, kalamar, fesleğen sosu', en: 'Shrimp, calamari, basil sauce' }, + price: 215, + }, + { + id: 'p4', + emoji: '🌿', + name: { tr: 'Fıstık Pesto', en: 'Pesto Pizza' }, + description: { tr: 'Fıstıklı pesto, roka, parmesan', en: 'Pistachio pesto, rocket, parmesan' }, + price: 175, + }, + ], + }, + { + id: 'mezze', + emoji: '🫒', + label: { tr: 'Mezeler', en: 'Mezze' }, + items: [ + { + id: 'mz1', + emoji: '🐟', + name: { tr: 'Tarama', en: 'Tarama' }, + description: { tr: 'Balık yumurtası ezmesi, taze limon', en: 'Fish roe spread, fresh lemon' }, + price: 75, + }, + { + id: 'mz2', + emoji: '🧄', + name: { tr: 'Haydari', en: 'Haydari' }, + description: { tr: 'Yoğurt, sarımsak, dereotu', en: 'Yogurt, garlic, dill' }, + price: 65, + }, + { + id: 'mz3', + emoji: '🥐', + name: { tr: 'Sigara Böreği', en: 'Cheese Pastry Rolls' }, + description: { tr: 'Peynirli, 5 adet', en: 'Cheese-filled, 5 pieces' }, + price: 85, + }, + { + id: 'mz4', + emoji: '🐚', + name: { tr: 'Midye Tava', en: 'Fried Mussels' }, + description: { tr: 'Tarator sos ile servis', en: 'Served with tarator sauce' }, + price: 95, + }, + { + id: 'mz5', + emoji: '🍤', + name: { tr: 'Karides Güveç', en: 'Shrimp Casserole' }, + description: { tr: 'Domates sos, baharatlar, fırında', en: 'Tomato sauce, spices, oven-baked' }, + price: 135, + }, + ], + }, + { + id: 'drinks', + emoji: '🍹', + label: { tr: 'İçecekler', en: 'Drinks' }, + items: [ + { + id: 'd1', + emoji: '🍸', + name: { tr: 'Mojito', en: 'Mojito' }, + description: { tr: 'Nane, lime, soda, rum', en: 'Mint, lime, soda, rum' }, + price: 95, + }, + { + id: 'd2', + emoji: '🍊', + name: { tr: 'Aperol Spritz', en: 'Aperol Spritz' }, + description: { tr: 'Aperol, prosecco, portakal dilimi', en: 'Aperol, prosecco, orange slice' }, + price: 115, + }, + { + id: 'd3', + emoji: '🍋', + name: { tr: 'Limonata', en: 'Lemonade' }, + description: { tr: 'Taze sıkılmış, nane seçeneği ile', en: 'Freshly squeezed, optional mint' }, + price: 55, + }, + { + id: 'd4', + emoji: '🥛', + name: { tr: 'Ayran', en: 'Ayran' }, + description: { tr: 'Ev yapımı, taze çırpılmış', en: 'Homemade, freshly churned' }, + price: 35, + }, + { + id: 'd5', + emoji: '🍵', + name: { tr: 'Soğuk Çay', en: 'Iced Tea' }, + description: { tr: 'Şeftali veya limon aromalı', en: 'Peach or lemon flavored' }, + price: 45, + }, + ], + }, + { + id: 'desserts', + emoji: '🍮', + label: { tr: 'Tatlılar', en: 'Desserts' }, + items: [ + { + id: 'dt1', + emoji: '🍨', + name: { tr: 'Dondurma', en: 'Ice Cream' }, + description: { tr: '3 top, günlük seçim', en: '3 scoops, daily selection' }, + price: 85, + }, + { + id: 'dt2', + emoji: '🥮', + name: { tr: 'Baklava', en: 'Baklava' }, + description: { tr: 'Antep fıstıklı, kaymak ile', en: 'Pistachio, with clotted cream' }, + price: 75, + }, + { + id: 'dt3', + emoji: '🍮', + name: { tr: 'Panna Cotta', en: 'Panna Cotta' }, + description: { tr: 'Çilek sosu, taze nane', en: 'Strawberry sauce, fresh mint' }, + price: 95, + }, + { + id: 'dt4', + emoji: '🥞', + name: { tr: 'Akıtma', en: 'Akıtma' }, + description: { tr: 'Geleneksel, nar ekşisi ile', en: 'Traditional, with pomegranate molasses' }, + price: 65, + }, + ], + }, +] diff --git a/dictionaries/en.json b/dictionaries/en.json new file mode 100644 index 0000000..2b3e1c5 --- /dev/null +++ b/dictionaries/en.json @@ -0,0 +1,27 @@ +{ + "meta": { + "title": "Kozmos Beach & More — Menu", + "description": "Kozmos Beach & More digital menu" + }, + "header": { + "tagline": "BEACH & MORE" + }, + "nav": { + "starters": "Starters", + "mains": "Main Courses", + "pizzas": "Pizzas", + "mezze": "Mezze", + "drinks": "Drinks", + "desserts": "Desserts" + }, + "whatsapp": { + "label": "Reserve", + "message": "Hello, I would like to make a reservation." + }, + "currency": "₺", + "langSwitch": { + "current": "EN", + "other": "TR", + "otherHref": "/tr" + } +} diff --git a/dictionaries/tr.json b/dictionaries/tr.json new file mode 100644 index 0000000..29959af --- /dev/null +++ b/dictionaries/tr.json @@ -0,0 +1,27 @@ +{ + "meta": { + "title": "Kozmos Beach & More — Menü", + "description": "Kozmos Beach & More dijital menüsü" + }, + "header": { + "tagline": "BEACH & MORE" + }, + "nav": { + "starters": "Başlangıçlar", + "mains": "Ana Yemekler", + "pizzas": "Pizzalar", + "mezze": "Mezeler", + "drinks": "İçecekler", + "desserts": "Tatlılar" + }, + "whatsapp": { + "label": "Rezervasyon", + "message": "Merhaba, rezervasyon yapmak istiyorum." + }, + "currency": "₺", + "langSwitch": { + "current": "TR", + "other": "EN", + "otherHref": "/en" + } +} diff --git a/proxy.ts b/proxy.ts new file mode 100644 index 0000000..562d378 --- /dev/null +++ b/proxy.ts @@ -0,0 +1,22 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +const locales = ['tr', 'en'] +const defaultLocale = 'tr' + +export function proxy(request: NextRequest) { + const { pathname } = request.nextUrl + + const hasLocale = locales.some( + (locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`) + ) + + if (!hasLocale) { + request.nextUrl.pathname = `/${defaultLocale}${pathname}` + return NextResponse.redirect(request.nextUrl) + } +} + +export const config = { + matcher: ['/((?!_next|favicon.ico|icons|manifest.webmanifest).*)'], +}