From 164c2bdaa61d2df597c810fcf2b57768aee4ecad Mon Sep 17 00:00:00 2001 From: andreyjan Date: Thu, 29 Aug 2024 15:32:54 +0300 Subject: [PATCH] Make language switcher work with translatable slugs. --- app/[locale]/[[...slug]]/page.tsx | 18 +++++++---- app/[locale]/layout.tsx | 11 ++++--- app/[locale]/locale-provider.tsx | 4 +-- components/language-data-provider/index.ts | 1 + .../language-data-provider.tsx | 14 ++++++++ .../language-data-setter.tsx | 15 +++++++++ .../language-selector/language-selector.tsx | 32 ++++++++++++++----- ui/context/index.ts | 1 + ui/context/language-data.context.ts | 4 +++ ui/hooks/index.ts | 1 + ui/hooks/use-language-data-context.hook.ts | 8 +++++ 11 files changed, 89 insertions(+), 20 deletions(-) create mode 100644 components/language-data-provider/index.ts create mode 100644 components/language-data-provider/language-data-provider.tsx create mode 100644 components/language-data-provider/language-data-setter.tsx create mode 100644 ui/context/index.ts create mode 100644 ui/context/language-data.context.ts create mode 100644 ui/hooks/index.ts create mode 100644 ui/hooks/use-language-data-context.hook.ts diff --git a/app/[locale]/[[...slug]]/page.tsx b/app/[locale]/[[...slug]]/page.tsx index da29492..a270472 100644 --- a/app/[locale]/[[...slug]]/page.tsx +++ b/app/[locale]/[[...slug]]/page.tsx @@ -7,6 +7,7 @@ import { ComponentRenderer } from '#/components/component-renderer'; import DebugMode from '#/components/debug-mode/debug-mode'; import { ComponentDuplexFieldsFragment } from '#/components/duplex-ctf/duplex-ctf'; import { ComponentHeroBannerFieldsFragment } from '#/components/hero-banner-ctf/hero-banner-ctf'; +import { LanguageDataSetter } from '#/components/language-data-provider/language-data-setter'; import { graphqlClient } from '#/lib/graphqlClient'; import { getLocaleFromPath } from '#/locales/get-locale-from-path'; import { getStaticParams } from '#/locales/server'; @@ -23,6 +24,8 @@ const getPage = async (slug: string, locale: string, preview = false) => { ...ComponentDuplexFields } } + slugEn: slug(locale: "en-US") + slugDe: slug(locale: "de-DE") } } } @@ -38,7 +41,7 @@ const getPage = async (slug: string, locale: string, preview = false) => { ).data?.pageCollection?.items?.[0]; }; -const getPageSlugs = async () => { +const getPageSlugs = async (locale: string) => { const pageQuery = graphql(` query PageSlugs($locale: String) { # Fetch 50 pages. Ideally we would fetch a good sample of most popular pages for pre-rendering, @@ -52,7 +55,7 @@ const getPageSlugs = async () => { `); const pages = await graphqlClient(false).query(pageQuery, { - locale: 'en-US', + locale: locale, }); return ( @@ -78,6 +81,9 @@ export default async function LandingPage({ params }: { params: { slug: string[] return (
+ {topComponents ? : null}
); @@ -88,10 +94,10 @@ export const revalidate = 120; export async function generateStaticParams() { const params = getStaticParams(); const returnData: Array<{ slug?: string[]; locale: string }> = []; - const slugs = (await getPageSlugs()).map((page) => ({ - slug: page?.slug?.split('/'), - })); - for (const locale of params) { + for await (const locale of params) { + const slugs = (await getPageSlugs(getLocaleFromPath(locale.locale))).map((page) => ({ + slug: page?.slug?.split('/'), + })); for (const slug of slugs) { returnData.push({ slug: slug.slug, locale: locale.locale }); } diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index df52c62..8b045d7 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -10,6 +10,7 @@ import '@contentful/live-preview/style.css'; import { LocaleProvider } from '#/app/[locale]/locale-provider'; import { ContentfulPreviewProvider } from '#/components/contentful-preview-provider'; +import { LanguageDataProvider } from '#/components/language-data-provider'; import { NavigationFieldsFragment } from '#/components/navigation'; import { SiteHeader } from '#/components/site-header'; import { fontSans } from '#/lib/fonts'; @@ -56,10 +57,12 @@ export default async function RootLayout({ -
- -
{children}
-
+ +
+ +
{children}
+
+
diff --git a/app/[locale]/locale-provider.tsx b/app/[locale]/locale-provider.tsx index f885cfa..9cd5172 100644 --- a/app/[locale]/locale-provider.tsx +++ b/app/[locale]/locale-provider.tsx @@ -9,10 +9,10 @@ type LocaleProviderProps = { children: ReactNode; }; -export function LocaleProvider({ locale, children }: LocaleProviderProps) { +export const LocaleProvider = ({ locale, children }: LocaleProviderProps) => { return ( Loading...

}> {children}
); -} +}; diff --git a/components/language-data-provider/index.ts b/components/language-data-provider/index.ts new file mode 100644 index 0000000..6112576 --- /dev/null +++ b/components/language-data-provider/index.ts @@ -0,0 +1 @@ +export * from './language-data-provider'; diff --git a/components/language-data-provider/language-data-provider.tsx b/components/language-data-provider/language-data-provider.tsx new file mode 100644 index 0000000..a67fa96 --- /dev/null +++ b/components/language-data-provider/language-data-provider.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { ReactNode, useState } from 'react'; + +import { LanguageDataContext } from 'ui/context'; + +interface LanguageDataProviderProps { + children: ReactNode; +} + +export const LanguageDataProvider = ({ children }: LanguageDataProviderProps) => { + const slugsState = useState>({}); + return {children}; +}; diff --git a/components/language-data-provider/language-data-setter.tsx b/components/language-data-provider/language-data-setter.tsx new file mode 100644 index 0000000..4c9149c --- /dev/null +++ b/components/language-data-provider/language-data-setter.tsx @@ -0,0 +1,15 @@ +'use client'; + +import { useEffect } from 'react'; + +import { useLanguageDataContext } from '#/ui/hooks'; + +export const LanguageDataSetter = ({ data }: { data?: Record }) => { + const [, setSlugs] = useLanguageDataContext()!.slugsState; + useEffect(() => { + if (data) { + setSlugs(data); + } + }, [data]); + return null; +}; diff --git a/components/language-selector/language-selector.tsx b/components/language-selector/language-selector.tsx index de1d896..1310c28 100644 --- a/components/language-selector/language-selector.tsx +++ b/components/language-selector/language-selector.tsx @@ -1,19 +1,35 @@ 'use client'; -import { useChangeLocale, useCurrentLocale } from '#/locales/client'; +import { ChangeEvent, useTransition } from 'react'; +import { useRouter } from 'next/navigation'; + +import { useCurrentLocale } from '#/locales/client'; +import { useLanguageDataContext } from '#/ui/hooks'; export const LanguageSelector = () => { const locale = useCurrentLocale(); - const changeLocale = useChangeLocale({ preserveSearchParams: true }); + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const languageData = useLanguageDataContext(); - const handleLanguageChange = (e: React.ChangeEvent) => { - changeLocale(e.target.value as 'en' | 'de'); + const handleLanguageChange = (e: ChangeEvent) => { + startTransition(() => { + const selectedLocale = e.target.value; + const [slugs] = languageData?.slugsState ?? []; + if (slugs?.[selectedLocale]) { + router.push(`/${selectedLocale}/${slugs[selectedLocale]}`); + } else { + router.push(`/${selectedLocale}`); + } + }); }; return ( - + <> + + ); }; diff --git a/ui/context/index.ts b/ui/context/index.ts new file mode 100644 index 0000000..13a1514 --- /dev/null +++ b/ui/context/index.ts @@ -0,0 +1 @@ +export * from './language-data.context'; diff --git a/ui/context/language-data.context.ts b/ui/context/language-data.context.ts new file mode 100644 index 0000000..67692b2 --- /dev/null +++ b/ui/context/language-data.context.ts @@ -0,0 +1,4 @@ +import { createContext, Dispatch, SetStateAction } from 'react'; + +type LanguageData = { slugsState: [Record, Dispatch>>] }; +export const LanguageDataContext = createContext(undefined); diff --git a/ui/hooks/index.ts b/ui/hooks/index.ts new file mode 100644 index 0000000..a35ce30 --- /dev/null +++ b/ui/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-language-data-context.hook'; diff --git a/ui/hooks/use-language-data-context.hook.ts b/ui/hooks/use-language-data-context.hook.ts new file mode 100644 index 0000000..62dd272 --- /dev/null +++ b/ui/hooks/use-language-data-context.hook.ts @@ -0,0 +1,8 @@ +import { useContext } from 'react'; + +import { LanguageDataContext } from '#/ui/context'; + +export function useLanguageDataContext() { + const context = useContext(LanguageDataContext); + return context ?? null; +}