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:
co-authored by
Cursor
parent
ab5425663f
commit
eccbb5d79d
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user