Add Saleh Kargar factory website with FA/AR i18n.

Clone of salehkargar.ir as a Next.js site with localized pages, SEO, and product gallery.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-14 13:35:07 +03:30
co-authored by Cursor
parent ab5425663f
commit eccbb5d79d
111 changed files with 4456 additions and 114 deletions
+72
View File
@@ -0,0 +1,72 @@
import type { Metadata } from "next";
import Image from "next/image";
import { getTranslations, setRequestLocale } from "next-intl/server";
import PageShell from "@/components/PageShell";
import { buildMetadata } from "@/lib/seo";
const sectionMeta = [
{ key: "history", image: "/images/about-lime/1.jpg" },
{ key: "traditional", image: "/images/about-lime/2.jpg" },
{ key: "difference", image: "/images/about-lime/3.jpg" },
{ key: "uses", image: "/images/products/building-lime.jpg" },
] as const;
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return buildMetadata({
title: t("aboutLimeTitle"),
description: t("aboutLimeDescription"),
path: `/${locale}/aboutlime`,
locale,
});
}
export default async function AboutLimePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("AboutLimePage");
return (
<PageShell title={t("title")}>
<section className="section content-page">
<div className="container">
{sectionMeta.map((section, index) => (
<article
key={section.key}
className={`content-block${index % 2 === 1 ? " is-reverse" : ""}`}
>
<div className="content-block-media shadow-box">
<Image
src={section.image}
alt={t(`sections.${section.key}.title`)}
width={700}
height={520}
/>
</div>
<div className="content-block-text">
<h2>{t(`sections.${section.key}.title`)}</h2>
<Image
src="/images/infoitems/line.png"
alt=""
width={151}
height={5}
className="section-line"
/>
{t(`sections.${section.key}.body`)
.split("\n")
.map((para) =>
para.trim() ? <p key={para.slice(0, 24)}>{para}</p> : null,
)}
</div>
</article>
))}
</div>
</section>
</PageShell>
);
}
+74
View File
@@ -0,0 +1,74 @@
import type { Metadata } from "next";
import Image from "next/image";
import { getTranslations, setRequestLocale } from "next-intl/server";
import PageShell from "@/components/PageShell";
import { buildMetadata } from "@/lib/seo";
const sectionMeta = [
{ key: "history", image: "/images/about-chalk/1.jpg" },
{ key: "method", image: "/images/about-chalk/2.jpg" },
{ key: "strength", image: "/images/about-chalk/3.jpg" },
{ key: "pros", image: "/images/about-chalk/4.jpg" },
{ key: "cons", image: "/images/about-chalk/1.jpg" },
{ key: "super", image: "/images/products/super-chalk.jpg" },
] as const;
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return buildMetadata({
title: t("aboutChalkTitle"),
description: t("aboutChalkDescription"),
path: `/${locale}/aboutsuperchalk`,
locale,
});
}
export default async function AboutChalkPage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("AboutChalkPage");
return (
<PageShell title={t("title")}>
<section className="section content-page">
<div className="container">
{sectionMeta.map((section, index) => (
<article
key={section.key}
className={`content-block${index % 2 === 1 ? " is-reverse" : ""}`}
>
<div className="content-block-media shadow-box">
<Image
src={section.image}
alt={t(`sections.${section.key}.title`)}
width={700}
height={520}
/>
</div>
<div className="content-block-text">
<h2>{t(`sections.${section.key}.title`)}</h2>
<Image
src="/images/infoitems/line.png"
alt=""
width={151}
height={5}
className="section-line"
/>
{t(`sections.${section.key}.body`)
.split("\n")
.map((para) =>
para.trim() ? <p key={para.slice(0, 24)}>{para}</p> : null,
)}
</div>
</article>
))}
</div>
</section>
</PageShell>
);
}
+65
View File
@@ -0,0 +1,65 @@
import type { Metadata } from "next";
import Image from "next/image";
import { getTranslations, setRequestLocale } from "next-intl/server";
import PageShell from "@/components/PageShell";
import { buildMetadata } from "@/lib/seo";
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return buildMetadata({
title: t("aboutUsTitle"),
description: t("aboutUsDescription"),
path: `/${locale}/aboutus`,
locale,
});
}
export default async function AboutUsPage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("AboutUsPage");
return (
<PageShell title={t("title")}>
<section className="section about-us-page">
<div className="container about-us-grid">
<div className="about-us-media shadow-box">
<Image
src="/images/about-us/1.jpg"
alt={t("imageAlt")}
width={900}
height={600}
priority
sizes="(max-width: 1024px) 100vw, 55vw"
/>
</div>
<div className="about-us-copy">
<Image
src="/images/about-us/2.png"
alt=""
width={90}
height={90}
className="about-us-icon"
/>
<h2>{t("title")}</h2>
<Image
src="/images/infoitems/line.png"
alt=""
width={151}
height={5}
className="section-line"
/>
<p>{t("p1")}</p>
<p>{t("p2")}</p>
<p>{t("p3")}</p>
</div>
</div>
</section>
</PageShell>
);
}
+49
View File
@@ -0,0 +1,49 @@
import type { Metadata } from "next";
import Image from "next/image";
import { getTranslations, setRequestLocale } from "next-intl/server";
import PageShell from "@/components/PageShell";
import { galleryImages } from "@/data/pages";
import { buildMetadata } from "@/lib/seo";
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return buildMetadata({
title: t("galleryTitle"),
description: t("galleryDescription"),
path: `/${locale}/gallery`,
locale,
});
}
export default async function GalleryPage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("GalleryPage");
return (
<PageShell title={t("title")}>
<section className="section">
<div className="container">
<div className="gallery-grid">
{galleryImages.map((item) => (
<figure key={item.id} className="gallery-item">
<Image
src={item.src}
alt={t("itemAlt", { n: item.id })}
width={700}
height={500}
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
/>
</figure>
))}
</div>
</div>
</section>
</PageShell>
);
}
+112
View File
@@ -0,0 +1,112 @@
import type { Metadata, Viewport } from "next";
import { hasLocale, NextIntlClientProvider } from "next-intl";
import { getMessages, getTranslations, setRequestLocale } from "next-intl/server";
import { Vazirmatn } from "next/font/google";
import { notFound } from "next/navigation";
import JsonLd, {
localBusinessJsonLd,
organizationJsonLd,
} from "@/components/JsonLd";
import { routing } from "@/i18n/routing";
import { siteConfig } from "@/data/seo";
import "../globals.css";
const vazirmatn = Vazirmatn({
subsets: ["arabic"],
weight: ["300", "400", "500", "700", "800"],
variable: "--font-vazirmatn",
display: "swap",
});
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
themeColor: "#000000",
};
type Props = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return {
metadataBase: new URL(siteConfig.url),
title: {
default: t("siteName"),
template: `%s | ${t("siteName")}`,
},
description: t("homeDescription"),
applicationName: siteConfig.shortName,
icons: {
icon: "/favicon.png",
apple: "/favicon.png",
},
openGraph: {
type: "website",
locale: locale === "ar" ? "ar_SA" : "fa_IR",
url: `${siteConfig.url}/${locale}`,
siteName: t("siteName"),
title: t("siteName"),
description: t("homeDescription"),
images: [
{
url: siteConfig.ogImage,
width: 1920,
height: 680,
alt: t("siteName"),
},
],
},
twitter: {
card: "summary_large_image",
title: t("siteName"),
description: t("homeDescription"),
images: [siteConfig.ogImage],
},
alternates: {
canonical: `/${locale}`,
languages: {
"fa-IR": "/fa",
"ar": "/ar",
"x-default": "/fa",
},
},
robots: {
index: true,
follow: true,
},
};
}
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
setRequestLocale(locale);
const messages = await getMessages();
return (
<html lang={locale} dir="rtl" className={`${vazirmatn.variable} h-full`}>
<body className={`${vazirmatn.className} min-h-full antialiased`}>
<NextIntlClientProvider messages={messages}>
<JsonLd data={[organizationJsonLd(), localBusinessJsonLd()]} />
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
+47
View File
@@ -0,0 +1,47 @@
import type { Metadata } from "next";
import { getTranslations, setRequestLocale } from "next-intl/server";
import AboutTabs from "@/components/AboutTabs";
import Footer from "@/components/Footer";
import Header from "@/components/Header";
import Hero from "@/components/Hero";
import Location from "@/components/Location";
import PowderFeatures from "@/components/PowderFeatures";
import Productions from "@/components/Productions";
import ProductsCarousel from "@/components/ProductsCarousel";
import { buildMetadata } from "@/lib/seo";
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return buildMetadata({
title: t("homeTitle"),
description: t("homeDescription"),
path: `/${locale}`,
locale,
});
}
export default async function HomePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
return (
<>
<Header />
<main>
<Hero />
<Productions />
<PowderFeatures />
<AboutTabs />
<ProductsCarousel />
<Location />
</main>
<Footer />
</>
);
}
+65
View File
@@ -0,0 +1,65 @@
import type { Metadata } from "next";
import { getTranslations, setRequestLocale } from "next-intl/server";
import { notFound } from "next/navigation";
import JsonLd, { breadcrumbJsonLd, productJsonLd } from "@/components/JsonLd";
import PageShell from "@/components/PageShell";
import ProductDetail from "@/components/ProductDetail";
import { getProductBySlug, getProducts } from "@/data/products";
import { buildMetadata } from "@/lib/seo";
type Props = {
params: Promise<{ locale: string; slug: string }>;
};
export async function generateStaticParams() {
return getProducts().map((product) => ({ slug: product.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale, slug } = await params;
const product = getProductBySlug(slug);
const t = await getTranslations({ locale, namespace: "Products" });
if (!product) return { title: t("notFound") };
return buildMetadata({
title: t(`items.${product.slug}.title`),
description: t(`items.${product.slug}.description`),
path: `/${locale}/products/${product.slug}`,
image: product.image,
locale,
});
}
export default async function ProductDetailPage({ params }: Props) {
const { locale, slug } = await params;
setRequestLocale(locale);
const product = getProductBySlug(slug);
const t = await getTranslations("Products");
const tCommon = await getTranslations("Common");
if (!product) notFound();
const title = t(`items.${product.slug}.title`);
return (
<PageShell title={t("detailsTitle")}>
<JsonLd
data={[
productJsonLd({
...product,
title,
description: t(`items.${product.slug}.description`),
}),
breadcrumbJsonLd([
{ name: tCommon("home"), path: `/${locale}` },
{ name: tCommon("products"), path: `/${locale}/products` },
{
name: title,
path: `/${locale}/products/${product.slug}`,
},
]),
]}
/>
<ProductDetail product={product} />
</PageShell>
);
}
+38
View File
@@ -0,0 +1,38 @@
import type { Metadata } from "next";
import { getTranslations, setRequestLocale } from "next-intl/server";
import PageShell from "@/components/PageShell";
import ProductGrid from "@/components/ProductGrid";
import { getProducts } from "@/data/products";
import { buildMetadata } from "@/lib/seo";
type Props = {
params: Promise<{ locale: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "Meta" });
return buildMetadata({
title: t("productsTitle"),
description: t("productsDescription"),
path: `/${locale}/products`,
locale,
});
}
export default async function ProductsPage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations("Products");
const products = getProducts();
return (
<PageShell title={t("pageTitle")}>
<section className="section">
<div className="container">
<ProductGrid products={products} />
</div>
</section>
</PageShell>
);
}