Skip to content

Commit

Permalink
Make language switcher work with translatable slugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
andreyjan committed Aug 29, 2024
1 parent 686672c commit 164c2bd
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 20 deletions.
18 changes: 12 additions & 6 deletions app/[locale]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -23,6 +24,8 @@ const getPage = async (slug: string, locale: string, preview = false) => {
...ComponentDuplexFields
}
}
slugEn: slug(locale: "en-US")
slugDe: slug(locale: "de-DE")
}
}
}
Expand All @@ -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,
Expand All @@ -52,7 +55,7 @@ const getPageSlugs = async () => {
`);

const pages = await graphqlClient(false).query(pageQuery, {
locale: 'en-US',
locale: locale,
});

return (
Expand All @@ -78,6 +81,9 @@ export default async function LandingPage({ params }: { params: { slug: string[]
return (
<div>
<DebugMode slug={slug} />
<LanguageDataSetter
data={{ ...(pageData?.slugEn && { en: pageData.slugEn }), ...(pageData?.slugDe && { de: pageData.slugDe }) }}
/>
{topComponents ? <ComponentRenderer data={topComponents} /> : null}
</div>
);
Expand All @@ -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 });
}
Expand Down
11 changes: 7 additions & 4 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -56,10 +57,12 @@ export default async function RootLayout({
<body className={cn('min-h-screen bg-background font-sans antialiased', fontSans.variable)}>
<ContentfulPreviewProvider isDraftMode={isDraftMode}>
<LocaleProvider locale={locale}>
<div className="relative flex min-h-screen flex-col">
<SiteHeader navigationData={layoutData.data?.navigationMenuCollection} />
<div className="flex-1">{children}</div>
</div>
<LanguageDataProvider>
<div className="relative flex min-h-screen flex-col">
<SiteHeader navigationData={layoutData.data?.navigationMenuCollection} />
<div className="flex-1">{children}</div>
</div>
</LanguageDataProvider>
</LocaleProvider>
</ContentfulPreviewProvider>
</body>
Expand Down
4 changes: 2 additions & 2 deletions app/[locale]/locale-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ type LocaleProviderProps = {
children: ReactNode;
};

export function LocaleProvider({ locale, children }: LocaleProviderProps) {
export const LocaleProvider = ({ locale, children }: LocaleProviderProps) => {
return (
<I18nProviderClient locale={locale} fallback={<p>Loading...</p>}>
{children}
</I18nProviderClient>
);
}
};
1 change: 1 addition & 0 deletions components/language-data-provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './language-data-provider';
14 changes: 14 additions & 0 deletions components/language-data-provider/language-data-provider.tsx
Original file line number Diff line number Diff line change
@@ -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<Record<string, string>>({});
return <LanguageDataContext.Provider value={{ slugsState }}>{children}</LanguageDataContext.Provider>;
};
15 changes: 15 additions & 0 deletions components/language-data-provider/language-data-setter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';

import { useEffect } from 'react';

import { useLanguageDataContext } from '#/ui/hooks';

export const LanguageDataSetter = ({ data }: { data?: Record<string, string> }) => {
const [, setSlugs] = useLanguageDataContext()!.slugsState;
useEffect(() => {
if (data) {
setSlugs(data);
}
}, [data]);
return null;
};
32 changes: 24 additions & 8 deletions components/language-selector/language-selector.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLSelectElement>) => {
changeLocale(e.target.value as 'en' | 'de');
const handleLanguageChange = (e: ChangeEvent<HTMLSelectElement>) => {
startTransition(() => {
const selectedLocale = e.target.value;
const [slugs] = languageData?.slugsState ?? [];
if (slugs?.[selectedLocale]) {
router.push(`/${selectedLocale}/${slugs[selectedLocale]}`);
} else {
router.push(`/${selectedLocale}`);
}
});
};

return (
<select value={locale} onChange={handleLanguageChange}>
<option value="en">English</option>
<option value="de">Deutsche</option>
</select>
<>
<select value={locale} onChange={handleLanguageChange} disabled={isPending}>
<option value="en">English</option>
<option value="de">Deutsche</option>
</select>
</>
);
};
1 change: 1 addition & 0 deletions ui/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './language-data.context';
4 changes: 4 additions & 0 deletions ui/context/language-data.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createContext, Dispatch, SetStateAction } from 'react';

type LanguageData = { slugsState: [Record<string, string>, Dispatch<SetStateAction<Record<string, string>>>] };
export const LanguageDataContext = createContext<LanguageData | undefined>(undefined);
1 change: 1 addition & 0 deletions ui/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './use-language-data-context.hook';
8 changes: 8 additions & 0 deletions ui/hooks/use-language-data-context.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useContext } from 'react';

import { LanguageDataContext } from '#/ui/context';

export function useLanguageDataContext() {
const context = useContext(LanguageDataContext);
return context ?? null;
}

0 comments on commit 164c2bd

Please sign in to comment.