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({
-
+
+
+
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;
+}