From f8ca2034e0a93703da4248e99476d8601b8c8772 Mon Sep 17 00:00:00 2001
From: ayrisdev
Date: Thu, 11 Jun 2026 14:05:36 +0300
Subject: [PATCH] kite-menu-lang
---
app/MenuClient.tsx | 39 +++++++++++++++---
app/{ => [lang]}/layout.tsx | 21 ++++++++--
app/{ => [lang]}/page.tsx | 24 +++++++-----
app/favicon.ico | Bin 15406 -> 15406 bytes
implementation_plan.md | 62 +++++++++++++++++++++++++++++
lib/dictionaries.ts | 26 ++++++++++++
proxy.ts | 76 ++++++++++++++++++++++++------------
7 files changed, 206 insertions(+), 42 deletions(-)
rename app/{ => [lang]}/layout.tsx (57%)
rename app/{ => [lang]}/page.tsx (67%)
create mode 100644 implementation_plan.md
create mode 100644 lib/dictionaries.ts
diff --git a/app/MenuClient.tsx b/app/MenuClient.tsx
index 20f2206..760cfd2 100644
--- a/app/MenuClient.tsx
+++ b/app/MenuClient.tsx
@@ -2,9 +2,11 @@
import { useEffect, useState, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
+import { useRouter, usePathname } from "next/navigation";
import { MenuCategory, MenuItem as MenuItemType } from "@/data/menu";
import { CategoryNav } from "@/components/CategoryNav";
import { MenuItem } from "@/components/MenuItem";
+import { getDictionary } from "@/lib/dictionaries";
export const CATEGORY_ICONS: Record = {
"kahvaltiliklar": "🌅",
@@ -35,12 +37,23 @@ type SiteSettings = {
restaurantName: string
}
-export default function MenuClient({ initialCategories, siteSettings }: { initialCategories: MenuCategory[], siteSettings: SiteSettings }) {
+export default function MenuClient({ initialCategories, siteSettings, lang = "tr" }: { initialCategories: MenuCategory[], siteSettings: SiteSettings, lang?: string }) {
const [activeCategoryId, setActiveCategoryId] = useState(
initialCategories[0]?.id || ""
);
const [selectedItem, setSelectedItem] = useState(null);
const sectionRefs = useRef<(HTMLElement | null)[]>([]);
+
+ const router = useRouter();
+ const pathname = usePathname();
+ const t = getDictionary(lang);
+
+ const switchLanguage = (newLang: string) => {
+ if (newLang === lang) return;
+ // Replace the current locale in the pathname
+ const newPath = pathname.replace(`/${lang}`, `/${newLang}`);
+ router.push(newPath || `/${newLang}`);
+ };
useEffect(() => {
const observer = new IntersectionObserver(
@@ -81,6 +94,22 @@ export default function MenuClient({ initialCategories, siteSettings }: { initia
}}
/>
+ {/* Language Switcher */}
+
+
+
+
+
{/* Dark overlay to make text readable */}
@@ -96,7 +125,7 @@ export default function MenuClient({ initialCategories, siteSettings }: { initia
className="text-[10px] tracking-[0.38em] uppercase mb-8 font-sans font-medium"
style={{ color: "rgba(255,220,160,1)" }}
>
- Akyaka · Muğla
+ {siteSettings.location || t.hero.location}
{/* Logo */}
@@ -120,7 +149,7 @@ export default function MenuClient({ initialCategories, siteSettings }: { initia
className="text-xs font-sans tracking-wide"
style={{ color: "rgba(255,218,168,0.5)" }}
>
- Akyaka'nın en keyifli menüsü
+ {t.hero.subtitle}
@@ -203,7 +232,7 @@ export default function MenuClient({ initialCategories, siteSettings }: { initia
className="text-[10px] font-sans tracking-[0.3em] uppercase mt-1"
style={{ color: "rgba(255,200,130,0.35)" }}
>
- Akyaka, Muğla
+ {siteSettings.location || t.hero.location}
- © 2026 KiteBeach Akyaka. Tüm hakları saklıdır.
+ {t.footer.copyright}
;
}>) {
+ const { lang } = await params;
+
return (
-
- {children}
+
+
+ {children}
+ {/* VPS Panel Analytics */}
+
+
);
}
diff --git a/app/page.tsx b/app/[lang]/page.tsx
similarity index 67%
rename from app/page.tsx
rename to app/[lang]/page.tsx
index 906f114..71c5732 100644
--- a/app/page.tsx
+++ b/app/[lang]/page.tsx
@@ -1,8 +1,8 @@
import prisma from "@/lib/prisma";
-import MenuClient from "./MenuClient";
+import MenuClient from "../MenuClient";
import { MenuCategory } from "@/data/menu";
-async function getMenuData(): Promise {
+async function getMenuData(lang: string): Promise {
const dbCategories = await prisma.categories.findMany({
where: { status: 1 },
orderBy: { order_num: "asc" },
@@ -17,12 +17,12 @@ async function getMenuData(): Promise {
const categoryProducts = dbProducts.filter((p) => p.category_id === cat.id);
return {
id: cat.slug,
- title: cat.name_tr,
+ title: lang === "tr" ? cat.name_tr : cat.name,
items: categoryProducts.map((p) => ({
id: p.id.toString(),
- name: p.name_tr,
- description: p.description_tr,
- price: p.price.toString() + " ₺",
+ name: lang === "tr" ? p.name_tr : p.name,
+ description: (lang === "tr" ? p.description_tr : p.description) || "",
+ price: p.price.toString() + (lang === "tr" ? " ₺" : " ₺"), // Or maybe dynamic currency formatting
image: p.image_url || '/default-product.png',
})),
};
@@ -43,11 +43,17 @@ async function getSiteSettings() {
}
}
-export default async function Page() {
+export default async function Page({
+ params,
+}: {
+ params: Promise<{ lang: string }>;
+}) {
+ const { lang } = await params;
+
const [menuData, siteSettings] = await Promise.all([
- getMenuData(),
+ getMenuData(lang),
getSiteSettings(),
]);
- return ;
+ return ;
}
diff --git a/app/favicon.ico b/app/favicon.ico
index 380789bc43bc8636cc2755f539b212b58716e388..fcc18274c1cf3a142ff57cceacd8ca98e26826b7 100644
GIT binary patch
literal 15406
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
L{(s(IbPfD3b3h#I
literal 15406
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 [!IMPORTANT]
+> **User Review Required**
+> Lütfen aşağıdaki planı inceleyin ve onay verin veya eklenmesini istediğiniz başka bir dil/detay varsa belirtin.
+
+## Açık Sorular
+> [!WARNING]
+> - Varsayılan (default) dili **Türkçe** (`tr`) olarak belirliyorum, uygun mudur?
+> - Dil seçici butonunu ekranın neresinde göstermek istersiniz? (Öneri: Menü navigasyonunun hemen üzerinde veya sayfanın en üstünde sağ köşede)
+
+---
+
+## Önerilen Değişiklikler
+
+### Routing ve Middleware
+Ana sayfayı ve layout'u çoklu dil yapısına uygun hale getireceğiz.
+#### [NEW] `middleware.ts`
+Uygulamaya gelen istekleri kontrol edip, eğer URL'de `/tr` veya `/en` yoksa kullanıcının tarayıcı tercihine veya varsayılan dile (`tr`) yönlendirecek.
+#### [MODIFY] `app/layout.tsx` -> `app/[lang]/layout.tsx`
+Layout, artık url'den gelen `params.lang` değerini alacak ve `` özelliğini dinamik olarak ayarlayacak.
+#### [MODIFY] `app/page.tsx` -> `app/[lang]/page.tsx`
+Sayfa, `lang` parametresine göre çalışacak ve veritabanından verileri çekerken bu dile dikkat edecek.
+
+---
+
+### Veritabanı ve Veri Çekme (Data Fetching)
+Mevcut şemada zaten İngilizce ve Türkçe alanlar bulunuyor.
+#### [MODIFY] `app/[lang]/page.tsx` (İçerik)
+`getMenuData` fonksiyonu güncellenecek:
+- Eğer dil `en` ise: `title: cat.name`, `name: p.name`, `description: p.description`
+- Eğer dil `tr` ise: `title: cat.name_tr`, `name: p.name_tr`, `description: p.description_tr`
+
+---
+
+### Sabit Metinler (Dictionaries)
+Arayüzde veritabanından gelmeyen sabit yazılar için bir sözlük mekanizması kuracağız.
+#### [NEW] `lib/dictionaries.ts`
+İçerisinde "Akyaka'nın en keyifli menüsü", "Tüm hakları saklıdır" gibi metinlerin hem Türkçe hem İngilizce karşılıklarını tutan basit bir sistem oluşturulacak.
+
+---
+
+### Arayüz (UI)
+#### [MODIFY] `app/MenuClient.tsx`
+- Sabit metinler (Header yazıları, Footer yazıları) `dictionaries` objesinden beslenecek şekilde `props` olarak alınacak.
+- Sayfaya (Örneğin sağ üst köşeye veya menü başlıklarının üstüne) `TR / EN` dilleri arasında geçiş yapmayı sağlayacak bir buton eklenecek.
+
+---
+
+## Doğrulama Planı (Verification Plan)
+1. **Middleware Testi**: `http://localhost:3000/` adresine girildiğinde `http://localhost:3000/tr` adresine otomatik yönlendiriliyor mu?
+2. **İngilizce Testi**: `http://localhost:3000/en` adresine girildiğinde ürünlerin İngilizce isimleri (`name`) ve açıklamaları (`description`) geliyor mu? Sabit metinler (Copyright vb.) İngilizceye dönüyor mu?
+3. **Dil Değiştirici Testi**: UI üzerindeki butonla diller arası geçiş yapıldığında sayfa sorunsuz bir şekilde güncelleniyor mu?
diff --git a/lib/dictionaries.ts b/lib/dictionaries.ts
new file mode 100644
index 0000000..849a990
--- /dev/null
+++ b/lib/dictionaries.ts
@@ -0,0 +1,26 @@
+export const dictionaries = {
+ tr: {
+ hero: {
+ location: "Akyaka · Muğla",
+ subtitle: "Akyaka'nın en keyifli menüsü",
+ },
+ footer: {
+ copyright: "© 2026 KiteBeach Akyaka. Tüm hakları saklıdır.",
+ createdBy: "Created by ayris.tech",
+ },
+ },
+ en: {
+ hero: {
+ location: "Akyaka · Mugla",
+ subtitle: "The most delightful menu in Akyaka",
+ },
+ footer: {
+ copyright: "© 2026 KiteBeach Akyaka. All rights reserved.",
+ createdBy: "Created by ayris.tech",
+ },
+ },
+};
+
+export const getDictionary = (lang: string) => {
+ return dictionaries[lang as keyof typeof dictionaries] || dictionaries['tr'];
+};
diff --git a/proxy.ts b/proxy.ts
index bfde159..891bd5c 100644
--- a/proxy.ts
+++ b/proxy.ts
@@ -2,37 +2,65 @@ import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { updateSession, decrypt } from './lib/auth'
+const locales = ['en', 'tr'];
+const defaultLocale = 'tr';
+
export async function proxy(request: NextRequest) {
- // Always update session expiration
- let response = await updateSession(request)
+ const { pathname } = request.nextUrl;
- const isAuthPage = request.nextUrl.pathname.startsWith('/admin/login')
- const isAdminPage = request.nextUrl.pathname.startsWith('/admin')
+ // --- Admin Auth Logic ---
+ const isAdminPath = pathname.startsWith('/admin');
+ if (isAdminPath) {
+ // Always update session expiration for admin
+ let response = await updateSession(request)
- // Check if session exists and is valid
- const sessionValue = request.cookies.get('session')?.value
- let session = null
- if (sessionValue) {
- try {
- session = await decrypt(sessionValue)
- } catch(e) {
- session = null
+ const isAuthPage = pathname.startsWith('/admin/login')
+
+ // Check if session exists and is valid
+ const sessionValue = request.cookies.get('session')?.value
+ let session = null
+ if (sessionValue) {
+ try {
+ session = await decrypt(sessionValue)
+ } catch(e) {
+ session = null
+ }
+ }
+
+ // If trying to access admin pages (except login) without a valid session
+ if (!isAuthPage && !session) {
+ return NextResponse.redirect(new URL('/admin/login', request.url))
+ }
+
+ // If trying to access login page with a valid session
+ if (isAuthPage && session) {
+ return NextResponse.redirect(new URL('/admin', request.url))
+ }
+
+ return response || NextResponse.next()
+ }
+
+ // --- i18n Logic ---
+ const isPublicFile = pathname.includes('.') || pathname.startsWith('/_next') || pathname.startsWith('/api');
+
+ if (!isPublicFile) {
+ const pathnameHasLocale = locales.some(
+ (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
+ );
+
+ if (!pathnameHasLocale) {
+ // Redirect if there is no locale
+ request.nextUrl.pathname = `/${defaultLocale}${pathname}`;
+ return NextResponse.redirect(request.nextUrl);
}
}
- // If trying to access admin pages (except login) without a valid session
- if (isAdminPage && !isAuthPage && !session) {
- return NextResponse.redirect(new URL('/admin/login', request.url))
- }
-
- // If trying to access login page with a valid session
- if (isAuthPage && session) {
- return NextResponse.redirect(new URL('/admin', request.url))
- }
-
- return response || NextResponse.next()
+ return NextResponse.next();
}
export const config = {
- matcher: ['/admin/:path*'],
+ matcher: [
+ // Apply proxy to all paths except public static assets
+ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|avif)$).*)',
+ ],
}