Replace entire repo content with updated code from /root/shahikitchen-website/

This commit is contained in:
root
2026-06-30 12:19:55 +00:00
parent 50e3a34895
commit c8b7dc95e4
26 changed files with 1153 additions and 161 deletions
+5
View File
@@ -55,6 +55,11 @@
scroll-margin-top: var(--header-height);
}
/* Prevent horizontal scroll from overflowing children */
html {
overflow-x: clip;
}
/* Base */
body,
.shahi-body {
+1 -1
View File
@@ -113,7 +113,7 @@ export default function RootLayout({
{children}
<CartDrawer />
<WishlistDrawer />
<Toaster position="top-center" richColors closeButton />
<Toaster position="top-center" richColors closeButton offset={124} />
</WishlistProvider>
</CartProvider>
</LanguageProvider>
+2 -2
View File
@@ -55,7 +55,7 @@ export default function LocationsPage() {
{/* Branch 1: Askim */}
<div className="bg-white border border-[#EDE6D9] rounded-2xl p-8">
<div className="uppercase text-[#B38B4D] text-xs tracking-[2px] mb-2">RESTAURANT &amp; BUFFET</div>
<h2 className="text-4xl tracking-[-1.5px] mb-4">Shahi Kitchen Askim (Sisjön)</h2>
<h2 className="text-2xl sm:text-3xl md:text-4xl tracking-[-1.5px] mb-4 break-words">Shahi Kitchen Askim (Sisjön)</h2>
<div className="space-y-5 text-[15px]">
<div>
@@ -95,7 +95,7 @@ export default function LocationsPage() {
{/* Branch 2: Backaplan */}
<div className="bg-white border border-[#EDE6D9] rounded-2xl p-8">
<div className="uppercase text-[#B38B4D] text-xs tracking-[2px] mb-2">SWEETS, SNACKS &amp; CAFÉ</div>
<h2 className="text-4xl tracking-[-1.5px] mb-4">Shahi Sweets Backaplan</h2>
<h2 className="text-2xl sm:text-3xl md:text-4xl tracking-[-1.5px] mb-4 break-words">Shahi Sweets Backaplan</h2>
<div className="space-y-5 text-[15px]">
<div>
+7 -7
View File
@@ -137,7 +137,7 @@ export default function LoginPage() {
{t.auth.usernameLabel}
</label>
<div className="relative">
<User className="absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<User className="absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="username"
name="username"
@@ -146,7 +146,7 @@ export default function LoginPage() {
onChange={handleChange}
placeholder={t.auth.usernamePlaceholder}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -157,7 +157,7 @@ export default function LoginPage() {
{t.auth.passwordLabel}
</label>
<div className="relative">
<Lock className="absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Lock className="absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="password"
name="password"
@@ -166,7 +166,7 @@ export default function LoginPage() {
onChange={handleChange}
placeholder={t.auth.passwordPlaceholder}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -177,21 +177,21 @@ export default function LoginPage() {
{t.auth.siteLabel}
</label>
<div className="relative">
<MapPin className="pointer-events-none absolute left-4 top-4 z-10 h-5 w-5 text-[#B38B4D]" />
<MapPin className="pointer-events-none absolute start-4 top-4 z-10 h-5 w-5 text-[#B38B4D]" />
<select
id="site"
name="site"
value={formData.site}
onChange={handleChange}
required
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
>
<option value="">{t.auth.sitePlaceholder}</option>
<option value="askim">{t.auth.askim}</option>
<option value="backaplan">{t.auth.backaplan}</option>
</select>
{/* Custom dropdown arrow for polish */}
<div className="pointer-events-none absolute right-4 top-4 text-[#B38B4D]"></div>
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]"></div>
</div>
<p className="mt-1.5 text-[11px] text-[#8A8478]">Select the location you are working at today.</p>
</div>
+33 -18
View File
@@ -18,6 +18,11 @@ import { useCart } from "@/components/CartContext";
import WishlistButton from "@/components/WishlistButton";
import { useLanguage } from "@/lib/language-context";
import { getTranslation } from "@/lib/translations";
import {
getCategoryName,
getMenuItemDescription,
getMenuItemName,
} from "@/application/i18n/menu-localization";
import {
dishImageUrl,
getMenuPosterSrc,
@@ -45,7 +50,10 @@ export default function MenuPage() {
// Sidebar categories
const sidebarCategories = [
{ id: "All", name: t.menu.allDishes || "All Dishes" },
...menuCategories.map((cat) => ({ id: cat.id, name: cat.name })),
...menuCategories.map((cat) => ({
id: cat.id,
name: getCategoryName(language, cat.id, cat.name),
})),
];
// Filtering logic
@@ -62,11 +70,15 @@ export default function MenuPage() {
// Search
if (searchQuery.trim()) {
const q = searchQuery.toLowerCase().trim();
items = items.filter(
(item) =>
item.name.toLowerCase().includes(q) ||
(item.description && item.description.toLowerCase().includes(q))
);
items = items.filter((item) => {
const localizedName = getMenuItemName(language, item).toLowerCase();
const localizedDescription = getMenuItemDescription(language, t, item).toLowerCase();
return (
localizedName.includes(q) ||
localizedDescription.includes(q) ||
item.name.toLowerCase().includes(q)
);
});
}
// Vegetarian
@@ -77,7 +89,7 @@ export default function MenuPage() {
return { ...category, items };
})
.filter((category) => category.items.length > 0);
}, [searchQuery, showVegetarianOnly, activeCategory]);
}, [searchQuery, showVegetarianOnly, activeCategory, language, t]);
const handleCategorySelect = (id: string) => {
setActiveCategory(id);
@@ -85,9 +97,9 @@ export default function MenuPage() {
};
const getItemDescription = (item: MenuItem) =>
(t as { menuDescriptions?: Record<string, string> }).menuDescriptions?.[item.id] ||
item.description ||
"";
getMenuItemDescription(language, t, item);
const getItemName = (item: MenuItem) => getMenuItemName(language, item);
const openDishDetails = (item: MenuItem) => {
setDetailItem(item);
@@ -173,7 +185,7 @@ export default function MenuPage() {
<div className="sticky top-[116px]">
<div className="mb-6">
<div className="text-xs tracking-[3px] text-[#c99a2e] mb-2">EXPLORE OUR</div>
<h3 className="font-serif text-3xl tracking-tight">Signature Categories</h3>
<h3 className="font-serif text-2xl sm:text-3xl tracking-tight">Signature Categories</h3>
</div>
{/* Smooth scroll: horizontal on mobile, vertical on desktop when list exceeds viewport */}
@@ -265,7 +277,9 @@ export default function MenuPage() {
filteredCategories.map((category) => (
<div key={category.id} className="mb-16">
<div className="flex items-center gap-4 mb-7">
<div className="text-3xl tracking-[-1px] text-[#101724]">{category.name}</div>
<div className="text-xl sm:text-2xl md:text-3xl tracking-[-1px] text-[#101724] min-w-0 break-words">
{getCategoryName(language, category.id, category.name)}
</div>
<div className="flex-1 h-px bg-gradient-to-r from-[#e5e1d7] to-transparent" />
<div className="text-sm font-medium text-[#68717f] tracking-widest">
{category.items.length} {t.menu.dishes}
@@ -314,7 +328,7 @@ export default function MenuPage() {
>
<motion.img
src={getCardImageSrc(item, category.id)}
alt={item.name}
alt={getItemName(item)}
className="absolute inset-0 w-full h-full object-cover"
loading="lazy"
whileHover={!isMobile ? {
@@ -337,18 +351,18 @@ export default function MenuPage() {
)}
<div className="absolute top-3 right-3 z-10">
<WishlistButton
item={{ id: item.id, name: item.name, price: item.price, image: item.image }}
item={{ id: item.id, name: getItemName(item), price: item.price, image: item.image }}
size="sm"
/>
</div>
</div>
<div className="p-6 flex flex-col flex-1">
<div className="flex justify-between items-start gap-4 mb-4">
<h3 className="text-[22px] leading-tight tracking-[-0.4px] text-[#2C2A26]">
{item.name}
<div className="flex justify-between items-start gap-3 mb-4 min-w-0">
<h3 className="min-w-0 flex-1 text-lg sm:text-[22px] leading-tight tracking-[-0.4px] text-[#2C2A26] break-words">
{getItemName(item)}
</h3>
{renderMenuPrice(item)}
<div className="shrink-0">{renderMenuPrice(item)}</div>
</div>
{usesDetailModal ? (
@@ -404,6 +418,7 @@ export default function MenuPage() {
{detailItem && (
<DishDetailsModal
item={detailItem}
displayName={getItemName(detailItem)}
description={getItemDescription(detailItem)}
isOpen={!!detailItem}
onClose={() => setDetailItem(null)}
+68 -26
View File
@@ -15,7 +15,7 @@
import React, { useMemo, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { motion, AnimatePresence } from 'framer-motion';
import { filterMenuItems } from '@/application/menu/filter-menu';
import { getMenuPosterSrc, logoUrl, SITE_ASSETS } from '@/lib/assets';
import { buildTableOrderPayload } from '@/application/table-order/table-order-use-cases';
import { parseTableId } from '@/domain/table/table-id';
@@ -34,6 +34,11 @@ import type { MenuItem } from '@/domain/menu/entities';
import { playAddSound, playSuccessSound } from '@/infrastructure/audio/web-audio-sound-adapter';
import { useLanguage } from '@/presentation/providers/language-provider';
import { getTranslation } from '@/presentation/i18n/translations';
import {
getMenuItemDescription,
getMenuItemName,
localizeOrderLineName,
} from '@/application/i18n/menu-localization';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
@@ -66,14 +71,20 @@ export default function OrderFromTablePage() {
const tableDisplay = isValid ? parsed.tableNumber : '';
// Filtered dishes for the selector grid (same data source as everywhere else)
const filteredDishes = useMemo(
() =>
filterMenuItems(allMenuItems, {
searchQuery: search,
vegetarianOnly: vegOnly,
}),
[allMenuItems, search, vegOnly]
);
const filteredDishes = useMemo(() => {
const query = search.toLowerCase().trim();
return allMenuItems.filter((item) => {
if (vegOnly && !item.isVegetarian) return false;
if (!query) return true;
const localizedName = getMenuItemName(language, item).toLowerCase();
const localizedDescription = getMenuItemDescription(language, t, item).toLowerCase();
return (
localizedName.includes(query) ||
localizedDescription.includes(query) ||
item.name.toLowerCase().includes(query)
);
});
}, [allMenuItems, search, vegOnly, language, t]);
const selectedTotal = calculateOrderTotal(selected);
@@ -231,7 +242,7 @@ export default function OrderFromTablePage() {
</div>
</div>
<div className="max-w-6xl mx-auto px-6 pt-8 pb-24">
<div className={`max-w-6xl mx-auto px-6 pt-8 ${selected.length > 0 ? 'pb-36 lg:pb-24' : 'pb-24'}`}>
{/* Intro */}
<div className="mb-8">
<div className="text-[#B38B4D] text-xs tracking-[3.5px] mb-2 font-medium">{to.tableLabel.toUpperCase()} {tableDisplay}</div>
@@ -240,18 +251,18 @@ export default function OrderFromTablePage() {
</div>
<div className="grid lg:grid-cols-12 gap-8">
{/* DISH SELECTOR */}
<div className="lg:col-span-7">
{/* DISH SELECTOR — below summary on mobile for faster checkout */}
<div className="order-2 lg:order-1 lg:col-span-7">
{/* Filters */}
<div className="mb-5 flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
<div className="relative flex-1">
<Search className="pointer-events-none absolute left-4 top-3.5 h-4 w-4 text-[#B38B4D]" />
<Search className="pointer-events-none absolute start-4 top-3.5 h-4 w-4 text-[#B38B4D]" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={to.searchPlaceholder}
className="w-full rounded-2xl border border-[#EDE6D9] bg-white py-3 pl-11 pr-4 text-sm placeholder:text-[#8A8478] focus:border-[#B38B4D] focus:ring-1 focus:ring-[#B38B4D]/20 outline-none"
className="w-full rounded-2xl border border-[#EDE6D9] bg-white py-3 ps-11 pe-4 text-sm placeholder:text-[#8A8478] focus:border-[#B38B4D] focus:ring-1 focus:ring-[#B38B4D]/20 outline-none"
/>
</div>
<button
@@ -273,6 +284,8 @@ export default function OrderFromTablePage() {
filteredDishes.map((dish) => {
const inOrder = selected.some((s) => s.id === dish.id);
const poster = getMenuPosterSrc(dish, 'optimized');
const dishName = getMenuItemName(language, dish);
const dishDescription = getMenuItemDescription(language, t, dish);
return (
<motion.button
key={dish.id}
@@ -283,7 +296,7 @@ export default function OrderFromTablePage() {
<div className="relative h-20 w-20 flex-shrink-0 overflow-hidden rounded-xl bg-[#F2EDE4]">
<img
src={poster}
alt={dish.name}
alt={dishName}
className="absolute inset-0 h-full w-full object-cover group-active:scale-105 transition"
onError={(e) => {
const target = e.currentTarget as HTMLImageElement;
@@ -296,7 +309,7 @@ export default function OrderFromTablePage() {
<div className="flex min-w-0 flex-1 flex-col py-0.5">
<div className="flex items-start justify-between gap-2">
<div className="font-medium tracking-[-0.2px] text-[15px] leading-tight line-clamp-2 pr-1">
{dish.name}
{dishName}
</div>
<div className="shrink-0 text-right font-semibold text-[#B38B4D] tabular-nums text-sm leading-tight">
{dish.pricing === 'weight' ? (
@@ -313,9 +326,9 @@ export default function OrderFromTablePage() {
</div>
</div>
{dish.description && (
{dishDescription && (
<div className="mt-1 text-xs text-[#6B665F] line-clamp-2">
{dish.description}
{dishDescription}
</div>
)}
@@ -338,9 +351,9 @@ export default function OrderFromTablePage() {
</div>
</div>
{/* ORDER SUMMARY (sticky on desktop) */}
<div className="lg:col-span-5">
<div className="lg:sticky lg:top-24">
{/* ORDER SUMMARY (first on mobile, sticky on desktop) */}
<div className="order-1 lg:order-2 lg:col-span-5">
<div className="lg:sticky lg:top-[calc(var(--header-height)+5.5rem)]">
<div className="rounded-3xl border border-[#EDE6D9] bg-white p-6 shadow-sm">
<div className="flex items-center justify-between mb-4">
<div className="font-semibold tracking-tight text-lg">{to.yourOrder}</div>
@@ -364,7 +377,7 @@ export default function OrderFromTablePage() {
{selected.map((item) => (
<div key={item.id} className="flex items-center gap-3 rounded-2xl bg-[#F8F5F0] px-4 py-3">
<div className="min-w-0 flex-1">
<div className="font-medium text-[15px] tracking-[-0.2px]">{item.name}</div>
<div className="font-medium text-[15px] tracking-[-0.2px]">{localizeOrderLineName(language, item)}</div>
<div className="text-xs text-[#6B665F] tabular-nums">
{item.pricingMode === 'weight'
? `${item.pricePerHalfKg ?? item.price} kr / ½ kg · ${formatLineQuantityLabel(item)}`
@@ -376,7 +389,7 @@ export default function OrderFromTablePage() {
<div className="flex items-center gap-1.5">
<button
onClick={() => updateQty(item.id, item.quantity - 1)}
className="h-8 w-8 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95"
className="h-11 w-11 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95 touch-manipulation"
aria-label="Decrease"
>
@@ -386,7 +399,7 @@ export default function OrderFromTablePage() {
</span>
<button
onClick={() => updateQty(item.id, item.quantity + 1)}
className="h-8 w-8 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95"
className="h-11 w-11 flex items-center justify-center rounded-full border border-[#EDE6D9] bg-white active:bg-white text-lg leading-none active:scale-95 touch-manipulation"
aria-label="Increase"
>
+
@@ -418,8 +431,8 @@ export default function OrderFromTablePage() {
/>
</div>
{/* Total + Send */}
<div className="mt-5 border-t border-[#EDE6D9] pt-5">
{/* Total + Send — desktop only; mobile uses sticky bottom bar */}
<div className="hidden lg:block mt-5 border-t border-[#EDE6D9] pt-5">
<div className="flex items-baseline justify-between text-lg mb-4">
<span className="text-[#6B665F]">{to.total}</span>
<span className="font-semibold tabular-nums">{selectedTotal} kr</span>
@@ -437,6 +450,14 @@ export default function OrderFromTablePage() {
Staff will come to your table to confirm.
</p>
</div>
{/* Mobile: show total inline in summary card */}
<div className="lg:hidden mt-5 border-t border-[#EDE6D9] pt-4">
<div className="flex items-baseline justify-between text-lg">
<span className="text-[#6B665F]">{to.total}</span>
<span className="font-semibold tabular-nums">{selectedTotal} kr</span>
</div>
</div>
</div>
{/* Small context line */}
@@ -448,6 +469,27 @@ export default function OrderFromTablePage() {
</div>
</div>
{/* Mobile sticky order bar */}
{selected.length > 0 && (
<div className="lg:hidden fixed inset-x-0 bottom-0 z-50 border-t border-[#EDE6D9] bg-white/95 backdrop-blur-xl shadow-[0_-4px_24px_-4px_rgba(16,23,36,0.12)] px-4 py-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]">
<div className="mx-auto flex max-w-6xl items-center gap-4">
<div className="min-w-0 flex-1">
<div className="text-xs text-[#6B665F]">
{selected.length} {selected.length === 1 ? 'item' : 'items'}
</div>
<div className="font-semibold tabular-nums text-[#101724]">{selectedTotal} kr</div>
</div>
<button
onClick={handleSendOrder}
disabled={isSending}
className="shrink-0 rounded-2xl bg-gradient-to-r from-[#B38B4D] via-[#c99a2e] to-[#B38B4D] px-6 py-3.5 text-sm font-semibold tracking-[0.5px] text-white shadow-lg active:brightness-95 disabled:opacity-60 touch-manipulation"
>
{isSending ? 'Sending...' : to.sendOrder}
</button>
</div>
</div>
)}
<Footer />
</div>
);
+10 -5
View File
@@ -17,6 +17,10 @@ import { menuCategories } from "@/lib/menu-data";
import { buildCartLineFromMenuItem } from "@/application/cart/cart-line-builder";
import type { MenuItem } from "@/domain/menu/entities";
import { getTranslation } from "@/lib/translations";
import {
getMenuItemDescription,
getMenuItemName,
} from "@/application/i18n/menu-localization";
import SignatureMenuMarquee, { type SignatureDish } from "@/components/SignatureMenuMarquee";
type SignatureFilter = "All" | "Rice" | "Curry" | "Meat" | "Street" | "Roll" | "Sweets";
@@ -55,11 +59,12 @@ export default function ShahiKitchenHomepage() {
menuCategories.flatMap((category) =>
category.items.map((item) => ({
...item,
desc: item.description ?? "",
name: getMenuItemName(language, item),
desc: getMenuItemDescription(language, t, item),
filterTag: getDishFilterTag(item, category.id),
}))
),
[]
[language, t]
);
const filtered = useMemo(() => {
@@ -175,9 +180,9 @@ export default function ShahiKitchenHomepage() {
{ title: "Shahi Sweets", desc: "Homemade mithai made daily. From fresh Jalebi to Rasmalai — the perfect sweet ending." },
{ title: "Warm Hospitality", desc: "Whether you're here for a quick lunch or a family celebration, you will always be treated like Shahi." },
]).map((item, index) => (
<div key={index} className="experience-card group relative border border-[#EDE6D9] p-9 rounded-2xl bg-[#F8F5F0] overflow-hidden">
<div className="text-[#B38B4D] text-6xl font-light mb-9 tracking-[-2px]">0{index + 1}</div>
<h4 className="text-[29px] tracking-[-0.8px] mb-5 text-[#2C2A26] leading-tight">{item.title}</h4>
<div key={index} className="experience-card group relative border border-[#EDE6D9] p-6 sm:p-9 rounded-2xl bg-[#F8F5F0] overflow-hidden">
<div className="text-[#B38B4D] text-4xl sm:text-6xl font-light mb-6 sm:mb-9 tracking-[-2px]">0{index + 1}</div>
<h4 className="text-xl sm:text-2xl md:text-[29px] tracking-[-0.8px] mb-4 sm:mb-5 text-[#2C2A26] leading-tight break-words">{item.title}</h4>
<p className="text-[#6B665F] text-[15.5px] leading-relaxed">{item.desc}</p>
</div>
))}
+48 -33
View File
@@ -26,6 +26,11 @@ import {
import { container } from '@/infrastructure/di/container';
import { useLanguage } from '@/presentation/providers/language-provider';
import { getTranslation } from '@/presentation/i18n/translations';
import {
getMenuItemDescription,
getMenuItemName,
localizeOrderLineName,
} from '@/application/i18n/menu-localization';
import { useState } from 'react';
import { motion, AnimatePresence } from "framer-motion";
import { MapPin, Calendar, Clock, Users, User, Phone, Mail, MessageCircle, Plus, Minus, X, ArrowRight, Search, CheckCircle } from "lucide-react";
@@ -93,10 +98,16 @@ export default function ReservePage() {
// Flatten menu for selector (with search)
const allDishes = menuCategories.flatMap((cat) => cat.items);
const filteredDishes = allDishes.filter((dish) =>
dish.name.toLowerCase().includes(search.toLowerCase()) ||
(dish.description && dish.description.toLowerCase().includes(search.toLowerCase()))
);
const filteredDishes = allDishes.filter((dish) => {
const q = search.toLowerCase();
const localizedName = getMenuItemName(language, dish).toLowerCase();
const localizedDescription = getMenuItemDescription(language, t, dish).toLowerCase();
return (
localizedName.includes(q) ||
localizedDescription.includes(q) ||
dish.name.toLowerCase().includes(q)
);
});
const addToPreOrder = (dish: MenuItem) => {
setPreOrder((prev) => addOrderLine(prev, buildCartLineFromMenuItem(dish)));
@@ -173,7 +184,7 @@ export default function ReservePage() {
<div className="mb-6 inline-flex h-16 w-16 items-center justify-center rounded-2xl border border-[#c99a2e]/30 bg-white/5 p-3 backdrop-blur mx-auto">
<img src={logoUrl()} alt="Shahi Kitchen" className="h-full w-full object-contain" />
</div>
<div className="font-serif text-4xl tracking-[-1.5px] mb-1">Shahi Kitchen</div>
<div className="font-serif text-2xl sm:text-3xl md:text-4xl tracking-[-1.5px] mb-1">Shahi Kitchen</div>
<div className="text-sm font-medium tracking-[2.5px] text-[#c99a2e] mb-4">TABLE RESERVATIONS</div>
<p className="text-sm leading-relaxed text-white/70 max-w-xs mx-auto mb-4">
Two locations. Unforgettable evenings. Pre-order your favorites for a perfect arrival.
@@ -187,7 +198,7 @@ export default function ReservePage() {
<div className="max-w-4xl mx-auto space-y-8">
{/* Booking Form Card - now wider (full in max-w-4xl) */}
<div className="rounded-3xl border border-[#EDE6D9] bg-white p-6 sm:p-8 shadow-xl md:p-10">
<h2 className="text-3xl tracking-tight font-serif mb-2">{t.booking.formTitle}</h2>
<h2 className="text-2xl sm:text-3xl tracking-tight font-serif mb-2">{t.booking.formTitle}</h2>
<p className="text-[#6B665F] mb-8">Fill in your details to secure your table.</p>
<form onSubmit={handleContinue} className="space-y-6">
@@ -195,21 +206,21 @@ export default function ReservePage() {
<div>
<label htmlFor="location" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.locationLabel}</label>
<div className="relative">
<MapPin className="pointer-events-none absolute left-4 top-4 z-10 h-5 w-5 text-[#B38B4D]" />
<MapPin className="pointer-events-none absolute start-4 top-4 z-10 h-5 w-5 text-[#B38B4D]" />
<select
id="location"
name="location"
value={booking.location}
onChange={handleBookingChange}
required
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
>
<option value="">{t.booking.locationPlaceholder}</option>
{locationOptions.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
<div className="pointer-events-none absolute right-4 top-4 text-[#B38B4D]"></div>
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]"></div>
</div>
</div>
@@ -218,7 +229,7 @@ export default function ReservePage() {
<div>
<label htmlFor="date" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.dateLabel}</label>
<div className="relative">
<Calendar className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Calendar className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="date"
name="date"
@@ -227,7 +238,7 @@ export default function ReservePage() {
onChange={handleBookingChange}
min={new Date().toISOString().split('T')[0]}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -236,7 +247,7 @@ export default function ReservePage() {
<div>
<label htmlFor="time" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.timeLabel}</label>
<div className="relative">
<Clock className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Clock className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="time"
name="time"
@@ -244,7 +255,7 @@ export default function ReservePage() {
value={booking.time}
onChange={handleBookingChange}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -254,19 +265,19 @@ export default function ReservePage() {
<div>
<label htmlFor="guests" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.guestsLabel}</label>
<div className="relative">
<Users className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Users className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<select
id="guests"
name="guests"
value={booking.guests}
onChange={handleBookingChange}
required
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full cursor-pointer appearance-none rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-10 text-[17px] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
>
<option value="">Select</option>
{[1,2,3,4,5,6,7,8,9,10].map(n => <option key={n} value={n}>{n}</option>)}
</select>
<div className="pointer-events-none absolute right-4 top-4 text-[#B38B4D]"></div>
<div className="pointer-events-none absolute end-4 top-4 text-[#B38B4D]"></div>
</div>
</div>
@@ -274,7 +285,7 @@ export default function ReservePage() {
<div>
<label htmlFor="name" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.nameLabel}</label>
<div className="relative">
<User className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<User className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="name"
name="name"
@@ -283,7 +294,7 @@ export default function ReservePage() {
onChange={handleBookingChange}
placeholder={t.booking.namePlaceholder}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -292,7 +303,7 @@ export default function ReservePage() {
<div>
<label htmlFor="phone" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.phoneLabel}</label>
<div className="relative">
<Phone className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Phone className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="phone"
name="phone"
@@ -301,7 +312,7 @@ export default function ReservePage() {
onChange={handleBookingChange}
placeholder={t.booking.phonePlaceholder}
required
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -310,7 +321,7 @@ export default function ReservePage() {
<div>
<label htmlFor="email" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.emailLabel}</label>
<div className="relative">
<Mail className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Mail className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
id="email"
name="email"
@@ -318,7 +329,7 @@ export default function ReservePage() {
value={booking.email}
onChange={handleBookingChange}
placeholder={t.booking.emailPlaceholder}
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
</div>
@@ -327,7 +338,7 @@ export default function ReservePage() {
<div>
<label htmlFor="notes" className="mb-2 block text-sm font-semibold tracking-wide text-[#6B665F]">{t.booking.notesLabel}</label>
<div className="relative">
<MessageCircle className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<MessageCircle className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<textarea
id="notes"
name="notes"
@@ -335,7 +346,7 @@ export default function ReservePage() {
onChange={handleBookingChange}
placeholder={t.booking.notesPlaceholder}
rows={3}
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20 resize-y"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20 resize-y"
/>
</div>
</div>
@@ -360,18 +371,18 @@ export default function ReservePage() {
exit={{ opacity: 0 }}
className="rounded-3xl border border-[#EDE6D9] bg-white p-6 sm:p-8 shadow-xl md:p-10"
>
<h2 className="text-3xl tracking-tight font-serif mb-2">{t.booking.menuTitle}</h2>
<h2 className="text-2xl sm:text-3xl tracking-tight font-serif mb-2">{t.booking.menuTitle}</h2>
<p className="text-[#6B665F] mb-6">{t.booking.menuSubtitle}</p>
{/* Search */}
<div className="relative mb-6">
<Search className="pointer-events-none absolute left-4 top-4 h-5 w-5 text-[#B38B4D]" />
<Search className="pointer-events-none absolute start-4 top-4 h-5 w-5 text-[#B38B4D]" />
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={t.booking.searchPlaceholder}
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 pl-12 pr-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
className="w-full rounded-2xl border border-[#EDE6D9] bg-[#F8F5F0] py-4 ps-12 pe-4 text-[17px] placeholder:text-[#8A8478] transition-all outline-none focus:border-[#B38B4D] focus:bg-white focus:ring-1 focus:ring-[#B38B4D]/20"
/>
</div>
@@ -380,7 +391,11 @@ export default function ReservePage() {
{filteredDishes.length > 0 ? (
filteredDishes.map((dish) => {
const posterSrc = getMenuPosterSrc(dish);
const shortDesc = dish.description ? (dish.description.length > 80 ? dish.description.slice(0, 80) + '...' : dish.description) : '';
const dishName = getMenuItemName(language, dish);
const dishDescription = getMenuItemDescription(language, t, dish);
const shortDesc = dishDescription
? (dishDescription.length > 80 ? dishDescription.slice(0, 80) + '...' : dishDescription)
: '';
const isAdded = preOrder.some((p) => p.id === dish.id);
return (
<motion.div
@@ -390,13 +405,13 @@ export default function ReservePage() {
onMouseEnter={() => playHoverSound()}
>
{/* Item Name */}
<div className="font-semibold tracking-[-0.3px] text-base mb-1 text-[#2C2A26] line-clamp-1">{dish.name}</div>
<div className="font-semibold tracking-[-0.3px] text-base mb-1 text-[#2C2A26] line-clamp-1">{dishName}</div>
{/* Poster under name */}
<div className="relative w-full h-24 sm:h-28 md:h-32 rounded-xl overflow-hidden border border-[#EDE6D9] mb-2 bg-[#F8F5F0]">
<img
src={posterSrc}
alt={dish.name}
alt={dishName}
className="absolute inset-0 w-full h-full object-cover"
loading="lazy"
onError={(e) => {
@@ -464,11 +479,11 @@ export default function ReservePage() {
{/* Small poster thumbnail in summary for better visualization */}
<img
src={thumb}
alt={item.name}
alt={localizeOrderLineName(language, item)}
className="w-10 h-10 object-cover rounded-lg border border-[#EDE6D9] flex-shrink-0"
/>
<div className="flex-1 min-w-0">
<div className="font-medium text-sm truncate">{item.name}</div>
<div className="font-medium text-sm truncate">{localizeOrderLineName(language, item)}</div>
<div className="text-[10px] text-[#6B665F]">
{item.pricingMode === 'weight'
? `${item.pricePerHalfKg ?? item.price} kr / ½ kg · ${formatLineQuantityLabel(item)}`
@@ -520,7 +535,7 @@ export default function ReservePage() {
<div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-[#B38B4D]/10">
<CheckCircle className="h-10 w-10 text-[#B38B4D]" />
</div>
<h2 className="text-4xl tracking-tight font-serif mb-4">{t.booking.successTitle}</h2>
<h2 className="text-2xl sm:text-3xl md:text-4xl tracking-tight font-serif mb-4 break-words">{t.booking.successTitle}</h2>
<p className="text-[#6B665F] mb-8 leading-relaxed">{t.booking.successMessage}</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
+58
View File
@@ -0,0 +1,58 @@
import type { Language } from '@/domain/language/entities';
import type { MenuItem } from '@/domain/menu/entities';
import type { OrderLine } from '@/domain/shared/order-line';
import { getMenuItemById } from '@/infrastructure/menu/static-menu-data';
import { getCategoryName as resolveCategoryName } from '@/presentation/i18n/menu/category-names';
import { getMenuItemName as resolveItemName } from '@/presentation/i18n/menu/item-names';
import type { TranslationBundle } from '@/presentation/i18n/translations';
type MenuTextBundle = TranslationBundle & {
menuDescriptions?: Record<string, string>;
};
export function getMenuItemName(
lang: Language,
item: Pick<MenuItem, 'id' | 'name'>,
): string {
return resolveItemName(lang, item.id, item.name);
}
export function getMenuItemDescription(
lang: Language,
t: TranslationBundle,
item: Pick<MenuItem, 'id' | 'description'>,
): string {
const bundle = t as MenuTextBundle;
return (
bundle.menuDescriptions?.[item.id] ??
(lang === 'en' ? item.description : undefined) ??
item.description ??
''
);
}
export function getCategoryName(
lang: Language,
categoryId: string,
fallback: string,
): string {
return resolveCategoryName(lang, categoryId, fallback);
}
export function localizeMenuItem(
lang: Language,
t: TranslationBundle,
item: MenuItem,
): MenuItem {
return {
...item,
name: getMenuItemName(lang, item),
description: getMenuItemDescription(lang, t, item),
};
}
export function localizeOrderLineName(lang: Language, line: OrderLine): string {
const menuItem = getMenuItemById(line.id);
if (!menuItem) return line.name;
return getMenuItemName(lang, menuItem);
}
@@ -2,6 +2,7 @@ import type { DeliveryInquiryDetails, PickupInquiryDetails } from '@/domain/cart
import type { BookingDetails } from '@/domain/booking/entities';
import { isBookingComplete } from '@/domain/booking/validation';
import type { Language } from '@/domain/language/entities';
import { localizeOrderLineName } from '@/application/i18n/menu-localization';
import {
calculateOrderTotal,
formatOrderLinesForInquiry,
@@ -46,14 +47,21 @@ export function getSuggestedPickupTime(language: Language, from: Date = new Date
return suggested.toLocaleTimeString(timeLocale, { hour: '2-digit', minute: '2-digit' });
}
function localizeInquiryLines(lang: Language, items: OrderLine[]): string {
return formatOrderLinesForInquiry(
items.map((item) => ({ ...item, name: localizeOrderLineName(lang, item) })),
);
}
export function buildPickupInquiryMessage(
items: OrderLine[],
details: PickupInquiryDetails,
copy: CartInquiryCopy,
language: Language = 'en',
): string | null {
if (items.length === 0) return null;
const lines = formatOrderLinesForInquiry(items);
const lines = localizeInquiryLines(language, items);
const total = calculateOrderTotal(items);
return `${copy.pickupIntro}
@@ -67,10 +75,11 @@ export function buildDeliveryInquiryMessage(
items: OrderLine[],
details: DeliveryInquiryDetails,
copy: CartInquiryCopy,
language: Language = 'en',
): string | null {
if (items.length === 0) return null;
const lines = formatOrderLinesForInquiry(items);
const lines = localizeInquiryLines(language, items);
const total = calculateOrderTotal(items);
return `${copy.deliveryIntro}
@@ -83,6 +92,15 @@ ${copy.messagePreferredTime}: ${details.preferredTime.trim()}
${copy.deliverySwishNote}`;
}
const PACKAGE_NAME_PLACEHOLDER = '{{package}}';
export function buildCateringPackagePricingMessage(
packageName: string,
template: string,
): string {
return template.split(PACKAGE_NAME_PLACEHOLDER).join(packageName);
}
export function buildBookingWhatsAppMessage(
booking: BookingDetails,
preOrder: OrderLine[],
+19 -8
View File
@@ -18,6 +18,8 @@ import { container } from '@/infrastructure/di/container';
import { useCart } from '@/presentation/providers/cart-provider';
import { useLanguage } from '@/presentation/providers/language-provider';
import { getTranslation } from '@/presentation/i18n/translations';
import { getMenuItemName } from '@/application/i18n/menu-localization';
import { getMenuItemById } from '@/lib/menu-data';
import CartInquiryModal from '@/components/CartInquiryModal';
export default function CartDrawer() {
@@ -31,7 +33,7 @@ export default function CartDrawer() {
clearCart,
} = useCart();
const { language } = useLanguage();
const { language, isRtl } = useLanguage();
const t = getTranslation(language);
const [inquiryMode, setInquiryMode] = useState<FulfillmentMode | null>(null);
@@ -53,11 +55,11 @@ export default function CartDrawer() {
};
const handlePickupSubmit = (details: PickupInquiryDetails) => {
openWhatsApp(buildPickupInquiryMessage(items, details, inquiryCopy));
openWhatsApp(buildPickupInquiryMessage(items, details, inquiryCopy, language));
};
const handleDeliverySubmit = (details: DeliveryInquiryDetails) => {
openWhatsApp(buildDeliveryInquiryMessage(items, details, inquiryCopy));
openWhatsApp(buildDeliveryInquiryMessage(items, details, inquiryCopy, language));
};
if (!isOpen) return null;
@@ -69,7 +71,11 @@ export default function CartDrawer() {
onClick={closeCart}
/>
<div className="fixed top-0 right-0 h-full w-full max-w-md bg-[#F8F5F0] z-[990] shadow-2xl flex flex-col">
<div
className={`fixed top-0 h-full w-full max-w-md bg-[#F8F5F0] z-[990] shadow-2xl flex flex-col ${
isRtl ? 'left-0 border-r border-[#EDE6D9]' : 'right-0 border-l border-[#EDE6D9]'
}`}
>
<div className="flex items-center justify-between p-6 border-b border-[#EDE6D9]">
<div className="flex items-center gap-3">
<h2 className="text-2xl tracking-[-0.5px]">{t.cartDrawer.title}</h2>
@@ -117,7 +123,12 @@ export default function CartDrawer() {
<div className="flex-1">
<div className="flex justify-between">
<div>
<h4 className="font-medium tracking-[-0.3px]">{item.name}</h4>
<h4 className="font-medium tracking-[-0.3px]">
{getMenuItemName(language, {
id: item.id,
name: getMenuItemById(item.id)?.name ?? item.name,
})}
</h4>
<p className="text-sm text-[#6B665F]">
{item.pricingMode === 'weight'
? `${item.pricePerHalfKg ?? item.price} kr / ½ kg`
@@ -132,7 +143,7 @@ export default function CartDrawer() {
<div className="flex items-center gap-3 mt-3">
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
className="w-8 h-8 flex items-center justify-center border border-[#EDE6D9] rounded hover:bg-white transition"
className="w-11 h-11 flex items-center justify-center border border-[#EDE6D9] rounded hover:bg-white transition touch-manipulation"
>
</button>
@@ -141,7 +152,7 @@ export default function CartDrawer() {
</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
className="w-8 h-8 flex items-center justify-center border border-[#EDE6D9] rounded hover:bg-white transition"
className="w-11 h-11 flex items-center justify-center border border-[#EDE6D9] rounded hover:bg-white transition touch-manipulation"
>
+
</button>
@@ -161,7 +172,7 @@ export default function CartDrawer() {
</div>
{items.length > 0 && (
<div className="p-6 border-t border-[#EDE6D9] bg-white">
<div className="p-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] border-t border-[#EDE6D9] bg-white">
<button
onClick={clearCart}
className="text-xs text-[#8A8478] hover:text-[#B38B4D] mb-3 underline"
+18 -11
View File
@@ -6,9 +6,11 @@ import {
CATERING_CATEGORY_ORDER,
groupDishesByCategory,
} from '@/lib/catering-data';
import { buildCateringPackagePricingMessage } from '@/application/messaging/whatsapp-message-builder';
import { container } from '@/infrastructure/di/container';
import { useLanguage } from '@/lib/language-context';
import { getTranslation } from '@/lib/translations';
import { Users, UtensilsCrossed } from 'lucide-react';
import { MessageCircle, Users, UtensilsCrossed } from 'lucide-react';
import { motion } from 'framer-motion';
interface CateringPackageCardProps {
@@ -26,6 +28,13 @@ export default function CateringPackageCard({ pkg, index = 0 }: CateringPackageC
const packageDescription =
(t.catering.packageDescriptions as Record<string, string>)?.[pkg.id] ?? pkg.description;
const handlePricingInquiry = () => {
const template = (t.catering as { packagePricingInquiry?: string }).packagePricingInquiry;
if (!template) return;
const message = buildCateringPackagePricingMessage(packageName, template);
container.messagingGateway.openWhatsApp(message);
};
return (
<motion.article
initial={{ opacity: 0, y: 28 }}
@@ -67,16 +76,14 @@ export default function CateringPackageCard({ pkg, index = 0 }: CateringPackageC
</div>
</div>
<div className="mb-5 rounded-2xl border border-[#c99a2e]/25 bg-[#fffdf8] px-4 py-3.5">
<div className="flex items-baseline gap-2">
<span className="text-2xl font-medium tabular-nums tracking-[-0.5px] text-[#B38B4D]">
{pkg.pricePerHead}
</span>
<span className="text-sm font-semibold text-[#8a6a25]">
{t.catering.perHead} · {pkg.currency}
</span>
</div>
</div>
<button
type="button"
onClick={handlePricingInquiry}
className="mb-5 flex w-full items-center justify-center gap-2 rounded-2xl border border-[#c99a2e]/35 bg-[#fffdf8] px-4 py-3.5 text-base font-semibold tracking-[-0.2px] text-[#B38B4D] transition-all hover:border-[#c99a2e] hover:bg-[#fff6dc] hover:text-[#8C6B3A] active:scale-[0.98]"
>
<MessageCircle className="h-4 w-4 shrink-0" aria-hidden />
{t.catering.contactForPricing}
</button>
<div className="mb-2 flex items-center gap-2 text-xs font-bold uppercase tracking-[0.18em] text-[#8a6a25]">
<UtensilsCrossed className="h-3.5 w-3.5 text-[#c99a2e]" />
+4 -2
View File
@@ -13,6 +13,7 @@ import {
interface DishDetailsModalProps {
item: MenuItem;
displayName: string;
description: string;
isOpen: boolean;
onClose: () => void;
@@ -24,6 +25,7 @@ interface DishDetailsModalProps {
export default function DishDetailsModal({
item,
displayName,
description,
isOpen,
onClose,
@@ -83,7 +85,7 @@ export default function DishDetailsModal({
) : imageSrc ? (
<img
src={imageSrc}
alt={item.name}
alt={displayName}
className="w-full h-[240px] sm:h-[280px] object-cover bg-[#F2EDE4]"
onError={(e) => {
applyNextImageFallback(
@@ -114,7 +116,7 @@ export default function DishDetailsModal({
id="dish-detail-title"
className="font-serif text-2xl sm:text-[26px] tracking-[-0.4px] text-[#2C2A26]"
>
{item.name}
{displayName}
</h2>
{description && (
+16 -12
View File
@@ -28,7 +28,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
const [scrolled, setScrolled] = useState(false);
const { totalItems, openCart } = useCart();
const { totalItems: wishlistCount, openWishlist } = useWishlist();
const { language } = useLanguage();
const { language, isRtl } = useLanguage();
const t = getTranslation(language);
const pathname = usePathname();
@@ -91,7 +91,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
</Link>
{/* Desktop Navigation - Premium Mesmerizing Design */}
<div className="hidden md:block flex-shrink-0">
<div className="hidden lg:block flex-shrink-0 min-w-0">
<div className="flex items-center rounded-full border border-[#c99a2e]/20 bg-white/60 px-2 py-1.5 backdrop-blur-3xl shadow-sm">
<div className="relative flex items-center gap-1 text-sm font-medium text-[#101724]">
{navLinks.map((link, index) => {
@@ -127,7 +127,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
</div>
{/* Desktop Actions — Stunning Mesmerizing Buttons */}
<div className="hidden md:flex items-center gap-3">
<div className="hidden lg:flex items-center gap-3 shrink-0">
{/* Wishlist Button */}
<button
onClick={openWishlist}
@@ -168,11 +168,11 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
</div>
{/* Mobile Hamburger + Wishlist + Cart (compact, high touch target) */}
<div className="md:hidden flex items-center gap-2">
<div className="lg:hidden flex items-center gap-1.5 shrink-0">
{/* MOBILE WISHLIST ICON */}
<button
onClick={openWishlist}
className="relative flex items-center justify-center w-10 h-10 rounded-full hover:bg-[#F5F1E9] active:bg-[#EDE6D9] transition-colors"
className="relative flex items-center justify-center w-11 h-11 rounded-full hover:bg-[#F5F1E9] active:bg-[#EDE6D9] transition-colors"
aria-label={t.wishlist}
>
<Heart className={`h-5 w-5 ${wishlistCount > 0 ? 'text-[#c99a2e] fill-[#c99a2e]/30' : 'text-[#101724]'}`} />
@@ -186,7 +186,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
{/* MOBILE CART ICON (always visible) */}
<button
onClick={openCart}
className="relative flex items-center justify-center w-10 h-10 rounded-full hover:bg-[#F5F1E9] active:bg-[#EDE6D9] transition-colors"
className="relative flex items-center justify-center w-11 h-11 rounded-full hover:bg-[#F5F1E9] active:bg-[#EDE6D9] transition-colors"
aria-label="Open cart"
>
<ShoppingBag className="h-5 w-5 text-[#101724]" />
@@ -199,7 +199,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
<button
onClick={() => setIsOpen(!isOpen)}
className="text-[#B38B4D] p-2 -mr-1 active:text-[#8C6B3A] transition-colors"
className="flex items-center justify-center w-11 h-11 text-[#B38B4D] -me-1 active:text-[#8C6B3A] transition-colors"
aria-label="Toggle menu"
aria-expanded={isOpen}
>
@@ -219,7 +219,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
{/* Modern Mobile Menu Drawer (slide-in, animated, high-contrast, touch-friendly) - high z to always appear in front of hero banner video etc. */}
<AnimatePresence>
{isOpen && (
<div className="md:hidden fixed inset-0 z-[9999]">
<div className="lg:hidden fixed inset-0 z-[9999]">
{/* Backdrop */}
<motion.div
className="absolute inset-0 bg-[#101724]/70 backdrop-blur-md"
@@ -232,10 +232,14 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
{/* Sliding Panel - modern, full-bleed on small phones, elegant on larger */}
<motion.div
className="absolute right-0 top-0 bottom-0 w-[82%] max-w-[340px] bg-[#fbf7ef] shadow-2xl border-l border-[#c99a2e]/10 flex flex-col overflow-y-auto z-[10000]"
initial={{ x: '100%' }}
className={`absolute top-0 bottom-0 w-[82%] max-w-[340px] bg-[#fbf7ef] shadow-2xl flex flex-col overflow-y-auto z-[10000] ${
isRtl
? 'left-0 border-r border-[#c99a2e]/10'
: 'right-0 border-l border-[#c99a2e]/10'
}`}
initial={{ x: isRtl ? '-100%' : '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
exit={{ x: isRtl ? '-100%' : '100%' }}
transition={{ type: 'spring', stiffness: 320, damping: 32 }}
>
{/* Header */}
@@ -294,7 +298,7 @@ export default function Navbar({ variant = "default" }: NavbarProps) {
</div>
{/* Secondary actions */}
<div className="mt-auto px-5 pb-8 pt-4 border-t border-[#EDE6D9] bg-white/40 space-y-3">
<div className="mt-auto px-5 pt-4 pb-[max(2rem,env(safe-area-inset-bottom))] border-t border-[#EDE6D9] bg-white/40 space-y-3">
<Link
href="/reserve"
onClick={closeMenu}
+2 -2
View File
@@ -151,7 +151,7 @@ export default function OrderDisplay() {
{ id: 'T-01', source: 'table', label: 'Table 1', customer: 'Table 1', items: [{name: 'Shahi Paneer', qty: 1}, {name: 'Naan', qty: 3}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*8), etaMinutes: 7 },
{ id: 'T-04', source: 'table', label: 'Table 4', customer: 'Table 4', items: [{name: 'Chicken Tikka', qty: 2}, {name: 'Rice', qty: 1}], status: 'review', receivedAt: new Date(now.getTime() - 1000*60*5), etaMinutes: 10 },
{ id: 'T-06', source: 'table', label: 'Table 6', customer: 'Table 6', items: [{name: 'Lahore Sizzler', qty: 1}, {name: 'Naan', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*3), etaMinutes: 5 },
{ id: 'T-07', source: 'table', label: 'Table 7', customer: 'Table 7', items: [{name: 'Tikka Boti', qty: 1}, {name: 'Salad', qty: 1}], status: 'received', receivedAt: new Date(now.getTime() - 1000*60*1), etaMinutes: 12 },
{ id: 'T-07', source: 'table', label: 'Table 7', customer: 'Table 7', items: [{name: 'Chicken Tikka', qty: 1}, {name: 'Salad', qty: 1}], status: 'received', receivedAt: new Date(now.getTime() - 1000*60*1), etaMinutes: 12 },
// Delivery Partners (online) - 8 orders (mix of platforms)
{ id: 'FD-3921', source: 'foodora', label: 'Foodora #FD-3921', customer: 'Aisha K.', items: [{name: 'Lahore Pizza', qty: 1}, {name: 'Mango Lassi', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*9), etaMinutes: 3 },
@@ -166,7 +166,7 @@ export default function OrderDisplay() {
// Pickup orders (whatsapp/phone) - 8 orders
{ id: 'WA-4521', source: 'pickup', label: 'Pickup WA-4521', customer: 'Ahmed R.', items: [{name: 'Chicken Biryani', qty: 2}, {name: 'Raita', qty: 1}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*13), etaMinutes: 5 },
{ id: 'PH-783', source: 'pickup', label: 'Pickup PH-783', customer: 'Sara M.', items: [{name: 'Veg Pizza', qty: 1}, {name: 'Garlic Naan', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*6), etaMinutes: 2 },
{ id: 'WA-4530', source: 'pickup', label: 'Pickup WA-4530', customer: 'Omar K.', items: [{name: 'Tikka Boti', qty: 1}], status: 'ready', receivedAt: new Date(now.getTime() - 1000*60*20), etaMinutes: 0 },
{ id: 'WA-4530', source: 'pickup', label: 'Pickup WA-4530', customer: 'Omar K.', items: [{name: 'Chicken Tikka', qty: 1}], status: 'ready', receivedAt: new Date(now.getTime() - 1000*60*20), etaMinutes: 0 },
{ id: 'WA-4535', source: 'pickup', label: 'Pickup WA-4535', customer: 'Fatima L.', items: [{name: 'Shahi Paneer', qty: 1}, {name: 'Naan', qty: 1}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*8), etaMinutes: 7 },
{ id: 'PH-791', source: 'pickup', label: 'Pickup PH-791', customer: 'Hassan Q.', items: [{name: 'Daal Makhani', qty: 1}, {name: 'Rice', qty: 2}], status: 'review', receivedAt: new Date(now.getTime() - 1000*60*11), etaMinutes: 8 },
{ id: 'WA-4542', source: 'pickup', label: 'Pickup WA-4542', customer: 'Ayesha S.', items: [{name: 'Samosa Keema', qty: 3}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*4), etaMinutes: 3 },
+27 -9
View File
@@ -12,6 +12,11 @@ import {
import { menuCategories, allMenuItems, type MenuItem, type MenuCategory } from '@/lib/menu-data';
import { useLanguage } from '@/lib/language-context';
import { getTranslation } from '@/lib/translations';
import {
getMenuItemDescription,
getMenuItemName,
} from '@/application/i18n/menu-localization';
import type { Language } from '@/domain/language/entities';
interface ScreenDisplayProps {
screen: 1 | 2;
@@ -102,9 +107,20 @@ function SpiceParticles() {
);
}
function MenuCard({ item, isHighlighted, t }: { item: MenuItem; isHighlighted?: boolean; t: any }) {
function MenuCard({
item,
isHighlighted,
language,
t,
}: {
item: MenuItem;
isHighlighted?: boolean;
language: Language;
t: ReturnType<typeof getTranslation>;
}) {
const poster = getMenuPosterSrc(item, 'optimized');
const desc = (t as any).menuDescriptions?.[item.id] || item.description || '';
const displayName = getMenuItemName(language, item);
const desc = getMenuItemDescription(language, t, item);
return (
<motion.div
@@ -119,7 +135,7 @@ function MenuCard({ item, isHighlighted, t }: { item: MenuItem; isHighlighted?:
<div className="relative h-36 bg-[#F2EDE4] overflow-hidden">
<motion.img
src={poster}
alt={item.name}
alt={displayName}
className="absolute inset-0 w-full h-full object-cover"
animate={{ scale: [1, 1.018, 1] }}
transition={{ duration: 8.5, repeat: Infinity, ease: 'easeInOut' }}
@@ -147,7 +163,7 @@ function MenuCard({ item, isHighlighted, t }: { item: MenuItem; isHighlighted?:
<div className="p-3.5 flex-1 flex flex-col">
<div className="font-medium tracking-[-0.3px] text-[15px] leading-tight text-[#2C2A26] mb-1 line-clamp-1 group-hover:text-[#B38B4D] transition-colors">
{item.name}
{displayName}
</div>
<p className="text-[#6B665F] text-[12px] leading-snug line-clamp-2 flex-1">
{desc}
@@ -257,7 +273,8 @@ export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
};
}, []);
const translatedDesc = current ? ((t as any).menuDescriptions?.[current.id] || current.description) : '';
const currentDisplayName = current ? getMenuItemName(language, current) : '';
const translatedDesc = current ? getMenuItemDescription(language, t, current) : '';
// =====================================================
// CONTINUOUS MENU REEL (one line, loop like a film reel) - items move LEFT
@@ -282,6 +299,7 @@ export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
// ReelCard: large poster + number+name on left, price on right (opposite side, same line) so price is always clearly visible as cards move left in the reel. Sized big for 43" TV distance viewing.
function ReelCard({ item, number }: { item: MenuItem; number: number }) {
const numStr = String(number).padStart(2, '0');
const reelName = getMenuItemName(language, item);
return (
<div
@@ -291,7 +309,7 @@ export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
<div className="relative bg-[#F2EDE4]" style={{ height: '320px' }}>
<motion.img
src={getMenuPosterSrc(item, 'optimized')}
alt={item.name}
alt={reelName}
className="w-full h-full object-cover"
animate={{ scale: [1, 1.015, 1] }}
transition={{ duration: 6, repeat: Infinity, ease: 'easeInOut' }}
@@ -304,7 +322,7 @@ export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
</div>
<div className="px-4 py-5 flex items-baseline justify-between gap-4">
<div className="font-serif text-3xl tracking-[-0.5px] leading-none text-[#2C2A26]">
{numStr}. {item.name}
{numStr}. {reelName}
</div>
<div className="text-[#B38B4D] text-2xl font-semibold tracking-tight whitespace-nowrap">
{item.price} kr
@@ -381,7 +399,7 @@ export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
<motion.img
key={`${current.id}-poster`}
src={getMenuPosterSrc(current, 'optimized')}
alt={current.name}
alt={currentDisplayName}
className="absolute inset-0 w-full h-full object-cover"
initial={{ opacity: 0, scale: 1 }}
animate={{ opacity: 1, scale: [1, 1.06, 1] }}
@@ -420,7 +438,7 @@ export default function ScreenDisplay({ screen }: ScreenDisplayProps) {
</div>
<h1 className="font-serif text-[56px] md:text-[64px] leading-[0.88] tracking-[-3.2px] mb-2 text-[#B38B4D] drop-shadow-[0_0_10px_rgba(179,139,77,0.85)]">
{current.name}
{currentDisplayName}
</h1>
<div className="flex items-baseline gap-2 mb-4">
+20 -7
View File
@@ -8,6 +8,7 @@ import { useLanguage } from '@/presentation/providers/language-provider';
import { getTranslation } from '@/presentation/i18n/translations';
import { getMenuPosterSrc, applyNextImageFallback, getMenuPosterCandidates } from '@/lib/assets';
import { getMenuItemById } from '@/lib/menu-data';
import { getMenuItemName } from '@/application/i18n/menu-localization';
import { buildCartLineFromMenuItem } from '@/application/cart/cart-line-builder';
export default function WishlistDrawer() {
@@ -20,7 +21,7 @@ export default function WishlistDrawer() {
} = useWishlist();
const { addToCart } = useCart();
const { language } = useLanguage();
const { language, isRtl } = useLanguage();
const t = getTranslation(language);
if (!isOpen) return null;
@@ -43,7 +44,11 @@ export default function WishlistDrawer() {
onClick={closeWishlist}
/>
<div className="fixed top-0 right-0 h-full w-full max-w-md bg-[#F8F5F0] z-[985] shadow-2xl flex flex-col">
<div
className={`fixed top-0 h-full w-full max-w-md bg-[#F8F5F0] z-[985] shadow-2xl flex flex-col ${
isRtl ? 'left-0 border-r border-[#EDE6D9]' : 'right-0 border-l border-[#EDE6D9]'
}`}
>
<div className="flex items-center justify-between p-6 border-b border-[#EDE6D9]">
<div className="flex items-center gap-3">
<Heart className="h-5 w-5 text-[#c99a2e] fill-[#c99a2e]/20" />
@@ -88,7 +93,14 @@ export default function WishlistDrawer() {
</div>
) : (
<div className="space-y-5">
{items.map((item) => (
{items.map((item) => {
const menuItem = getMenuItemById(item.id);
const displayName = getMenuItemName(language, {
id: item.id,
name: menuItem?.name ?? item.name,
});
return (
<div
key={item.id}
className="flex gap-4 rounded-2xl border border-[#EDE6D9] bg-white p-4"
@@ -96,7 +108,7 @@ export default function WishlistDrawer() {
<div className="h-20 w-20 shrink-0 overflow-hidden rounded-xl bg-[#F2EDE4]">
<img
src={getMenuPosterSrc(item)}
alt={item.name}
alt={displayName}
className="h-full w-full object-cover"
loading="lazy"
onError={(e) => {
@@ -112,7 +124,7 @@ export default function WishlistDrawer() {
<div className="flex-1 min-w-0">
<div className="flex justify-between gap-2">
<h4 className="font-medium tracking-[-0.3px] text-[#101724] truncate">
{item.name}
{displayName}
</h4>
<span className="shrink-0 text-right font-medium text-[#B38B4D] tabular-nums text-sm leading-tight">
{(() => {
@@ -147,13 +159,14 @@ export default function WishlistDrawer() {
</div>
</div>
</div>
))}
);
})}
</div>
)}
</div>
{items.length > 0 && (
<div className="p-6 border-t border-[#EDE6D9] bg-white">
<div className="p-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] border-t border-[#EDE6D9] bg-white">
<p className="text-xs text-center text-[#6B665F] mb-4">
{t.wishlistDrawer.hint}
</p>
-1
View File
@@ -130,7 +130,6 @@ export const menuCategories: MenuCategory[] = [
items: [
{ id: "chicken-biryani", name: "Chicken Biryani", description: "Fragrant aged basmati rice layered with tender spiced chicken, saffron and caramelized onions.", price: 149, image: "chicken-biryani.jpg", video: "chicken-biryani.mp4" },
{ id: "chicken-tikka", name: "Chicken Tikka", description: "Boneless chicken pieces marinated in yogurt and spices, grilled in a tandoor.", price: 149, image: "chicken-tikka.jpg", video: "chicken-tikka.mp4" },
{ id: "tikka-boti", name: "Tikka Boti", description: "Tender chicken chunks marinated in spices and grilled on skewers.", price: 149, image: "chicken-tikka.jpg", video: "tikka-boti.mp4" },
{ id: "chicken-karahi", name: "Chicken Karahi", description: "Wok-tossed chicken in a robust tomato, chili and ginger gravy.", price: 149, image: "chicken-karahi.jpg", video: "chicken-karahi.mp4" },
{ id: "lahore-sizzler", name: "Lahore Sizzler", description: "Sizzling platter of marinated chicken with vegetables and spicy sauces.", price: 169, image: "lahore-sizzler.jpg", video: "lahore-sizzler.mp4" },
{ id: "butter-chicken", name: "Butter Chicken", description: "Tender chicken in a creamy tomato and butter gravy with mild spices.", price: 149, image: "butter-chicken.jpg" },
+4 -2
View File
@@ -69,7 +69,7 @@ export const arTranslations = {
filterGrill: 'مشويات',
filterSweets: 'حلويات',
filterMeat: 'لحوم',
filterStreet: 'شارع',
filterStreet: 'طعام الشارع',
filterRoll: 'رول',
addToTable: 'أضف إلى الطاولة',
viewFullMenu: 'اعرض القائمة الكاملة — أكثر من 40 طبقاً',
@@ -109,7 +109,6 @@ export const arTranslations = {
'shami-sandwich': 'أقراص شامي كباب من لحم مفروم متبل تُقدَّم في خبز مع صلصة وبصل.',
'chicken-biryani': 'أرز بسمتي معتق عطري مع دجاج متبل طري وطبقات من الزعفران والبصل المكرمل.',
'chicken-tikka': 'قطع دجاج بدون عظم متبلة بالزبادي والبهارات، مشوية في التندور.',
'tikka-boti': 'قطع دجاج طرية متبلة بالبهارات ومشوية على أسياخ.',
'chicken-karahi': 'دجاج مقلي في الووك بمرق قوي من الطماطم والفلفل والزنجبيل.',
'lahore-sizzler': 'طبق ساخن من دجاج متبل مع خضروات وصلصات حارة.',
'butter-chicken': 'دجاج طري في مرق كريمي من الطماطم والزبدة ببهارات خفيفة.',
@@ -179,6 +178,9 @@ export const arTranslations = {
subtitle:
'باقات فاخرة لكل ضيف للأعراس والفعاليات الشركات والتجمعات العائلية — نكهات شاهي أصيلة بتقديم راقٍ.',
perHead: 'لكل ضيف',
contactForPricing: 'تواصل معنا للاستفسار عن الأسعار',
packagePricingInquiry:
'مرحباً Shahi Kitchen 👋\n\nأود معرفة أسعار باقة التموين {{package}}.\n\nيرجى التواصل معي بخصوص التوفر والخيارات.\n\nشكراً لك!',
includedDishes: 'الأطباق المشمولة',
mostPopular: 'الأكثر شعبية',
guestNote: 'قوائم مخصصة متاحة — تواصل معنا لتخصيص مناسبتك.',
+4 -2
View File
@@ -69,7 +69,7 @@ export const trTranslations = {
filterGrill: 'Izgara',
filterSweets: 'Tatlılar',
filterMeat: 'Et',
filterStreet: 'Sokak',
filterStreet: 'Sokak Yemeği',
filterRoll: 'Dürüm',
addToTable: 'Masaya Ekle',
viewFullMenu: 'TAM MENÜYÜ GÖRÜN — 40+ YEMEK',
@@ -109,7 +109,6 @@ export const trTranslations = {
'shami-sandwich': 'Sos ve soğanla ekmek arasında servis edilen baharatlı kıymalı şami kebab köfteleri.',
'chicken-biryani': 'Yumuşak baharatlı tavuk, safran ve karamelize soğan katmanlarıyla aromatik yaşlandırılmış basmati pilav.',
'chicken-tikka': 'Yoğurt ve baharatlarda marine edilmiş kemiksiz tavuk parçaları, tandırda ızgara yapılır.',
'tikka-boti': 'Baharatlarda marine edilmiş yumuşak tavuk parçaları, şişte ızgara yapılır.',
'chicken-karahi': 'Wokta kavrulmuş tavuk, domates, biber ve zencefil sosunda.',
'lahore-sizzler': 'Sebzeler ve baharatlı soslarla marine edilmiş tavuk, ıslık çalan tabakta servis edilir.',
'butter-chicken': 'Hafif baharatlı kremalı domates ve tereyağı sosunda yumuşak tavuk.',
@@ -179,6 +178,9 @@ export const trTranslations = {
subtitle:
'Düğünler, kurumsal etkinlikler ve aile toplantıları için kişi başı premium paketler — otantik Shahi lezzetleri.',
perHead: 'kişi başı',
contactForPricing: 'Fiyat için bizimle iletişime geçin',
packagePricingInquiry:
'Merhaba Shahi Kitchen 👋\n\n{{package}} catering paketi için fiyat almak istiyorum.\n\nLütfen müsaitlik ve seçenekler için benimle iletişime geçin.\n\nTeşekkürler!',
includedDishes: 'Dahil yemekler',
mostPopular: 'En Popüler',
guestNote: 'Özel menüler mevcut — etkinliğiniz için bizimle iletişime geçin.',
+76
View File
@@ -0,0 +1,76 @@
import type { Language } from '@/domain/language/entities';
const EN: Record<string, string> = {
"street-food": "Street Food & Starters",
"vegetarian": "Vegetarian",
"meat": "Meat",
"burger-sandwich": "Burger & Sandwich",
"chicken": "Chicken",
"pizza": "Pizza",
"naan-roll": "Naan Roll",
"sweets": "Sweets / Mithai",
"drinks": "Drinks"
};
const LOCALIZED: Partial<Record<Language, Record<string, string>>> = {
"sv": {
"street-food": "Gatumat & Förrätter",
"vegetarian": "Vegetariskt",
"meat": "Kött",
"burger-sandwich": "Burgare & Smörgås",
"chicken": "Kyckling",
"pizza": "Pizza",
"naan-roll": "Naanrulle",
"sweets": "Sötsaker / Mithai",
"drinks": "Drycker"
},
"ar": {
"street-food": "طعام الشارع والمقبلات",
"vegetarian": "نباتي",
"meat": "لحوم",
"burger-sandwich": "برغر وساندويتش",
"chicken": "دجاج",
"pizza": "بيتزا",
"naan-roll": "رول نان",
"sweets": "حلويات / ميثاي",
"drinks": "مشروبات"
},
"tr": {
"street-food": "Sokak Yemeği ve Başlangıçlar",
"vegetarian": "Vejetaryen",
"meat": "Et",
"burger-sandwich": "Burger ve Sandviç",
"chicken": "Tavuk",
"pizza": "Pizza",
"naan-roll": "Naan Dürüm",
"sweets": "Tatlılar / Mithai",
"drinks": "İçecekler"
},
"hi": {
"street-food": "स्ट्रीट फूड और स्टार्टर्स",
"vegetarian": "शाकाहारी",
"meat": "मांस",
"burger-sandwich": "बर्गर और सैंडविच",
"chicken": "चिकन",
"pizza": "पिज़्ज़ा",
"naan-roll": "नान रोल",
"sweets": "मिठाइयाँ / मिठाई",
"drinks": "पेय"
},
"ur": {
"street-food": "اسٹریٹ فوڈ اور سٹارٹرز",
"vegetarian": "سبزی خور",
"meat": "گوشت",
"burger-sandwich": "برگر اور سینڈوچ",
"chicken": "چکن",
"pizza": "پیزا",
"naan-roll": "نان رول",
"sweets": "مٹھائیاں / مٹھائی",
"drinks": "مشروبات"
}
};
export function getCategoryName(lang: Language, categoryId: string, fallback: string): string {
if (lang === 'en') return EN[categoryId] ?? fallback;
return LOCALIZED[lang]?.[categoryId] ?? EN[categoryId] ?? fallback;
}
@@ -0,0 +1,207 @@
import type { Language } from '@/domain/language/entities';
export const HINDI_MENU_DESCRIPTIONS: Record<string, string> = {
"samosa-aloo": "मसालेदार आलू और मटर से भरी कुरकुरी तली हुई त्रिकोणीय पेस्ट्री।",
"samosa-keema": "मसालेदार कीमा भरावन वाली परतदार पेस्ट्री।",
"samosa-chat": "मसालेदार चने, दही, चटनी और ताज़ी जड़ी-बूटियों से सजी कुरकुरी समोसा।",
"chana-chat": "आलू, प्याज, टमाटर और चटनियों के साथ मिलाए गए खट्टे मसालेदार चने।",
"panipuri": "मसालेदार चने और आलू से भरे कुरकुरे खोखले पूरी, इमली के खट्टे पानी के साथ।",
"keema-naan-starter": "मसालेदार कीमा से भरा नरम नान, सुनहरा होने तक बेक किया गया।",
"palak-paneer": "हल्के मसालों और सुगंधित जड़ी-बूटियों के साथ क्रीमी पालक ग्रेवी में पका पनीर।",
"shahi-paneer": "भारतीय मसालों के साथ समृद्ध क्रीमी काजू-टमाटर ग्रेवी में नरम पनीर।",
"malai-kofta": "हल्के मसालों वाली समृद्ध क्रीमी प्याज-टमाटर ग्रेवी में नरम सब्ज़ी कोफ्ते।",
"daal-makhani": "सुगंधित मसालों के साथ मक्खनयुक्त क्रीमी टमाटर ग्रेवी में धीमी आंच पर पकी काली दाल।",
"lahore-chana": "पारंपरिक पंजाबी मसालों के साथ तीखी प्याज-टमाटर ग्रेवी में पके मसालेदार चने।",
"lamm-palak": "ताज़ी पालक के साथ हल्की, स्वादिष्ट ग्रेवी में पका कोमल लैम।",
"lamm-vindaloo": "सिरके और मिर्च आधारित सॉस में मसालेदार खट्टी लैम करी।",
"lamm-rogan-josh": "समृद्ध दही और कश्मीरी मसाला ग्रेवी में पकी सुगंधित लैम करी।",
"lamm-karahi": "टमाटर, अदरक, लहसुन और मसालों के साथ वोक में तले लैम के टुकड़े।",
"bong-nihari": "समृद्ध सुगंधित ग्रेवी में धीमी आंच पर पका बीफ शैंक, परंपरागत रूप से नान के साथ।",
"paye": "गाढ़ी, मसालेदार स्वादिष्ट ग्रेवी में धीमी आंच पर पके लैम के पैर।",
"shahi-burger": "विशेष सॉस, लेट्यूस और टमाटर के साथ नरम बन में रसीला मसालेदार मीट पैटी।",
"shami-sandwich": "चटनी और प्याज के साथ ब्रेड में परोसे गए मसालेदार कीमा शामी कबाब पैटी।",
"chicken-biryani": "नरम मसालेदार चिकन, केसर और कैरेमलाइज़्ड प्याज की परतों वाला सुगंधित बासमती चावल।",
"chicken-tikka": "दही और मसालों में मैरीनेट किया हड्डी रहित चिकन, तंदूर में ग्रिल किया।",
"chicken-karahi": "टमाटर, मिर्च और अदरक की मजबूत ग्रेवी में वोक में तला चिकन।",
"lahore-sizzler": "सब्ज़ियों और मसालेदार सॉस के साथ मैरीनेट चिकन की सिज़लिंग प्लेट।",
"butter-chicken": "हल्के मसालों के साथ क्रीमी टमाटर-मक्खन ग्रेवी में नरम चिकन।",
"chicken-haleem": "दाल, गेहूं और सुगंधित मसालों के साथ धीमी आंच पर पका कटा हुआ चिकन।",
"lahore-pizza": "कुरकुरे बेस पर मसालेदार चिकन, प्याज और विशेष लाहौरी सॉस वाली पिज़्ज़ा।",
"kebab-pizza": "कीमा कबाब टॉपिंग, पनीर, प्याज और सुगंधित मसालों वाली पिज़्ज़ा।",
"tikka-boti-pizza": "ग्रिल चिकन टिक्का, पनीर, टमाटर और ताज़ी जड़ी-बूटियों वाली पिज़्ज़ा।",
"peshawari-pizza": "कोमल मांस, मेवे, किशमिश और पेशावरी मसालों वाली नान-स्टाइल पिज़्ज़ा।",
"veg-pizza": "ताज़ी सब्ज़ियों, पनीर और टमाटर सॉस से भरी शाकाहारी पिज़्ज़ा।",
"tikka-boti-roll": "पुदीने की चटनी और प्याज के साथ नरम नान में लिपटा ग्रिल चिकन टिक्का।",
"kebab-roll": "चटनी, सलाद और सॉस के साथ नान में लिपटा मसालेदार कीमा कबाब।",
"falafel-roll": "सब्ज़ियों, हुम्मस और खट्टी सॉस के साथ नान में लिपटा कुरकुरा फलाफेल।",
"paneer-roll": "मसालों, चटनी और ताज़ी सब्ज़ियों के साथ नान में लिपटे ग्रिल पनीर के क्यूब।",
"namakpare": "अजवाइन और नमक से सीज़न किए कुरकुरे, नमकीन तले हुए आटे के स्नैक्स।",
"jalebi": "सुगंधित चीनी की चाशनी में भीगे कुरकुरे सुनहरे केसर के सर्पिल।",
"gajar-halwa": "दूध, चीनी, घी और मेवों के साथ धीमी आंच पर पकाई मीठी गाजर की हलवा।",
"rasmalai": "इलायची और केसर वाले ठंडे मीठे दूध में भीगे नरम पनीर के गोले।",
"kulfi": "इलायची, पिस्ता और केसर वाली क्रीमी पारंपरिक जमी हुई दूध की मिठाई।",
"masala-chai": "दूध, इलायची, अदरक और सुगंधित मसालों से बनी पारंपरिक मसाला चाय।",
"mango-lassi": "पके आम और इलायची के साथ मिलाई ताज़ा मीठी दही की ड्रिंक।",
"coca-cola": "क्लासिक ठंडी कोला सॉफ्ट ड्रिंक।",
"pepsi-fanta": "ताज़ा कोला या संतरे के स्वाद वाला कार्बोनेटेड पेय।",
"sprite-ramlosa": "कुरकुरी नींबू-नीबू सोडा या स्पार्कलिंग मिनरल वॉटर।",
"energy-drink": "तुरंत ऊर्जा के लिए कैफीन युक्त एनर्जी ड्रिंक।",
"juice": "ताज़ा फलों का रस, आमतौर पर आम या अन्य मौसमी स्वाद।",
"coffee": "ताज़ा बनी गर्म कॉफी।",
"latte": "भाप वाले दूध और हल्के फोम के साथ एस्प्रेसो कॉफी।",
"cappuccino": "भाप वाले दूध और गाढ़े फोम से ढका एस्प्रेसो।",
"tea": "पारंपरिक गर्म काली चाय, सादी या दूध के साथ।",
};
export const MENU_DESCRIPTION_SUPPLEMENT: Partial<Record<Language, Record<string, string>>> = {
en: {
"shakar-paray": "Sweet crispy flour bites coated in sugar syrup — a classic mithai snack.",
"gajar-barfi": "Rich carrot fudge made with milk, ghee and nuts — dense and aromatic.",
"habshi-halwa": "Dark, caramelised semolina halwa slow-cooked with ghee, milk and nuts.",
"gulab-jaman": "Soft milk-solid dumplings soaked in rose-cardamom sugar syrup.",
"lambay-gulab-jaman": "Elongated gulab jamun with extra syrup — a Shahi Sweets favourite.",
"cream-gulab-jaman": "Gulab jamun filled with creamy centre, finished in fragrant syrup.",
"ras-gulay": "Spongy cottage-cheese balls in light sugar syrup — chilled and refreshing.",
"cham-cham": "Oval Bengali sweet coated in coconut or pistachio — soft and milky.",
"paira": "Traditional milk fudge sweet with a smooth, grainy texture.",
"laddu": "Round gram-flour and ghee sweet balls — festive and aromatic.",
"qalakand": "Grainy milk cake sweet flavoured with cardamom — fresh and delicate.",
"patisa": "Flaky, layered soan papdi-style sweet that crumbles and melts on the tongue.",
"baisan-patisa": "Gram-flour patisa with crisp layers and a nutty, buttery finish.",
"plain-barfi": "Classic milk barfi set with sugar and cardamom — simple and elegant.",
"badam-barfi": "Rich almond barfi with ground nuts and a smooth, luxurious bite.",
"pistachio-barfi": "Vibrant pistachio barfi — nutty, fragrant and beautifully green.",
"pink-barfi": "Festive pink milk barfi with a soft texture and delicate sweetness.",
"coconut-barfi": "Coconut-forward barfi with tropical aroma and a chewy-soft bite.",
"chocolate-barfi": "Milk barfi blended with cocoa — a modern twist on a classic sweet.",
"baisan-barfi": "Roasted gram-flour barfi with ghee and sugar — warm and nutty.",
"milk-cake-plain": "Caramelised milk cake with a dense, fudgy centre and golden crust.",
"milk-cake-khajoor": "Milk cake with dates — rich, chewy and naturally sweet.",
"milk-cake-akhrot": "Milk cake studded with walnuts for extra crunch and depth."
},
sv: {
"shakar-paray": "Söta knapriga mjölsnacks glaserade med sockersirap — ett klassiskt mithai-snack.",
"gajar-barfi": "Rik morotsfudge gjord med mjölk, ghee och nötter — tät och aromatisk.",
"habshi-halwa": "Mörk karamelliserad halva långsamt kokad med ghee, mjölk och nötter.",
"gulab-jaman": "Mjuka mjölksolidkulor indränkta i ros-kardemummsirap.",
"lambay-gulab-jaman": "Förlängda gulab jamun med extra sirap — en Shahi Sweets-favorit.",
"cream-gulab-jaman": "Gulab jamun fylld med krämig kärna, avslutad i doftande sirap.",
"ras-gulay": "Svampiga kesokulor i lätt sockersirap — kyld och uppfriskande.",
"cham-cham": "Oval bengalisk sötsak täckt med kokos eller pistasch — mjuk och mjölkig.",
"paira": "Traditionell mjölkfudgesöt med slät, kornig textur.",
"laddu": "Runda gramflour- och ghee-sötkulor — festliga och aromatiska.",
"qalakand": "Kornig mjölkkaka söt smaksatt med kardemumma — färsk och delikat.",
"patisa": "Flikigt, lagerformat soan papdi-sött som smular och smälter på tungan.",
"baisan-patisa": "Gramflour-patisa med krispiga lager och nötig, smörig finish.",
"plain-barfi": "Klassisk mjölkbarfi med socker och kardemumma — enkel och elegant.",
"badam-barfi": "Rik mandelbarfi med malda nötter och en slät, lyxig tugga.",
"pistachio-barfi": "Livfull pistaschbarfi — nötig, doftande och vackert grön.",
"pink-barfi": "Festlig rosa mjölkbarfi med mjuk textur och delikat sötma.",
"coconut-barfi": "Kokosbarfi med tropisk arom och en segmjuk tugga.",
"chocolate-barfi": "Mjölkbarfi blandad med kakao — en modern twist på en klassiker.",
"baisan-barfi": "Rostat gramflour-barfi med ghee och socker — varmt och nötigt.",
"milk-cake-plain": "Karamelliserad mjölkkaka med tät, fudgeaktig kärna och gyllene skorpa.",
"milk-cake-khajoor": "Mjölkkaka med dadlar — rik, seg och naturligt söt.",
"milk-cake-akhrot": "Mjölkkaka med valnötter för extra crunch och djup."
},
hi: {
"shakar-paray": "चीनी की चाशनी में लिपटे मीठे कुरकुरे आटे के बाइट्स — एक क्लासिक मिठाई स्नैक।",
"gajar-barfi": "दूध, घी और मेवों से बनी समृद्ध गाजर की बर्फी — घनी और सुगंधित।",
"habshi-halwa": "घी, दूध और मेवों के साथ धीमी आंच पर पकाई गई गहरी कैरेमलाइज़्ड सूजी हलवा।",
"gulab-jaman": "गुलाब-इलायची की चाशनी में भीगे नरम दूध के गोले।",
"lambay-gulab-jaman": "अतिरिक्त चाशनी वाले लंबे गुलाब जामुन — शाही स्वीट्स की पसंदीदा।",
"cream-gulab-jaman": "क्रीमी भरावन वाले गुलाब जामुन, सुगंधित चाशनी में तैयार।",
"ras-gulay": "हल्की चाशनी में स्पंजी पनीर के गोले — ठंडे और ताज़े।",
"cham-cham": "नारियल या पिस्ता से ढकी अंडाकार बंगाली मिठाई — नरम और दूधिया।",
"paira": "पारंपरिक दूध की मिठाई, चिकनी दानेदार बनावट के साथ।",
"laddu": "देसी घी और बेसन के गोल लड्डू — उत्सवी और सुगंधित।",
"qalakand": "इलायची से सुगंधित दानेदार दूध केक मिठाई — ताज़ी और नाज़ुक।",
"patisa": "पतली परतों वाली सोन पापड़ी जैसी मिठाई जो जीभ पर घुल जाती है।",
"baisan-patisa": "कुरकुरी परतों वाली बेसन पटिसा — नट्टी और मक्खन जैसी।",
"plain-barfi": "चीनी और इलायची वाली क्लासिक दूध की बर्फी।",
"badam-barfi": "पिसे बादाम वाली समृद्ध बर्फी — चिकनी और शाही।",
"pistachio-barfi": "खुशबूदार हरी पिस्ता बर्फी।",
"pink-barfi": "त्योहारी गुलाबी दूध बर्फी — नरम और नाज़ुक मिठास।",
"coconut-barfi": "नारियल की सुगंध वाली चबाने वाली बर्फी।",
"chocolate-barfi": "कोकोा मिली दूध बर्फी — क्लासिक का आधुनिक रूप।",
"baisan-barfi": "घी और चीनी वाली भुनी बेसन बर्फी।",
"milk-cake-plain": "सुनहरी परत वाला कैरेमलाइज़्ड दूध केक।",
"milk-cake-khajoor": "खजूर वाला दूध केक — समृद्ध और प्राकृतिक मीठा।",
"milk-cake-akhrot": "अखरोट वाला दूध केक — अतिरिक्त कुरकुरापन।"
},
ar: {
"shakar-paray": "لقمات دقيق مقرمشة حلوة مغطاة بشراب السكر — وجبة ميثاي كلاسيكية.",
"gajar-barfi": "فudge جزر غني مصنوع من الحليب والسمن والمكسرات — كثيف وعطري.",
"habshi-halwa": "حلوى سميد داكنة مطهوة ببطء مع السمن والحليب والمكسرات.",
"gulab-jaman": "كرات حليب طرية منقوعة في شراب الورد والهيل.",
"lambay-gulab-jaman": "جلاب جامون ممدود مع شراب إضافي — مفضل من شاهي سويتس.",
"cream-gulab-jaman": "جلاب جامون محشو بمركز كريمي في شراب عطري.",
"ras-gulay": "كرات جبن إسفنجية في شراب سكر خفيف — باردة ومنعشة.",
"cham-cham": "حلوى بنغالية بيضاوية مغطاة بجوز الهند أو الفستق — طرية وحليبية.",
"paira": "حلوى حليب تقليدية بملمس ناعم وحبيبي.",
"laddu": "كرات حلوة من الدقيق والسمن — احتفالية وعطرية.",
"qalakand": "حلوى كعكة حليب حبيبية بنكهة الهيل — طازجة ورقيقة.",
"patisa": "حلوى سون بابدي متقشرة تتفتت وتذوب على اللسان.",
"baisan-patisa": "باتيسا بيسان بطبقات مقرمشة ولمسة زبدية جوزية.",
"plain-barfi": "بارفي حليب كلاسيكي بالسكر والهيل — بسيط وأنيق.",
"badam-barfi": "بارفي لوز غني بمكسرات مطحونة ولمسة فاخرة.",
"pistachio-barfi": "بارفي فستق نابض بالحياة — جوزي وعطري وأخضر جميل.",
"pink-barfi": "بارفي حليب وردي احتفالي بنعومة وحلاوة رقيقة.",
"coconut-barfi": "بارفي جوز الهند بعبير استوائي وقوام مطاطي ناعم.",
"chocolate-barfi": "بارفي حليب ممزوج بالكاكاو — لمسة عصرية على كلاسيك.",
"baisan-barfi": "بارفي بيسان محمص بالسمن والسكر — دافئ وجوزي.",
"milk-cake-plain": "كعكة حليب كراميلية بمركز فدجي كثيف وقشرة ذهبية.",
"milk-cake-khajoor": "كعكة حليب بالتمر — غنية ومطاطية وحلوة طبيعياً.",
"milk-cake-akhrot": "كعكة حليب بالجوز لمزيد من القرمشة والعمق."
},
tr: {
"shakar-paray": "Şeker şurubuyla kaplı tatlı çıtır un lokmaları — klasik mithai atıştırmalığı.",
"gajar-barfi": "Süt, ghee ve kuruyemişlerle yapılmış zengin havuç fudge — yoğun ve aromatik.",
"habshi-halwa": "Ghee, süt ve kuruyemişlerle yavaş pişirilmiş koyu karamelize irmik helvası.",
"gulab-jaman": "Gül-kakule şurubuna batırılmış yumuşak sütlü toplar.",
"lambay-gulab-jaman": "Ekstra şuruplu uzun gulab jamun — Shahi Sweets favorisi.",
"cream-gulab-jaman": "Kremalı dolgulu gulab jamun, aromatik şurupta.",
"ras-gulay": "Hafif şeker şurubunda süngerimsi peynir topları — soğuk ve ferahlatıcı.",
"cham-cham": "Hindistan cevizi veya antep fıstığı kaplı oval Bengal tatlısı — yumuşak ve sütlü.",
"paira": "Pürüzsüz, taneli dokulu geleneksel sütlü fudge tatlısı.",
"laddu": "Yuvarlak gram unu ve ghee tatlı topları — şenlikli ve aromatik.",
"qalakand": "Kakule aromalı taneli sütlü kek tatlısı — taze ve narin.",
"patisa": "Dilde dağılan gevrek katmanlı soan papdi tarzı tatlı.",
"baisan-patisa": "Çıtır katmanlı besan patisa — fındıksı ve tereyağlı.",
"plain-barfi": "Şeker ve kakule ile klasik sütlü barfi — sade ve zarif.",
"badam-barfi": "Öğütülmüş bademli zengin barfi — pürüzsüz ve lüks.",
"pistachio-barfi": "Canlı antep fıstıklı barfi — fındıksı, aromatik ve yeşil.",
"pink-barfi": "Yumuşak dokulu ve narin tatlı pembe sütlü barfi.",
"coconut-barfi": "Tropikal aroma ve yumuşak çiğnenebilir hindistan cevizli barfi.",
"chocolate-barfi": "Kakao karışımlı sütlü barfi — klasik üzerine modern dokunuş.",
"baisan-barfi": "Ghee ve şekerle kavrulmuş besan barfi — sıcak ve fındıksı.",
"milk-cake-plain": "Yoğun fudji merkezli ve altın kabuklu karamelize sütlü kek.",
"milk-cake-khajoor": "Hurmalı sütlü kek — zengin, yapışkan ve doğal tatlı.",
"milk-cake-akhrot": "Ekstra çıtırlık için cevizli sütlü kek."
},
ur: {
"shakar-paray": "چینی کی چاشنی میں لپٹے میٹھے کرکرے آٹے کے بائٹس — ایک کلاسک مٹھائی سnack۔",
"gajar-barfi": "دودھ، گھی اور خشک میووں سے بنی امیر گاجر کی برفی — گھنی اور خوشبودار۔",
"habshi-halwa": "گھی، دودھ اور خشک میووں کے ساتھ دھیمی آنچ پر پکی گہری کیراملائزڈ سوجی حلوہ۔",
"gulab-jaman": "گلاب-الائچی کی چاشنی میں بھیگے نرم دودھ کے گولے۔",
"lambay-gulab-jaman": "اضافی چاشنی والے لمبے گلاب جامن — شاہی سویٹس کی پسندیدہ۔",
"cream-gulab-jaman": "کریمی بھرائی والے گلاب جامن، خوشبودار چاشنی میں تیار۔",
"ras-gulay": "ہلکی چاشنی میں اسفنجی پنیر کے گولے — ٹھنڈے اور تازہ۔",
"cham-cham": "ناریل یا پستہ سے ڈھکی بیضوی بنگالی مٹھائی — نرم اور دودھ جیسی۔",
"paira": "روایتی دودھ کی مٹھائی، ہموار دانے دار بناوٹ کے ساتھ۔",
"laddu": "دیسی گھی اور بیسن کے گول لڈو — تہواروں والی اور خوشبودار۔",
"qalakand": "الائچی سے خوشبودار دانے دار دودھ کیک مٹھائی — تازہ اور نازک۔",
"patisa": "پتلی پرتوں والی سون پاپڑی جیسی مٹھائی جو زبان پر گھل جاتی ہے۔",
"baisan-patisa": "کرکرے پرتوں والی بیسن پٹیسہ — نٹھی اور مکھن جیسی۔",
"plain-barfi": "چینی اور الائچی والی کلاسک دودھ کی برفی۔",
"badam-barfi": "پیسے بادام والی امیر برفی — ہموار اور شاہی۔",
"pistachio-barfi": "خوشبودار ہری پستہ برفی۔",
"pink-barfi": "تہواروں والی گلابی دودھ برفی — نرم اور نازک مٹھاس۔",
"coconut-barfi": "ناریل کی خوشبو والی چبانے والی برفی۔",
"chocolate-barfi": "کوکو ملی دودھ برفی — کلاسک کا جدید روپ۔",
"baisan-barfi": "گھی اور چینی والی بھنی بیسن برفی۔",
"milk-cake-plain": "سنہری پرت والا کیراملائزڈ دودھ کیک۔",
"milk-cake-khajoor": "کھجور والا دودھ کیک — امیر اور قدرتی میٹھا۔",
"milk-cake-akhrot": "اخروٹ والا دودھ کیک — اضافی کرکراہٹ۔"
},
};
@@ -0,0 +1 @@
export { HINDI_MENU_DESCRIPTIONS } from './description-supplement';
+466
View File
@@ -0,0 +1,466 @@
import type { Language } from '@/domain/language/entities';
const EN: Record<string, string> = {
"samosa-aloo": "Samosa Aloo Veg",
"samosa-keema": "Samosa Keema",
"samosa-chat": "Samosa Chat",
"chana-chat": "Chana Chat",
"panipuri": "Panipuri / Golgappe",
"keema-naan-starter": "Keema Naan",
"palak-paneer": "Palak Paneer",
"shahi-paneer": "Shahi Paneer",
"malai-kofta": "Malai Kofta",
"daal-makhani": "Daal Makhani",
"lahore-chana": "Lahore Chana",
"lamm-palak": "Lamm Palak",
"lamm-vindaloo": "Lamm Vindaloo",
"lamm-rogan-josh": "Lamm Rogan Josh",
"lamm-karahi": "Lamm Karahi",
"bong-nihari": "Bong Nihari",
"paye": "Paye",
"shahi-burger": "Shahi Burger",
"shami-sandwich": "Shami Sandwich Menu",
"chicken-biryani": "Chicken Biryani",
"chicken-tikka": "Chicken Tikka",
"chicken-karahi": "Chicken Karahi",
"lahore-sizzler": "Lahore Sizzler",
"butter-chicken": "Butter Chicken",
"chicken-haleem": "Chicken Haleem",
"lahore-pizza": "Lahore Pizza",
"kebab-pizza": "Kebab Pizza",
"tikka-boti-pizza": "Tikka Boti Pizza",
"peshawari-pizza": "Peshawari Pizza",
"veg-pizza": "Veg Pizza",
"tikka-boti-roll": "Tikka Boti Roll",
"kebab-roll": "Kebab Roll",
"falafel-roll": "Falafel Roll",
"paneer-roll": "Paneer Roll",
"namakpare": "Namak Paray",
"shakar-paray": "Shakar Paray",
"jalebi": "Jalebi",
"gajar-halwa": "Gajar Halwa",
"gajar-barfi": "Gajar Barfi",
"habshi-halwa": "Habshi Halwa",
"gulab-jaman": "Gulab Jamun",
"lambay-gulab-jaman": "Lambay Gulab Jamun",
"cream-gulab-jaman": "Cream Gulab Jamun",
"ras-gulay": "Ras Gulay",
"rasmalai": "Rasmalai",
"cham-cham": "Cham Cham",
"paira": "Paira",
"laddu": "Laddu",
"qalakand": "Kalakand",
"patisa": "Patisa",
"baisan-patisa": "Besan Patisa",
"plain-barfi": "Plain Barfi",
"badam-barfi": "Badam Barfi",
"pistachio-barfi": "Pistachio Barfi",
"pink-barfi": "Pink Barfi",
"coconut-barfi": "Coconut Barfi",
"chocolate-barfi": "Chocolate Barfi",
"baisan-barfi": "Besan Barfi",
"milk-cake-plain": "Milk Cake",
"milk-cake-khajoor": "Milk Cake Khajoor",
"milk-cake-akhrot": "Milk Cake Akhrot",
"kulfi": "Kulfi",
"masala-chai": "Masala Chai",
"mango-lassi": "Mango Lassi",
"coca-cola": "Coca-Cola",
"pepsi-fanta": "Pepsi / Fanta",
"sprite-ramlosa": "Sprite / Ramlösa",
"energy-drink": "Energy Drink",
"juice": "Juice",
"coffee": "Coffee",
"latte": "Latte",
"cappuccino": "Cappuccino",
"tea": "Tea"
};
const LOCALIZED: Partial<Record<Language, Record<string, string>>> = {
sv: {
"samosa-aloo": "Samosa Aloo (veg)",
"samosa-keema": "Samosa Keema",
"samosa-chat": "Samosa Chaat",
"chana-chat": "Chana Chaat",
"panipuri": "Panipuri / Golgappa",
"keema-naan-starter": "Keema Naan",
"palak-paneer": "Palak Paneer",
"shahi-paneer": "Shahi Paneer",
"malai-kofta": "Malai Kofta",
"daal-makhani": "Daal Makhani",
"lahore-chana": "Lahore Chana",
"lamm-palak": "Lamm Palak",
"lamm-vindaloo": "Lamm Vindaloo",
"lamm-rogan-josh": "Lamm Rogan Josh",
"lamm-karahi": "Lamm Karahi",
"bong-nihari": "Bong Nihari",
"paye": "Paye",
"shahi-burger": "Shahi Burgare",
"shami-sandwich": "Shami Sandwich",
"chicken-biryani": "Kyckling Biryani",
"chicken-tikka": "Kyckling Tikka",
"chicken-karahi": "Kyckling Karahi",
"lahore-sizzler": "Lahore Sizzler",
"butter-chicken": "Butter Chicken",
"chicken-haleem": "Kyckling Haleem",
"lahore-pizza": "Lahore Pizza",
"kebab-pizza": "Kebab Pizza",
"tikka-boti-pizza": "Tikka Boti Pizza",
"peshawari-pizza": "Peshawari Pizza",
"veg-pizza": "Veg Pizza",
"tikka-boti-roll": "Tikka Boti Rulle",
"kebab-roll": "Kebab Rulle",
"falafel-roll": "Falafel Rulle",
"paneer-roll": "Paneer Rulle",
"namakpare": "Namak Paray",
"shakar-paray": "Shakar Paray",
"jalebi": "Jalebi",
"gajar-halwa": "Gajar Halwa",
"gajar-barfi": "Gajar Barfi",
"habshi-halwa": "Habshi Halwa",
"gulab-jaman": "Gulab Jamun",
"lambay-gulab-jaman": "Lambay Gulab Jamun",
"cream-gulab-jaman": "Cream Gulab Jamun",
"ras-gulay": "Ras Gulay",
"rasmalai": "Rasmalai",
"cham-cham": "Cham Cham",
"paira": "Paira",
"laddu": "Laddu",
"qalakand": "Kalakand",
"patisa": "Patisa",
"baisan-patisa": "Besan Patisa",
"plain-barfi": "Plain Barfi",
"badam-barfi": "Badam Barfi",
"pistachio-barfi": "Pistachio Barfi",
"pink-barfi": "Pink Barfi",
"coconut-barfi": "Coconut Barfi",
"chocolate-barfi": "Chocolate Barfi",
"baisan-barfi": "Besan Barfi",
"milk-cake-plain": "Mjölkkaka",
"milk-cake-khajoor": "Mjölkkaka med dadlar",
"milk-cake-akhrot": "Mjölkkaka med valnötter",
"kulfi": "Kulfi",
"masala-chai": "Masala Chai",
"mango-lassi": "Mango Lassi",
"coca-cola": "Coca-Cola",
"pepsi-fanta": "Pepsi / Fanta",
"sprite-ramlosa": "Sprite / Ramlösa",
"energy-drink": "Energidryck",
"juice": "Juice",
"coffee": "Kaffe",
"latte": "Latte",
"cappuccino": "Cappuccino",
"tea": "Te"
},
ar: {
"samosa-aloo": "سموسة بطاطا (نباتي)",
"samosa-keema": "سموسة لحم مفروم",
"samosa-chat": "سموسة تشات",
"chana-chat": "تشات حمص",
"panipuri": "بانيبوري / غولغابا",
"keema-naan-starter": "نان لحم مفروم",
"palak-paneer": "بالاك بانير",
"shahi-paneer": "شاهي بانير",
"malai-kofta": "مالاي كوفتا",
"daal-makhani": "دال ماخاني",
"lahore-chana": "لاهوري حمص",
"lamm-palak": "لحم غنم بالسبانخ",
"lamm-vindaloo": "لحم غنم فيندالو",
"lamm-rogan-josh": "لحم غنم روغان جوش",
"lamm-karahi": "لحم غنم كراهي",
"bong-nihari": "نيهاري لحم بقر",
"paye": "أقدام غنم",
"shahi-burger": "برغر شاهي",
"shami-sandwich": "ساندويتش شامي",
"chicken-biryani": "برياني دجاج",
"chicken-tikka": "دجاج تكا",
"chicken-karahi": "دجاج كراهي",
"lahore-sizzler": "سيزلر لاهوري",
"butter-chicken": "دجاج بالزبدة",
"chicken-haleem": "حليم دجاج",
"lahore-pizza": "بيتزا لاهوري",
"kebab-pizza": "بيتزا كباب",
"tikka-boti-pizza": "بيتزا تكا بوتي",
"peshawari-pizza": "بيتزا بشاورية",
"veg-pizza": "بيتزا نباتية",
"tikka-boti-roll": "رول تكا بوتي",
"kebab-roll": "رول كباب",
"falafel-roll": "رول فلافل",
"paneer-roll": "رول بانير",
"namakpare": "ناماك باراي",
"shakar-paray": "شكر باراي",
"jalebi": "جلبي",
"gajar-halwa": "حلوى الجزر",
"gajar-barfi": "بارفي الجزر",
"habshi-halwa": "حلوى حبشي",
"gulab-jaman": "جلاب جامون",
"lambay-gulab-jaman": "جلاب جامون طويل",
"cream-gulab-jaman": "جلاب جامون بالكريمة",
"ras-gulay": "راس غولاي",
"rasmalai": "راس مالاي",
"cham-cham": "تشام تشام",
"paira": "بيرا",
"laddu": "لادو",
"qalakand": "كالاكاند",
"patisa": "باتيسا",
"baisan-patisa": "باتيسا بيسان",
"plain-barfi": "بارفي عادي",
"badam-barfi": "بارفي لوز",
"pistachio-barfi": "بارفي فستق",
"pink-barfi": "بارفي وردي",
"coconut-barfi": "بارفي جوز الهند",
"chocolate-barfi": "بارفي شوكولاتة",
"baisan-barfi": "بارفي بيسان",
"milk-cake-plain": "كعكة الحليب",
"milk-cake-khajoor": "كعكة الحليب بالتمر",
"milk-cake-akhrot": "كعكة الحليب بالجوز",
"kulfi": "كولفي",
"masala-chai": "شاي ماسالا",
"mango-lassi": "لاسي مانجو",
"coca-cola": "كوكا كولا",
"pepsi-fanta": "بيبسي / فانتا",
"sprite-ramlosa": "سبرايت / راملوزا",
"energy-drink": "مشروب طاقة",
"juice": "عصير",
"coffee": "قهوة",
"latte": "لاتيه",
"cappuccino": "كابتشينو",
"tea": "شاي"
},
tr: {
"samosa-aloo": "Patatesli Samosa (vej)",
"samosa-keema": "Kıymalı Samosa",
"samosa-chat": "Samosa Chaat",
"chana-chat": "Chana Chaat",
"panipuri": "Panipuri / Golgappa",
"keema-naan-starter": "Kıymalı Naan",
"palak-paneer": "Palak Paneer",
"shahi-paneer": "Shahi Paneer",
"malai-kofta": "Malai Kofta",
"daal-makhani": "Daal Makhani",
"lahore-chana": "Lahore Nohut",
"lamm-palak": "Kuzu Ispanak",
"lamm-vindaloo": "Kuzu Vindaloo",
"lamm-rogan-josh": "Kuzu Rogan Josh",
"lamm-karahi": "Kuzu Karahi",
"bong-nihari": "Bong Nihari",
"paye": "Paça",
"shahi-burger": "Shahi Burger",
"shami-sandwich": "Shami Sandviç",
"chicken-biryani": "Tavuk Biryani",
"chicken-tikka": "Tavuk Tikka",
"chicken-karahi": "Tavuk Karahi",
"lahore-sizzler": "Lahore Sizzler",
"butter-chicken": "Butter Chicken",
"chicken-haleem": "Tavuk Haleem",
"lahore-pizza": "Lahore Pizza",
"kebab-pizza": "Kebab Pizza",
"tikka-boti-pizza": "Tikka Boti Pizza",
"peshawari-pizza": "Peşaveri Pizza",
"veg-pizza": "Vejetaryen Pizza",
"tikka-boti-roll": "Tikka Boti Dürüm",
"kebab-roll": "Kebab Dürüm",
"falafel-roll": "Falafel Dürüm",
"paneer-roll": "Paneer Dürüm",
"namakpare": "Namak Paray",
"shakar-paray": "Shakar Paray",
"jalebi": "Jalebi",
"gajar-halwa": "Gajar Helva",
"gajar-barfi": "Gajar Barfi",
"habshi-halwa": "Habshi Helva",
"gulab-jaman": "Gulab Jamun",
"lambay-gulab-jaman": "Uzun Gulab Jamun",
"cream-gulab-jaman": "Kremalı Gulab Jamun",
"ras-gulay": "Ras Gulay",
"rasmalai": "Rasmalai",
"cham-cham": "Cham Cham",
"paira": "Paira",
"laddu": "Laddu",
"qalakand": "Kalakand",
"patisa": "Patisa",
"baisan-patisa": "Besan Patisa",
"plain-barfi": "Sade Barfi",
"badam-barfi": "Badem Barfi",
"pistachio-barfi": "Antep Fıstıklı Barfi",
"pink-barfi": "Pembe Barfi",
"coconut-barfi": "Hindistan Cevizli Barfi",
"chocolate-barfi": "Çikolatalı Barfi",
"baisan-barfi": "Besan Barfi",
"milk-cake-plain": "Süt Keki",
"milk-cake-khajoor": "Hurmalı Süt Keki",
"milk-cake-akhrot": "Cevizli Süt Keki",
"kulfi": "Kulfi",
"masala-chai": "Masala Çay",
"mango-lassi": "Mango Lassi",
"coca-cola": "Coca-Cola",
"pepsi-fanta": "Pepsi / Fanta",
"sprite-ramlosa": "Sprite / Ramlösa",
"energy-drink": "Enerji İçeceği",
"juice": "Meyve Suyu",
"coffee": "Kahve",
"latte": "Latte",
"cappuccino": "Kapuçino",
"tea": "Çay"
},
hi: {
"samosa-aloo": "आलू समोसा (वेज)",
"samosa-keema": "कीमा समोसा",
"samosa-chat": "समोसा चाट",
"chana-chat": "चना चाट",
"panipuri": "पानी पूरी / गोलगप्पे",
"keema-naan-starter": "कीमा नान",
"palak-paneer": "पालक पनीर",
"shahi-paneer": "शाही पनीर",
"malai-kofta": "मलाई कोफ्ता",
"daal-makhani": "दाल मखनी",
"lahore-chana": "लाहौर चना",
"lamm-palak": "लैम पालक",
"lamm-vindaloo": "लैम विंदालू",
"lamm-rogan-josh": "लैम रोगन जोश",
"lamm-karahi": "लैम कराही",
"bong-nihari": "बोंग निहारी",
"paye": "पाया",
"shahi-burger": "शाही बर्गर",
"shami-sandwich": "शामी सैंडविच",
"chicken-biryani": "चिकन बिरयानी",
"chicken-tikka": "चिकन टिक्का",
"chicken-karahi": "चिकन कराही",
"lahore-sizzler": "लाहौर सिज़लर",
"butter-chicken": "बटर चिकन",
"chicken-haleem": "चिकन हलीम",
"lahore-pizza": "लाहौर पिज़्ज़ा",
"kebab-pizza": "कबाब पिज़्ज़ा",
"tikka-boti-pizza": "टिक्का बोटी पिज़्ज़ा",
"peshawari-pizza": "पेशावरी पिज़्ज़ा",
"veg-pizza": "वेज पिज़्ज़ा",
"tikka-boti-roll": "टिक्का बोटी रोल",
"kebab-roll": "कबाब रोल",
"falafel-roll": "फलाफेल रोल",
"paneer-roll": "पनीर रोल",
"namakpare": "नमक परे",
"shakar-paray": "शक्कर परे",
"jalebi": "जलेबी",
"gajar-halwa": "गाजर हलवा",
"gajar-barfi": "गाजर बर्फी",
"habshi-halwa": "हबशी हलवा",
"gulab-jaman": "गुलाब जामुन",
"lambay-gulab-jaman": "लंबे गुलाब जामुन",
"cream-gulab-jaman": "क्रीम गुलाब जामुन",
"ras-gulay": "रस गुल्ले",
"rasmalai": "रसमलाई",
"cham-cham": "चम चम",
"paira": "पैरा",
"laddu": "लड्डू",
"qalakand": "कलाकंद",
"patisa": "पटिसा",
"baisan-patisa": "बेसन पटिसा",
"plain-barfi": "सादी बर्फी",
"badam-barfi": "बादाम बर्फी",
"pistachio-barfi": "पिस्ता बर्फी",
"pink-barfi": "गुलाबी बर्फी",
"coconut-barfi": "नारियल बर्फी",
"chocolate-barfi": "चॉकलेट बर्फी",
"baisan-barfi": "बेसन बर्फी",
"milk-cake-plain": "मिल्क केक",
"milk-cake-khajoor": "मिल्क केक खजूर",
"milk-cake-akhrot": "मिल्क केक अखरोट",
"kulfi": "कुल्फी",
"masala-chai": "मसाला चाय",
"mango-lassi": "आम लassi",
"coca-cola": "कोका-कोला",
"pepsi-fanta": "पेप्सी / फैंटा",
"sprite-ramlosa": "स्प्राइट / रामलोसा",
"energy-drink": "एनर्जी ड्रिंक",
"juice": "जूस",
"coffee": "कॉफी",
"latte": "लाटे",
"cappuccino": "कैप्पुचिनो",
"tea": "चाय"
},
ur: {
"samosa-aloo": "آلو سموسہ (ویج)",
"samosa-keema": "کیما سموسہ",
"samosa-chat": "سموسہ چاٹ",
"chana-chat": "چنا چاٹ",
"panipuri": "پانی پوری / گول گپے",
"keema-naan-starter": "کیما نان",
"palak-paneer": "پالک پنیر",
"shahi-paneer": "شاہی پنیر",
"malai-kofta": "ملائی کوفتہ",
"daal-makhani": "دال مکھنی",
"lahore-chana": "لاہور چنا",
"lamm-palak": "لیم پالک",
"lamm-vindaloo": "لیم وندالو",
"lamm-rogan-josh": "لیم روگن جوش",
"lamm-karahi": "لیم کراہی",
"bong-nihari": "بونگ نہاری",
"paye": "پائے",
"shahi-burger": "شاہی برگر",
"shami-sandwich": "شامی سینڈوچ",
"chicken-biryani": "چکن بریانی",
"chicken-tikka": "چکن ٹکا",
"chicken-karahi": "چکن کراہی",
"lahore-sizzler": "لاہور سزлер",
"butter-chicken": "بٹر چکن",
"chicken-haleem": "چکن حلیم",
"lahore-pizza": "لاہور پیزا",
"kebab-pizza": "کباب پیزا",
"tikka-boti-pizza": "ٹکا بوٹی پیزا",
"peshawari-pizza": "پشاوری پیزا",
"veg-pizza": "ویج پیزا",
"tikka-boti-roll": "ٹکا بوٹی رول",
"kebab-roll": "کباب رول",
"falafel-roll": "فلافل رول",
"paneer-roll": "پنیر رول",
"namakpare": "نمک پارے",
"shakar-paray": "شکر پارے",
"jalebi": "جلیبی",
"gajar-halwa": "گاجر حلوہ",
"gajar-barfi": "گاجر برفی",
"habshi-halwa": "حبشی حلوہ",
"gulab-jaman": "گلاب جامن",
"lambay-gulab-jaman": "لمبے گلاب جامن",
"cream-gulab-jaman": "کریم گلاب جامن",
"ras-gulay": "رس گلّے",
"rasmalai": "رسمالی",
"cham-cham": "چم چم",
"paira": "پیرا",
"laddu": "لڈو",
"qalakand": "قلاکند",
"patisa": "پٹیسہ",
"baisan-patisa": "بیسن پٹیسہ",
"plain-barfi": "سادہ برفی",
"badam-barfi": "بادام برفی",
"pistachio-barfi": "پستہ برفی",
"pink-barfi": "گلابی برفی",
"coconut-barfi": "ناریل برفی",
"chocolate-barfi": "چاکلیٹ برفی",
"baisan-barfi": "بیسن برفی",
"milk-cake-plain": "ملک کیک",
"milk-cake-khajoor": "ملک کیک کھجور",
"milk-cake-akhrot": "ملک کیک اخروٹ",
"kulfi": "قلفی",
"masala-chai": "مصالحہ چائے",
"mango-lassi": "آم لسی",
"coca-cola": "کوکا کولا",
"pepsi-fanta": "پیپسی / فانٹا",
"sprite-ramlosa": "سپرائٹ / راملوزا",
"energy-drink": "انرجی ڈرنک",
"juice": "جوس",
"coffee": "کافی",
"latte": "لیٹے",
"cappuccino": "کیپوچینو",
"tea": "چائے"
},
};
export function getMenuItemName(lang: Language, itemId: string, fallback: string): string {
if (lang === 'en') return EN[itemId] ?? fallback;
return LOCALIZED[lang]?.[itemId] ?? EN[itemId] ?? fallback;
}
+36 -10
View File
@@ -2,6 +2,10 @@ import type { Language, LanguageOption } from '@/domain/language/entities';
import { arTranslations } from './languages/ar';
import { trTranslations } from './languages/tr';
import { mergeTranslations } from './merge';
import {
HINDI_MENU_DESCRIPTIONS,
MENU_DESCRIPTION_SUPPLEMENT,
} from './menu/description-supplement';
export type { Language };
@@ -86,7 +90,7 @@ export const translations = {
filterGrill: 'Grill',
filterSweets: 'Sweets',
filterMeat: 'Meat',
filterStreet: 'Street',
filterStreet: 'Street Food',
filterRoll: 'Roll',
addToTable: 'Add to Table',
viewFullMenu: 'VIEW THE COMPLETE MENU — 40+ DISHES',
@@ -126,7 +130,6 @@ export const translations = {
"shami-sandwich": "Spiced minced meat shami kebab patties served in bread with chutney and onions.",
"chicken-biryani": "Fragrant aged basmati rice layered with tender spiced chicken, saffron and caramelized onions.",
"chicken-tikka": "Boneless chicken pieces marinated in yogurt and spices, grilled in a tandoor.",
"tikka-boti": "Tender chicken chunks marinated in spices and grilled on skewers.",
"chicken-karahi": "Wok-tossed chicken in a robust tomato, chili and ginger gravy.",
"lahore-sizzler": "Sizzling platter of marinated chicken with vegetables and spicy sauces.",
"butter-chicken": "Tender chicken in a creamy tomato and butter gravy with mild spices.",
@@ -196,6 +199,9 @@ export const translations = {
subtitle:
'Premium per-head packages for weddings, corporate events, and family gatherings — authentic Shahi flavours, beautifully served.',
perHead: 'per guest',
contactForPricing: 'Contact us for pricing',
packagePricingInquiry:
'Hello Shahi Kitchen 👋\n\nI would like pricing for the {{package}} catering package.\n\nPlease contact me with availability and options.\n\nThank you!',
includedDishes: 'Included dishes',
mostPopular: 'Most Popular',
guestNote: 'Custom menus available — contact us to tailor your event.',
@@ -457,7 +463,7 @@ export const translations = {
filterGrill: 'Grill',
filterSweets: 'Sötsaker',
filterMeat: 'Kött',
filterStreet: 'Gata',
filterStreet: 'Gatumat',
filterRoll: 'Rulle',
addToTable: 'Lägg till',
viewFullMenu: 'SE HELA MENYN — 40+ RÄTTER',
@@ -497,7 +503,6 @@ export const translations = {
"shami-sandwich": "Kryddade malet kött shami kebab-patties serverade i bröd med chutney och lök.",
"chicken-biryani": "Doftande åldrad basmatiris med lager av kryddad kyckling, saffran och karamelliserade lökar.",
"chicken-tikka": "Benfri kyckling marinerad i yoghurt och kryddor, grillad i tandoorugn.",
"tikka-boti": "Möra kycklingbitar marinerade i kryddor och grillade på spett.",
"chicken-karahi": "Wokad kyckling i en robust tomat-, chili- och ingefärssås.",
"lahore-sizzler": "Surrande fat med marinerad kyckling med grönsaker och kryddiga såser.",
"butter-chicken": "Mör kyckling i en krämig tomat- och smörsås med milda kryddor.",
@@ -564,6 +569,9 @@ export const translations = {
subtitle:
'Premium paket per gäst för bröllop, företagsevent och familjefester — autentiska Shahi-smaker, vackert serverade.',
perHead: 'per gäst',
contactForPricing: 'Kontakta oss för pris',
packagePricingInquiry:
'Hej Shahi Kitchen 👋\n\nJag vill ha pris för {{package}} cateringpaketet.\n\nVänligen kontakta mig med tillgänglighet och alternativ.\n\nTack!',
includedDishes: 'Ingående rätter',
mostPopular: 'Mest populär',
guestNote: 'Skräddarsydda menyer finns — kontakta oss för att anpassa ditt event.',
@@ -823,6 +831,7 @@ export const translations = {
filterRice: 'चावल',
filterGrill: 'ग्रिल',
filterSweets: 'मिठाइयाँ',
filterStreet: 'स्ट्रीट फूड',
addToTable: 'ऐड करें',
viewFullMenu: 'पूरी मेन्यू देखें — 40+ व्यंजन',
},
@@ -861,7 +870,6 @@ export const translations = {
"shami-sandwich": "Spiced minced meat shami kebab patties served in bread with chutney and onions.",
"chicken-biryani": "Fragrant aged basmati rice layered with tender spiced chicken, saffron and caramelized onions.",
"chicken-tikka": "Boneless chicken pieces marinated in yogurt and spices, grilled in a tandoor.",
"tikka-boti": "Tender chicken chunks marinated in spices and grilled on skewers.",
"chicken-karahi": "Wok-tossed chicken in a robust tomato, chili and ginger gravy.",
"lahore-sizzler": "Sizzling platter of marinated chicken with vegetables and spicy sauces.",
"butter-chicken": "Tender chicken in a creamy tomato and butter gravy with mild spices.",
@@ -1000,6 +1008,9 @@ export const translations = {
subtitle:
'शादियों, कॉर्पोरेट इवेंट और पारिवारिक समारोहों के लिए प्रीमियम प्रति व्यक्ति पैकेज — प्रामाणिक शाही स्वाद, सुंदर परोसा।',
perHead: 'प्रति अतिथि',
contactForPricing: 'मूल्य जानने के लिए हमसे संपर्क करें',
packagePricingInquiry:
'नमस्ते Shahi Kitchen 👋\n\nमुझे {{package}} कैटरिंग पैकेज की कीमत जाननी है।\n\nकृपया उपलब्धता और विकल्पों के साथ मुझसे संपर्क करें।\n\nधन्यवाद!',
includedDishes: 'शामिल व्यंजन',
mostPopular: 'सबसे लोकप्रिय',
guestNote: 'कस्टम मेनू उपलब्ध — अपने इवेंट के लिए हमसे संपर्क करें।',
@@ -1184,6 +1195,7 @@ export const translations = {
filterRice: 'چاول',
filterGrill: 'گرل',
filterSweets: 'مٹھائیاں',
filterStreet: 'سٹریٹ فوڈ',
addToTable: 'شامل کریں',
viewFullMenu: 'مکمل مینو دیکھیں — 40+ پکوان',
},
@@ -1222,7 +1234,6 @@ export const translations = {
"shami-sandwich": "مصالحہ دار کییما کے شامی کباب پیٹیز بریڈ میں چٹنی اور پیاز کے ساتھ۔",
"chicken-biryani": "خوشبودار پرانی باسمتی چاول کی تہوں میں نرم مصالحہ دار چکن، زعفران اور کاراملائزڈ پیاز۔",
"chicken-tikka": "ہڈیوں والے چکن کے ٹکڑے دہی اور مصالحوں میں مرینیٹ کیے گئے، ٹینڈور میں گرل کیے گئے۔",
"tikka-boti": "نرم چکن کے ٹکڑے مصالحوں میں مرینیٹ کیے گئے اور سیخوں پر گرل کیے گئے۔",
"chicken-karahi": "ووک میں تلے ہوئے چکن کا مضبوط ٹماٹر، مرچ اور ادرک کی گریوی میں۔",
"lahore-sizzler": "مرینیٹ کیے گئے چکن کا سٹیلنگ پلیٹر سبزیوں اور تیز ساس کے ساتھ۔",
"butter-chicken": "نرم چکن کریمی ٹماٹر اور مکھن کی گریوی میں ہلکے مصالحوں کے ساتھ۔",
@@ -1361,6 +1372,9 @@ export const translations = {
subtitle:
'شادیوں، کارپوریٹ ایونٹس اور خاندانی محفلوں کے لیے پریمیم فی مہمان پیکج — مستند شاہی ذائقے، خوبصورت پیشکش۔',
perHead: 'فی مہمان',
contactForPricing: 'قیمت کے لیے ہم سے رابطہ کریں',
packagePricingInquiry:
'ہیلو Shahi Kitchen 👋\n\nمجھے {{package}} کیٹرنگ پیکج کی قیمت جاننی ہے۔\n\nبراہ مہربانی دستیابی اور اختیارات کے ساتھ مجھ سے رابطہ کریں۔\n\nشکریہ!',
includedDishes: 'شامل کھانے',
mostPopular: 'سب سے مقبول',
guestNote: 'حسب ضرورت مینو دستیاب — اپنے ایونٹ کے لیے ہم سے رابطہ کریں۔',
@@ -1492,8 +1506,20 @@ const trBundle = mergeTranslations(
trTranslations as unknown as Partial<TranslationBundle>
);
export function getTranslation(lang: Language): TranslationBundle {
if (lang === 'ar') return arBundle as TranslationBundle;
if (lang === 'tr') return trBundle as TranslationBundle;
return translations[lang] as TranslationBundle;
function withMenuText(lang: Language, bundle: TranslationBundle): TranslationBundle {
const baseDescriptions = { ...bundle.menuDescriptions };
const hiDescriptions =
lang === 'hi' ? { ...baseDescriptions, ...HINDI_MENU_DESCRIPTIONS } : baseDescriptions;
const supplement = MENU_DESCRIPTION_SUPPLEMENT[lang];
const menuDescriptions = supplement
? { ...hiDescriptions, ...supplement }
: hiDescriptions;
return { ...bundle, menuDescriptions };
}
export function getTranslation(lang: Language): TranslationBundle {
if (lang === 'ar') return withMenuText(lang, arBundle as TranslationBundle);
if (lang === 'tr') return withMenuText(lang, trBundle as TranslationBundle);
return withMenuText(lang, translations[lang] as TranslationBundle);
}
+1 -1
View File
@@ -19,7 +19,7 @@ const JPEG_QUALITY = 88;
/** Menu ids that share another dish's ftp photo (main bilder-bp folder) */
const FTP_ALIASES: Record<string, string> = {
'tikka-boti': 'chicken-tikka',
};
/** Menu id → folder name under ftp-images/bilder-bp/sweets/ */