perf: optimize framer-motion bundle size and memoize dictionary fetching
- Wrap getDictionary with React cache to prevent redundant JSON parsing on the server - Create MotionProvider to globally initialize framer-motion LazyMotion with domAnimation - Replace standard 'motion' imports with 'm' across all animated client components (HomeClient, Navbar, GaleriClient, SSSClient, card, text-reveal) to drastically reduce First Load JS size
This commit is contained in:
+18
-18
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion, type Variants } from "framer-motion";
|
import { m, type Variants } from "framer-motion";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Utensils, Umbrella, Home, Wind, Hotel, Star, MapPin, ArrowRight } from "lucide-react";
|
import { Utensils, Umbrella, Home, Wind, Hotel, Star, MapPin, ArrowRight } from "lucide-react";
|
||||||
import { Button } from "@/app/components/ui/button";
|
import { Button } from "@/app/components/ui/button";
|
||||||
@@ -128,30 +128,30 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
|
|
||||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
||||||
{/* Eyebrow */}
|
{/* Eyebrow */}
|
||||||
<motion.p
|
<m.p
|
||||||
initial={{ opacity: 0, y: 12 }}
|
initial={{ opacity: 0, y: 12 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.1 }}
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
className="text-[10px] uppercase tracking-[0.3em] text-[var(--color-moy-gold)] mb-8 font-medium"
|
className="text-[10px] uppercase tracking-[0.3em] text-[var(--color-moy-gold)] mb-8 font-medium"
|
||||||
>
|
>
|
||||||
{dict.hero.eyebrow}
|
{dict.hero.eyebrow}
|
||||||
</motion.p>
|
</m.p>
|
||||||
|
|
||||||
{/* Main heading */}
|
{/* Main heading */}
|
||||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-serif text-white mb-8 leading-[0.95] tracking-tight">
|
<h1 className="text-5xl md:text-7xl lg:text-8xl font-serif text-white mb-8 leading-[0.95] tracking-tight">
|
||||||
<TextReveal text={dict.hero.title} delay={0.2} />
|
<TextReveal text={dict.hero.title} delay={0.2} />
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<motion.p
|
<m.p
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ duration: 0.8, delay: 0.9 }}
|
transition={{ duration: 0.8, delay: 0.9 }}
|
||||||
className="text-base md:text-lg text-gray-300/90 mb-12 font-light max-w-xl mx-auto leading-relaxed"
|
className="text-base md:text-lg text-gray-300/90 mb-12 font-light max-w-xl mx-auto leading-relaxed"
|
||||||
>
|
>
|
||||||
{dict.hero.subtitle}
|
{dict.hero.subtitle}
|
||||||
</motion.p>
|
</m.p>
|
||||||
|
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ opacity: 0, y: 16 }}
|
initial={{ opacity: 0, y: 16 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.7, delay: 1.1 }}
|
transition={{ duration: 0.7, delay: 1.1 }}
|
||||||
@@ -168,23 +168,23 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
<ArrowRight size={14} className="group-hover:translate-x-1 transition-transform" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</m.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scroll indicator */}
|
{/* Scroll indicator */}
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ delay: 1.6, duration: 0.6 }}
|
transition={{ delay: 1.6, duration: 0.6 }}
|
||||||
className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2"
|
className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2"
|
||||||
>
|
>
|
||||||
<span className="text-[9px] uppercase tracking-[0.25em] text-white/50">{dict.hero.scroll}</span>
|
<span className="text-[9px] uppercase tracking-[0.25em] text-white/50">{dict.hero.scroll}</span>
|
||||||
<motion.div
|
<m.div
|
||||||
animate={{ y: [0, 8, 0] }}
|
animate={{ y: [0, 8, 0] }}
|
||||||
transition={{ duration: 1.6, repeat: Infinity, ease: "easeInOut" }}
|
transition={{ duration: 1.6, repeat: Infinity, ease: "easeInOut" }}
|
||||||
className="w-[1px] h-8 bg-gradient-to-b from-white/50 to-transparent"
|
className="w-[1px] h-8 bg-gradient-to-b from-white/50 to-transparent"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</m.div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── Marquee Strip ── */}
|
{/* ── Marquee Strip ── */}
|
||||||
@@ -203,7 +203,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
<div className="max-w-5xl mx-auto px-6">
|
<div className="max-w-5xl mx-auto px-6">
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-12 md:gap-6">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-12 md:gap-6">
|
||||||
{STATS.map((s, i) => (
|
{STATS.map((s, i) => (
|
||||||
<motion.div
|
<m.div
|
||||||
key={s.label}
|
key={s.label}
|
||||||
custom={i}
|
custom={i}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
@@ -218,7 +218,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
<p className="text-[10px] uppercase tracking-[0.2em] text-gray-500 mt-3">
|
<p className="text-[10px] uppercase tracking-[0.2em] text-gray-500 mt-3">
|
||||||
{s.label}
|
{s.label}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</m.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,7 +236,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-5">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-5">
|
||||||
{SERVICES.map((service, i) => (
|
{SERVICES.map((service, i) => (
|
||||||
<motion.div
|
<m.div
|
||||||
key={service.id}
|
key={service.id}
|
||||||
custom={i}
|
custom={i}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
@@ -263,7 +263,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
{service.desc}
|
{service.desc}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</m.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,7 +281,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
|
|
||||||
<div className="space-y-20">
|
<div className="space-y-20">
|
||||||
{VENUES.map((venue, i) => (
|
{VENUES.map((venue, i) => (
|
||||||
<motion.div
|
<m.div
|
||||||
key={venue.id}
|
key={venue.id}
|
||||||
initial={{ opacity: 0, y: 40 }}
|
initial={{ opacity: 0, y: 40 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
@@ -321,7 +321,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
<ArrowRight size={12} className="group-hover:translate-x-1 transition-transform" />
|
<ArrowRight size={12} className="group-hover:translate-x-1 transition-transform" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</m.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,7 +341,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
{TESTIMONIALS.map((t, i) => (
|
{TESTIMONIALS.map((t, i) => (
|
||||||
<motion.div
|
<m.div
|
||||||
key={i}
|
key={i}
|
||||||
custom={i}
|
custom={i}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
@@ -371,7 +371,7 @@ export default function HomeClient({ lang, dict }: { lang: string; dict: any })
|
|||||||
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mt-0.5">{t.origin}</p>
|
<p className="text-[10px] uppercase tracking-[0.15em] text-gray-500 mt-0.5">{t.origin}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</m.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { m, AnimatePresence } from "framer-motion";
|
||||||
import { X, ZoomIn } from "lucide-react";
|
import { X, ZoomIn } from "lucide-react";
|
||||||
import { Badge } from "@/app/components/ui/badge";
|
import { Badge } from "@/app/components/ui/badge";
|
||||||
|
|
||||||
@@ -60,13 +60,13 @@ export default function GaleriClient({ lang, dict }: { lang: string; dict: any }
|
|||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 pb-28">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 pb-28">
|
||||||
<motion.div
|
<m.div
|
||||||
layout
|
layout
|
||||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||||
>
|
>
|
||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{filtered.map((img) => (
|
{filtered.map((img) => (
|
||||||
<motion.div
|
<m.div
|
||||||
key={img.src}
|
key={img.src}
|
||||||
layout
|
layout
|
||||||
initial={{ opacity: 0, scale: 0.94 }}
|
initial={{ opacity: 0, scale: 0.94 }}
|
||||||
@@ -97,16 +97,16 @@ export default function GaleriClient({ lang, dict }: { lang: string; dict: any }
|
|||||||
>
|
>
|
||||||
{img.cat}
|
{img.cat}
|
||||||
</Badge>
|
</Badge>
|
||||||
</motion.div>
|
</m.div>
|
||||||
))}
|
))}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</motion.div>
|
</m.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lightbox */}
|
{/* Lightbox */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{lightbox && (
|
{lightbox && (
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ opacity: 0 }}
|
||||||
@@ -119,7 +119,7 @@ export default function GaleriClient({ lang, dict }: { lang: string; dict: any }
|
|||||||
>
|
>
|
||||||
<X size={28} />
|
<X size={28} />
|
||||||
</button>
|
</button>
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ scale: 0.9, opacity: 0 }}
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
exit={{ scale: 0.9, opacity: 0 }}
|
exit={{ scale: 0.9, opacity: 0 }}
|
||||||
@@ -135,8 +135,8 @@ export default function GaleriClient({ lang, dict }: { lang: string; dict: any }
|
|||||||
className="w-full h-auto max-h-[80vh] object-contain"
|
className="w-full h-auto max-h-[80vh] object-contain"
|
||||||
/>
|
/>
|
||||||
<p className="text-center text-white font-serif text-xl mt-4">{lightbox.title}</p>
|
<p className="text-center text-white font-serif text-xl mt-4">{lightbox.title}</p>
|
||||||
</motion.div>
|
</m.div>
|
||||||
</motion.div>
|
</m.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|||||||
import { Cormorant_Garamond, Montserrat } from "next/font/google";
|
import { Cormorant_Garamond, Montserrat } from "next/font/google";
|
||||||
import Navbar from "../components/Navbar";
|
import Navbar from "../components/Navbar";
|
||||||
import Footer from "../components/Footer";
|
import Footer from "../components/Footer";
|
||||||
|
import { MotionProvider } from "../components/MotionProvider";
|
||||||
import "../globals.css";
|
import "../globals.css";
|
||||||
import { Locale, getDictionary } from "../dictionaries";
|
import { Locale, getDictionary } from "../dictionaries";
|
||||||
|
|
||||||
@@ -66,9 +67,11 @@ export default async function RootLayout({
|
|||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body className="min-h-full flex flex-col">
|
<body className="min-h-full flex flex-col">
|
||||||
|
<MotionProvider>
|
||||||
<Navbar lang={locale} dict={dict.navigation} />
|
<Navbar lang={locale} dict={dict.navigation} />
|
||||||
<main className="flex-1">{children}</main>
|
<main className="flex-1">{children}</main>
|
||||||
<Footer lang={locale} dict={dict.footer} />
|
<Footer lang={locale} dict={dict.footer} />
|
||||||
|
</MotionProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { m, AnimatePresence } from "framer-motion";
|
||||||
import { Plus, Minus } from "lucide-react";
|
import { Plus, Minus } from "lucide-react";
|
||||||
import { SectionHeader } from "@/app/components/ui/section-header";
|
import { SectionHeader } from "@/app/components/ui/section-header";
|
||||||
import { Badge } from "@/app/components/ui/badge";
|
import { Badge } from "@/app/components/ui/badge";
|
||||||
@@ -26,7 +26,7 @@ function FaqItem({ q, a }: { q: string; a: string }) {
|
|||||||
</button>
|
</button>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{open && (
|
{open && (
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ height: 0, opacity: 0 }}
|
initial={{ height: 0, opacity: 0 }}
|
||||||
animate={{ height: "auto", opacity: 1 }}
|
animate={{ height: "auto", opacity: 1 }}
|
||||||
exit={{ height: 0, opacity: 0 }}
|
exit={{ height: 0, opacity: 0 }}
|
||||||
@@ -34,7 +34,7 @@ function FaqItem({ q, a }: { q: string; a: string }) {
|
|||||||
className="overflow-hidden"
|
className="overflow-hidden"
|
||||||
>
|
>
|
||||||
<p className="text-gray-500 text-sm leading-relaxed pb-5">{a}</p>
|
<p className="text-gray-500 text-sm leading-relaxed pb-5">{a}</p>
|
||||||
</motion.div>
|
</m.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { LazyMotion, domAnimation } from "framer-motion";
|
||||||
|
|
||||||
|
export function MotionProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<LazyMotion features={domAnimation}>
|
||||||
|
{children}
|
||||||
|
</LazyMotion>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import Link from "next/link";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { Menu, X, Globe } from "lucide-react";
|
import { Menu, X, Globe } from "lucide-react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { m, AnimatePresence } from "framer-motion";
|
||||||
import { Locale } from "../dictionaries";
|
import { Locale } from "../dictionaries";
|
||||||
|
|
||||||
export default function Navbar({ lang, dict }: { lang: Locale; dict: any }) {
|
export default function Navbar({ lang, dict }: { lang: Locale; dict: any }) {
|
||||||
@@ -91,7 +91,7 @@ export default function Navbar({ lang, dict }: { lang: Locale; dict: any }) {
|
|||||||
{/* Mobile Nav */}
|
{/* Mobile Nav */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<motion.div
|
<m.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
exit={{ opacity: 0, y: -20 }}
|
exit={{ opacity: 0, y: -20 }}
|
||||||
@@ -114,7 +114,7 @@ export default function Navbar({ lang, dict }: { lang: Locale; dict: any }) {
|
|||||||
<Link href={switchLanguage('en')} onClick={() => setMobileMenuOpen(false)} className={`text-sm ${lang === 'en' ? 'text-[var(--color-moy-gold)] font-bold' : ''}`}>English</Link>
|
<Link href={switchLanguage('en')} onClick={() => setMobileMenuOpen(false)} className={`text-sm ${lang === 'en' ? 'text-[var(--color-moy-gold)] font-bold' : ''}`}>English</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</m.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import { motion } from "framer-motion";
|
import { m } from "framer-motion";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
/* ── Base Card ── */
|
/* ── Base Card ── */
|
||||||
@@ -15,7 +15,7 @@ interface CardProps {
|
|||||||
|
|
||||||
export function Card({ children, hover = true, glass = false, className }: CardProps) {
|
export function Card({ children, hover = true, glass = false, className }: CardProps) {
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<m.div
|
||||||
whileHover={hover ? { y: -6 } : undefined}
|
whileHover={hover ? { y: -6 } : undefined}
|
||||||
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
|
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -27,7 +27,7 @@ export function Card({ children, hover = true, glass = false, className }: CardP
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</motion.div>
|
</m.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { motion, useInView } from "framer-motion";
|
import { m, useInView } from "framer-motion";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export function TextReveal({
|
|||||||
return (
|
return (
|
||||||
<span ref={ref} className={cn("inline", className)}>
|
<span ref={ref} className={cn("inline", className)}>
|
||||||
{words.map((word, i) => (
|
{words.map((word, i) => (
|
||||||
<motion.span
|
<m.span
|
||||||
key={i}
|
key={i}
|
||||||
className="inline-block mr-[0.28em] last:mr-0"
|
className="inline-block mr-[0.28em] last:mr-0"
|
||||||
initial={{ opacity: 0, y: 22, filter: "blur(4px)" }}
|
initial={{ opacity: 0, y: 22, filter: "blur(4px)" }}
|
||||||
@@ -42,7 +42,7 @@ export function TextReveal({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{word}
|
{word}
|
||||||
</motion.span>
|
</m.span>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,5 @@
|
|||||||
import 'server-only';
|
import 'server-only';
|
||||||
|
import { cache } from 'react';
|
||||||
|
|
||||||
const dictionaries = {
|
const dictionaries = {
|
||||||
en: () => import('../dictionaries/en.json').then((module) => module.default),
|
en: () => import('../dictionaries/en.json').then((module) => module.default),
|
||||||
@@ -11,6 +12,6 @@ export const i18n = {
|
|||||||
locales: ['tr', 'en'],
|
locales: ['tr', 'en'],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const getDictionary = async (locale: Locale) => {
|
export const getDictionary = cache(async (locale: Locale) => {
|
||||||
return dictionaries[locale]?.() ?? dictionaries.tr();
|
return dictionaries[locale]?.() ?? dictionaries.tr();
|
||||||
};
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user