diff --git a/middleware.ts b/middleware.ts
index d09705a..4771067 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,40 +1,45 @@
-import createMiddleware from "next-intl/middleware";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
-import { routing } from "./src/i18n/routing";
+import { SUPPORTED_LOCALES, DEFAULT_LOCALE, LOCALE_COOKIE, getPathWithoutLocale, buildLocalePath, isValidLocale } from "@/lib/i18n/locales";
+import type { Locale } from "@/lib/i18n/locales";
-const oldSerbianPaths = ["", "products", "about", "contact", "checkout"];
+const OLD_SERBIAN_PATHS = ["products", "about", "contact", "checkout"];
+
+function detectLocale(cookieLocale: string | undefined, acceptLanguage: string): Locale {
+ if (cookieLocale && isValidLocale(cookieLocale)) {
+ return cookieLocale;
+ }
+ if (acceptLanguage.includes("en")) {
+ return "en";
+ }
+ return DEFAULT_LOCALE;
+}
export default function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
+ const cookieLocale = request.cookies.get(LOCALE_COOKIE)?.value;
+ const acceptLanguage = request.headers.get("accept-language") || "";
- const isOldSerbianPath = oldSerbianPaths.some((path) => {
- if (path === "") {
- return pathname === "/";
- }
- return pathname === `/${path}` || pathname.startsWith(`/${path}/`);
- });
-
- const hasLocalePrefix = routing.locales.some(
- (locale) => pathname === `/${locale}` || pathname.startsWith(`/${locale}/`)
- );
-
- if (isOldSerbianPath && !hasLocalePrefix) {
- const newPathname = pathname === "/"
- ? "/sr"
- : `/sr${pathname}`;
-
+ if (pathname === "/" || pathname === "") {
+ const locale = detectLocale(cookieLocale, acceptLanguage);
const url = request.nextUrl.clone();
- url.pathname = newPathname;
-
+ url.pathname = buildLocalePath(locale, "/");
return NextResponse.redirect(url, 301);
}
- const intlMiddleware = createMiddleware({
- ...routing,
- });
+ const isOldSerbianPath = OLD_SERBIAN_PATHS.some(
+ (path) => pathname === `/${path}` || pathname.startsWith(`/${path}/`)
+ );
- return intlMiddleware(request);
+ if (isOldSerbianPath) {
+ const locale = detectLocale(cookieLocale, acceptLanguage);
+ const newPath = buildLocalePath(locale, pathname);
+ const url = request.nextUrl.clone();
+ url.pathname = newPath;
+ return NextResponse.redirect(url, 301);
+ }
+
+ return NextResponse.next();
}
export const config = {
@@ -43,4 +48,4 @@ export const config = {
"/(sr|en|de|fr)/:path*",
"/((?!api|_next|_vercel|.*\\..*).*)",
],
-};
+};
\ No newline at end of file
diff --git a/public/favicon.png b/public/favicon.png
new file mode 100644
index 0000000..51b9bd6
Binary files /dev/null and b/public/favicon.png differ
diff --git a/public/icon.png b/public/icon.png
new file mode 100644
index 0000000..51b9bd6
Binary files /dev/null and b/public/icon.png differ
diff --git a/src/app/[locale]/about/page.tsx b/src/app/[locale]/about/page.tsx
index ae786ab..0976348 100644
--- a/src/app/[locale]/about/page.tsx
+++ b/src/app/[locale]/about/page.tsx
@@ -1,6 +1,8 @@
import { getTranslations, setRequestLocale } from "next-intl/server";
import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer";
+import { getPageMetadata } from "@/lib/i18n/pageMetadata";
+import { isValidLocale, DEFAULT_LOCALE, type Locale } from "@/lib/i18n/locales";
interface AboutPageProps {
params: Promise<{ locale: string }>;
@@ -8,19 +10,19 @@ interface AboutPageProps {
export async function generateMetadata({ params }: AboutPageProps) {
const { locale } = await params;
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ const metadata = getPageMetadata(validLocale as Locale);
return {
- title: locale === "sr"
- ? "O nama - ManoonOils"
- : "About - ManoonOils",
- description: locale === "sr"
- ? "Saznajte više o ManoonOils - naša priča, misija i posvećenost prirodnoj lepoti."
- : "Learn more about ManoonOils - our story, mission, and commitment to natural beauty.",
+ title: metadata.about.title,
+ description: metadata.about.description,
};
}
export default async function AboutPage({ params }: AboutPageProps) {
const { locale } = await params;
- setRequestLocale(locale);
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ const metadata = getPageMetadata(validLocale as Locale);
+ setRequestLocale(validLocale);
const t = await getTranslations("About");
return (
@@ -43,7 +45,7 @@ export default async function AboutPage({ params }: AboutPageProps) {
diff --git a/src/app/[locale]/checkout/page.tsx b/src/app/[locale]/checkout/page.tsx
index 957b3f3..6b3827a 100644
--- a/src/app/[locale]/checkout/page.tsx
+++ b/src/app/[locale]/checkout/page.tsx
@@ -212,7 +212,7 @@ export default function CheckoutPage() {
return (
<>
-
+
@@ -377,7 +377,7 @@ export default function CheckoutPage() {
-
+
>
);
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index e6b0a8a..0f2ad3b 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -1,9 +1,35 @@
+import { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getMessages, setRequestLocale } from "next-intl/server";
-import { routing } from "@/i18n/routing";
+import { SUPPORTED_LOCALES, DEFAULT_LOCALE, isValidLocale } from "@/lib/i18n/locales";
+
+const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
export function generateStaticParams() {
- return routing.locales.map((locale) => ({ locale }));
+ return SUPPORTED_LOCALES.map((locale) => ({ locale }));
+}
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ locale: string }>;
+}): Promise
{
+ const { locale } = await params;
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ const localePrefix = validLocale === DEFAULT_LOCALE ? "" : `/${validLocale}`;
+
+ const languages: Record = {};
+ for (const loc of SUPPORTED_LOCALES) {
+ const prefix = loc === DEFAULT_LOCALE ? "" : `/${loc}`;
+ languages[loc] = `${baseUrl}${prefix}`;
+ }
+
+ return {
+ alternates: {
+ canonical: `${baseUrl}${localePrefix}`,
+ languages,
+ },
+ };
}
export default async function LocaleLayout({
@@ -22,4 +48,4 @@ export default async function LocaleLayout({
{children}
);
-}
\ No newline at end of file
+}
diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx
index 7086bee..177f7d2 100644
--- a/src/app/[locale]/page.tsx
+++ b/src/app/[locale]/page.tsx
@@ -10,30 +10,32 @@ import ProductReviews from "@/components/product/ProductReviews";
import BeforeAfterGallery from "@/components/home/BeforeAfterGallery";
import ProblemSection from "@/components/home/ProblemSection";
import HowItWorks from "@/components/home/HowItWorks";
+import { getPageMetadata } from "@/lib/i18n/pageMetadata";
+import { isValidLocale, DEFAULT_LOCALE, getSaleorLocale, type Locale } from "@/lib/i18n/locales";
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
- setRequestLocale(locale);
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ const metadata = getPageMetadata(validLocale as Locale);
+ setRequestLocale(validLocale);
return {
- title: locale === "sr"
- ? "ManoonOils - Premium prirodna ulja za negu kose i kože"
- : "ManoonOils - Premium Natural Oils for Hair & Skin",
- description: locale === "sr"
- ? "Otkrijte našu premium kolekciju prirodnih ulja za negu kose i kože."
- : "Discover our premium collection of natural oils for hair and skin care.",
+ title: metadata.home.title,
+ description: metadata.home.description,
};
}
export default async function Homepage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
- setRequestLocale(locale);
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ setRequestLocale(validLocale);
const t = await getTranslations("Home");
const tBenefits = await getTranslations("Benefits");
+ const metadata = getPageMetadata(validLocale as Locale);
- const productLocale = locale === "sr" ? "SR" : "EN";
+ const saleorLocale = getSaleorLocale(validLocale as Locale);
let products: any[] = [];
try {
- products = await getProducts(productLocale);
+ products = await getProducts(saleorLocale);
} catch (e) {
console.log("Failed to fetch products during build");
}
@@ -41,7 +43,7 @@ export default async function Homepage({ params }: { params: Promise<{ locale: s
const featuredProducts = products?.slice(0, 4) || [];
const hasProducts = featuredProducts.length > 0;
- const basePath = `/${locale}`;
+ const basePath = `/${validLocale}`;
return (
<>
@@ -78,7 +80,7 @@ export default async function Homepage({ params }: { params: Promise<{ locale: s
{featuredProducts.map((product, index) => (
-
+
))}
@@ -122,7 +124,7 @@ export default async function Homepage({ params }: { params: Promise<{ locale: s
@@ -204,14 +206,14 @@ export default async function Homepage({ params }: { params: Promise<{ locale: s
+ type="submit"
+ className="px-8 bg-white text-black text-sm uppercase tracking-[0.1em] font-medium hover:bg-white/90 transition-colors whitespace-nowrap flex-shrink-0 rounded-b sm:rounded-r sm:rounded-bl-none"
+ >
+ {t("subscribe")}
+
diff --git a/src/app/[locale]/products/[slug]/page.tsx b/src/app/[locale]/products/[slug]/page.tsx
index f0656ab..5bbc541 100644
--- a/src/app/[locale]/products/[slug]/page.tsx
+++ b/src/app/[locale]/products/[slug]/page.tsx
@@ -5,6 +5,8 @@ import Footer from "@/components/layout/Footer";
import ProductDetail from "@/components/product/ProductDetail";
import type { Product } from "@/types/saleor";
import { routing } from "@/i18n/routing";
+import { getPageMetadata } from "@/lib/i18n/pageMetadata";
+import { isValidLocale, DEFAULT_LOCALE, getSaleorLocale, type Locale } from "@/lib/i18n/locales";
interface ProductPageProps {
params: Promise<{ locale: string; slug: string }>;
@@ -16,8 +18,8 @@ export async function generateStaticParams() {
for (const locale of locales) {
try {
- const productLocale = locale === "sr" ? "SR" : "EN";
- const products = await getProducts(productLocale, 100);
+ const saleorLocale = locale === "sr" ? "SR" : "EN";
+ const products = await getProducts(saleorLocale, 100);
products.forEach((product: Product) => {
params.push({ locale, slug: product.slug });
});
@@ -29,16 +31,18 @@ export async function generateStaticParams() {
export async function generateMetadata({ params }: ProductPageProps) {
const { locale, slug } = await params;
- const productLocale = locale === "sr" ? "SR" : "EN";
- const product = await getProductBySlug(slug, productLocale);
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ const metadata = getPageMetadata(validLocale as Locale);
+ const saleorLocale = validLocale === "sr" ? "SR" : "EN";
+ const product = await getProductBySlug(slug, saleorLocale);
if (!product) {
return {
- title: locale === "sr" ? "Proizvod nije pronađen" : "Product not found",
+ title: metadata.productNotFound,
};
}
- const localized = getLocalizedProduct(product, productLocale);
+ const localized = getLocalizedProduct(product, saleorLocale);
return {
title: localized.name,
@@ -48,12 +52,13 @@ export async function generateMetadata({ params }: ProductPageProps) {
export default async function ProductPage({ params }: ProductPageProps) {
const { locale, slug } = await params;
- setRequestLocale(locale);
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ setRequestLocale(validLocale);
const t = await getTranslations("Product");
- const productLocale = locale === "sr" ? "SR" : "EN";
- const product = await getProductBySlug(slug, productLocale);
+ const saleorLocale = getSaleorLocale(validLocale as Locale);
+ const product = await getProductBySlug(slug, saleorLocale);
- const basePath = locale === "sr" ? "" : `/${locale}`;
+ const basePath = `/${validLocale}`;
if (!product) {
return (
@@ -95,10 +100,10 @@ export default async function ProductPage({ params }: ProductPageProps) {
-
+
>
);
}
\ No newline at end of file
diff --git a/src/app/[locale]/products/page.tsx b/src/app/[locale]/products/page.tsx
index dc0ee7c..b3dd28e 100644
--- a/src/app/[locale]/products/page.tsx
+++ b/src/app/[locale]/products/page.tsx
@@ -4,6 +4,8 @@ import Header from "@/components/layout/Header";
import Footer from "@/components/layout/Footer";
import ProductCard from "@/components/product/ProductCard";
import { ChevronDown } from "lucide-react";
+import { getPageMetadata } from "@/lib/i18n/pageMetadata";
+import { isValidLocale, DEFAULT_LOCALE, getSaleorLocale, type Locale } from "@/lib/i18n/locales";
interface ProductsPageProps {
params: Promise<{ locale: string }>;
@@ -11,22 +13,21 @@ interface ProductsPageProps {
export async function generateMetadata({ params }: ProductsPageProps) {
const { locale } = await params;
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ const metadata = getPageMetadata(validLocale as Locale);
return {
- title: locale === "sr"
- ? "Proizvodi - ManoonOils"
- : "Products - ManoonOils",
- description: locale === "sr"
- ? "Pregledajte našu kolekciju premium prirodnih ulja za negu kose i kože."
- : "Browse our collection of premium natural oils for hair and skin care.",
+ title: metadata.products.title,
+ description: metadata.products.description,
};
}
export default async function ProductsPage({ params }: ProductsPageProps) {
const { locale } = await params;
- setRequestLocale(locale);
+ const validLocale = isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ setRequestLocale(validLocale);
const t = await getTranslations("Products");
- const productLocale = locale === "sr" ? "SR" : "EN";
- const products = await getProducts(productLocale);
+ const saleorLocale = getSaleorLocale(validLocale as Locale);
+ const products = await getProducts(saleorLocale);
return (
<>
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/src/app/favicon.ico and /dev/null differ
diff --git a/src/app/icon.png b/src/app/icon.png
new file mode 100644
index 0000000..51b9bd6
Binary files /dev/null and b/src/app/icon.png differ
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 64be938..e8ffdcd 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,9 @@
import "./globals.css";
import type { Metadata, Viewport } from "next";
import ErrorBoundary from "@/components/providers/ErrorBoundary";
+import { SUPPORTED_LOCALES } from "@/lib/i18n/locales";
+
+const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://dev.manoonoils.com";
export const metadata: Metadata = {
title: {
@@ -9,6 +12,12 @@ export const metadata: Metadata = {
},
description: "Discover our premium collection of natural oils for hair and skin care.",
robots: "index, follow",
+ alternates: {
+ canonical: baseUrl,
+ languages: Object.fromEntries(
+ SUPPORTED_LOCALES.map((locale) => [locale, locale === "sr" ? baseUrl : `${baseUrl}/${locale}`])
+ ),
+ },
openGraph: {
title: "ManoonOils - Premium Natural Oils for Hair & Skin",
description: "Discover our premium collection of natural oils for hair and skin care.",
@@ -37,4 +46,4 @@ export default async function RootLayout({