diff --git a/next/app/[locale]/(marketing)/ClientSlugHandler.tsx b/next/app/[locale]/(marketing)/ClientSlugHandler.tsx new file mode 100644 index 0000000..ed1049c --- /dev/null +++ b/next/app/[locale]/(marketing)/ClientSlugHandler.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useEffect } from "react"; +import { useSlugContext } from "@/app/context/SlugContext"; + +export default function ClientSlugHandler({ + localizedSlugs, +}: { + localizedSlugs: Record; +}) { + const { dispatch } = useSlugContext(); + + useEffect(() => { + if (localizedSlugs) { + dispatch({ type: "SET_SLUGS", payload: localizedSlugs }); + } + }, [localizedSlugs, dispatch]); + + return null; // This component only handles the state and doesn't render anything. +} diff --git a/next/app/[locale]/(marketing)/[slug]/page.tsx b/next/app/[locale]/(marketing)/[slug]/page.tsx index 6d6abc5..3f28594 100644 --- a/next/app/[locale]/(marketing)/[slug]/page.tsx +++ b/next/app/[locale]/(marketing)/[slug]/page.tsx @@ -3,6 +3,7 @@ import { Metadata } from 'next'; import PageContent from '@/lib/shared/PageContent'; import fetchContentType from '@/lib/strapi/fetchContentType'; import { generateMetadataObject } from '@/lib/shared/metadata'; +import ClientSlugHandler from '../ClientSlugHandler'; export async function generateMetadata({ params, @@ -27,7 +28,19 @@ export default async function Page({ params }: { params: { locale: string, slug: true ); + const localizedSlugs = pageData.localizations?.reduce( + (acc: Record, localization: any) => { + acc[localization.locale] = localization.slug; + return acc; + }, + { [params.locale]: params.slug } + ); + return ( - + <> + + + + ); } \ No newline at end of file diff --git a/next/app/[locale]/(marketing)/blog/[slug]/page.tsx b/next/app/[locale]/(marketing)/blog/[slug]/page.tsx index 7de4e32..ca7c575 100644 --- a/next/app/[locale]/(marketing)/blog/[slug]/page.tsx +++ b/next/app/[locale]/(marketing)/blog/[slug]/page.tsx @@ -1,33 +1,38 @@ -import { Metadata } from 'next'; +import React from "react"; import { BlogLayout } from "@/components/blog-layout"; import fetchContentType from "@/lib/strapi/fetchContentType"; -import { BlocksRenderer } from '@strapi/blocks-react-renderer'; +import { BlocksRenderer } from "@strapi/blocks-react-renderer"; -import { generateMetadataObject } from '@/lib/shared/metadata'; +import ClientSlugHandler from "../../ClientSlugHandler"; -export async function generateMetadata({ +export default async function SingleArticlePage({ params, }: { - params: { locale: string, slug: string }; -}): Promise { - const pageData = await fetchContentType("articles", `filters[slug]=${params?.slug}&filters[locale][$eq]=${params.locale}&populate=seo.metaImage`, true) - - const seo = pageData?.seo; - const metadata = generateMetadataObject(seo); - return metadata; -} - -export default async function singleArticlePage({ params }: { params: { slug: string, locale: string } }) { - const article = await fetchContentType("articles", `filters[slug]=${params?.slug}&filters[locale][$eq]=${params.locale}`, true) + params: { slug: string; locale: string }; +}) { + const article = await fetchContentType( + "articles", + `filters[slug]=${params?.slug}&filters[locale][$eq]=${params.locale}`, + true + ); if (!article) { return
Blog not found
; } + const localizedSlugs = article.localizations?.reduce( + (acc: Record, localization: any) => { + acc[localization.locale] = localization.slug; + return acc; + }, + { [params.locale]: params.slug } + ); + return ( + ); -} \ No newline at end of file +} diff --git a/next/app/[locale]/(marketing)/blog/page.tsx b/next/app/[locale]/(marketing)/blog/page.tsx index ab49e97..09178b0 100644 --- a/next/app/[locale]/(marketing)/blog/page.tsx +++ b/next/app/[locale]/(marketing)/blog/page.tsx @@ -11,6 +11,7 @@ import fetchContentType from "@/lib/strapi/fetchContentType"; import { Article } from "@/types/types"; import { generateMetadataObject } from '@/lib/shared/metadata'; +import ClientSlugHandler from "../ClientSlugHandler"; export async function generateMetadata({ params, @@ -27,13 +28,22 @@ export async function generateMetadata({ export default async function Blog({ params, }: { - params: { locale: string }; + params: { locale: string, slug: string }; }) { const blogPage = await fetchContentType('blog-page', `filters[locale]=${params.locale}`, true) const articles = await fetchContentType('articles', `filters[locale]=${params.locale}`) + const localizedSlugs = blogPage.localizations?.reduce( + (acc: Record, localization: any) => { + acc[localization.locale] = "blog"; + return acc; + }, + { [params.locale]: "blog" } + ); + return (
+
diff --git a/next/app/[locale]/(marketing)/page.tsx b/next/app/[locale]/(marketing)/page.tsx index 695e258..07b11e5 100644 --- a/next/app/[locale]/(marketing)/page.tsx +++ b/next/app/[locale]/(marketing)/page.tsx @@ -3,6 +3,7 @@ import { Metadata } from 'next'; import PageContent from '@/lib/shared/PageContent'; import fetchContentType from '@/lib/strapi/fetchContentType'; import { generateMetadataObject } from '@/lib/shared/metadata'; +import ClientSlugHandler from './ClientSlugHandler'; export async function generateMetadata({ params, @@ -27,5 +28,16 @@ export default async function HomePage({ params }: { params: { locale: string } true ); - return ; + const localizedSlugs = pageData.localizations?.reduce( + (acc: Record, localization: any) => { + acc[localization.locale] = ""; + return acc; + }, + { [params.locale]: "" } + ); + + return <> + + + ; } diff --git a/next/app/[locale]/(marketing)/products/page.tsx b/next/app/[locale]/(marketing)/products/page.tsx index 5d74ad4..23533ef 100644 --- a/next/app/[locale]/(marketing)/products/page.tsx +++ b/next/app/[locale]/(marketing)/products/page.tsx @@ -11,6 +11,8 @@ import { IconShoppingCartUp } from "@tabler/icons-react"; import fetchContentType from "@/lib/strapi/fetchContentType"; import { generateMetadataObject } from '@/lib/shared/metadata'; +import ClientSlugHandler from '../ClientSlugHandler'; + export async function generateMetadata({ params, }: { @@ -32,10 +34,18 @@ export default async function Products({ const productPage = await fetchContentType('product-page', `filters[locale]=${params.locale}`, true); const products = await fetchContentType('products', ``); + const localizedSlugs = productPage.localizations?.reduce( + (acc: Record, localization: any) => { + acc[localization.locale] = "products"; + return acc; + }, + { [params.locale]: "products" } + ); const featured = products?.data.filter((product: { featured: boolean }) => product.featured); return (
+ diff --git a/next/app/context/SlugContext.tsx b/next/app/context/SlugContext.tsx new file mode 100644 index 0000000..69dcfbd --- /dev/null +++ b/next/app/context/SlugContext.tsx @@ -0,0 +1,44 @@ +"use client"; + +import React, { createContext, useContext, useReducer } from "react"; + +type State = { + localizedSlugs: Record; +}; + +type Action = { + type: "SET_SLUGS"; + payload: Record; +}; + +const SlugContext = createContext<{ + state: State; + dispatch: React.Dispatch; +} | null>(null); + +const slugReducer = (state: State, action: Action): State => { + switch (action.type) { + case "SET_SLUGS": + return { ...state, localizedSlugs: action.payload }; + default: + return state; + } +}; + +export const SlugProvider = ({ children }: { children: React.ReactNode }) => { + const [state, dispatch] = useReducer(slugReducer, { localizedSlugs: {} }); + + return ( + + {children} + + ); +}; + +export const useSlugContext = () => { + const context = useContext(SlugContext); + if (!context) { + throw new Error("useSlugContext must be used within a SlugProvider"); + } + return context; +}; diff --git a/next/app/layout.tsx b/next/app/layout.tsx index bd65a4a..7d9947d 100644 --- a/next/app/layout.tsx +++ b/next/app/layout.tsx @@ -3,6 +3,8 @@ import { Locale, i18n } from '@/i18n.config' import "./globals.css"; +import { SlugProvider } from "./context/SlugContext"; + export const viewport: Viewport = { themeColor: [ { media: "(prefers-color-scheme: light)", color: "#06b6d4" }, @@ -24,7 +26,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); diff --git a/next/components/locale-switcher.tsx b/next/components/locale-switcher.tsx index 2672195..833d3fc 100644 --- a/next/components/locale-switcher.tsx +++ b/next/components/locale-switcher.tsx @@ -1,44 +1,55 @@ +"use client"; + import React from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { useSlugContext } from "@/app/context/SlugContext"; +import { cn } from "@/lib/utils"; +export function LocaleSwitcher({ currentLocale }: { currentLocale: string }) { + const { state } = useSlugContext(); + const { localizedSlugs } = state; -import Link from 'next/link' -import { usePathname } from 'next/navigation' -import { i18n } from '@/i18n.config' + const pathname = usePathname(); // Current path + const segments = pathname.split("/"); // Split path into segments -import { cn } from "@/lib/utils"; + // Generate localized path for each locale + const generateLocalizedPath = (locale: string): string => { + if (!pathname) return `/${locale}`; // Default to root path for the locale + + // Handle homepage (e.g., "/en" -> "/fr") + if (segments.length <= 2) { + return `/${locale}`; + } -export function LocaleSwitcher() { - const pathName = usePathname() - const currentLocale = pathName.split('/')[1] + // Handle dynamic paths (e.g., "/en/blog/[slug]") + if (localizedSlugs[locale]) { + segments[1] = locale; // Replace the locale + segments[segments.length - 1] = localizedSlugs[locale]; // Replace slug if available + return segments.join("/"); + } - const redirectedPathName = (locale: string) => { - if (!pathName) return '/' - const segments = pathName.split('/') - segments[1] = locale - return segments.join('/') - } + // Fallback to replace only the locale + segments[1] = locale; + return segments.join("/"); + }; return ( -
- {i18n.locales.map((locale) => ( - - -
- {locale} -
-
+
+ {!pathname.includes("/products/") && Object.keys(localizedSlugs).map((locale) => ( + +
+ {locale} +
))}
); -} \ No newline at end of file +} diff --git a/next/components/navbar/desktop-navbar.tsx b/next/components/navbar/desktop-navbar.tsx index 73da2db..bbc4c23 100644 --- a/next/components/navbar/desktop-navbar.tsx +++ b/next/components/navbar/desktop-navbar.tsx @@ -77,7 +77,7 @@ export const DesktopNavbar = ({ leftNavbarItems, rightNavbarItems, logo, locale
- + {rightNavbarItems.map((item, index) => (