Replace entire repo content with updated code from /root/shahikitchen-website/
This commit is contained in:
@@ -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
@@ -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>
|
||||
|
||||
@@ -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 & 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 & 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
@@ -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
@@ -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)}
|
||||
|
||||
@@ -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
@@ -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
@@ -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">
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]" />
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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: 'قوائم مخصصة متاحة — تواصل معنا لتخصيص مناسبتك.',
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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/ */
|
||||
|
||||
Reference in New Issue
Block a user