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) {
{locale
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
{locale
@@ -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) { -