Skip to content

Commit

Permalink
feat: localised 404 page
Browse files Browse the repository at this point in the history
  • Loading branch information
denkristoffer committed Sep 23, 2024
1 parent 710948e commit f30ed1f
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 28 deletions.
5 changes: 5 additions & 0 deletions src/app/[locale]/[...notFound]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { notFound } from 'next/navigation';

export default function NotFoundCatchAll() {
notFound();
}
12 changes: 8 additions & 4 deletions src/app/[locale]/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { headers } from 'next/headers';
import Link from 'next/link';
import { Trans } from 'react-i18next/TransWithoutContext';

import { Container } from '@src/components/shared/container';
import initTranslations from '@src/i18n';
import { defaultLocale } from '@src/i18n/config';

export default async function NotFound() {
const { t } = await initTranslations({ locale: defaultLocale });
const headersList = headers();
const locale = headersList.get('x-next-i18n-router-locale') || defaultLocale;
const { t } = await initTranslations({ locale });

return (
<Container>
<h1 className="h2">{t('notFound.title')}</h1>
<p className="mt-4">
{t('notFound.description')}

<Link className="text-blue500" href="/" />
<Trans i18nKey="notFound.description" t={t}>
<Link className="text-blue500" href="/" />
</Trans>
</p>
</Container>
);
Expand Down
50 changes: 47 additions & 3 deletions src/components/features/language-selector/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
'use client';

import { usePathname, useRouter } from 'next/navigation';
import { SyntheticEvent } from 'react';
import { useTranslation } from 'react-i18next';

import { LanguageSelectorDesktop } from '@src/components/features/language-selector/LanguageSelectorDesktop';
import { LanguageSelectorMobile } from '@src/components/features/language-selector/LanguageSelectorMobile';
import { locales } from '@src/i18n/config';
import i18nConfig, { locales } from '@src/i18n/config';

const localeName = locale => locale.split('-')[0];

Expand All @@ -11,15 +15,55 @@ const displayName = locale =>
type: 'language',
});

const isChangeEvent = (event: SyntheticEvent): event is React.ChangeEvent<HTMLSelectElement> => {
return event.type === 'change';
};

export const LanguageSelector = () => {
const { i18n } = useTranslation();
const currentLocale = i18n.language;
const router = useRouter();
const currentPathname = usePathname();

const handleLocaleChange: React.EventHandler<React.SyntheticEvent> = e => {
let newLocale: string | undefined = undefined;

if (isChangeEvent(e)) {
newLocale = e.target.value;
}

// set cookie for next-i18n-router
const days = 30;
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `NEXT_LOCALE=${newLocale};expires=${date.toUTCString()};path=/`;

// redirect to the new locale path
if (currentLocale === i18nConfig.defaultLocale) {
router.push('/' + newLocale + currentPathname);
} else {
router.push(currentPathname.replace(`/${currentLocale}`, `/${newLocale}`));
}

router.refresh();
};

return locales && locales.length > 1 ? (
<>
<div className="hidden md:block">
<LanguageSelectorDesktop displayName={displayName} localeName={localeName} />
<LanguageSelectorDesktop
displayName={displayName}
onChange={handleLocaleChange}
localeName={localeName}
/>
</div>

<div className="block md:hidden">
<LanguageSelectorMobile displayName={displayName} localeName={localeName} />
<LanguageSelectorMobile
displayName={displayName}
onChange={handleLocaleChange}
localeName={localeName}
/>
</div>
</>
) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LanguageIcon, ChevronDownTrimmedIcon, ChevronUpTrimmedIcon } from '@contentful/f36-icons';
import { LanguageIcon, ChevronDownIcon, ChevronUpIcon } from '@contentful/f36-icons';
import { useCurrentLocale } from 'next-i18n-router/client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
Expand All @@ -23,7 +23,7 @@ const useClickOutside = (ref, setIsOpen) => {
}, [ref, setIsOpen]);
};

export const LanguageSelectorDesktop = ({ localeName, displayName }) => {
export const LanguageSelectorDesktop = ({ localeName, onChange, displayName }) => {
const currentLocale = useCurrentLocale(i18nConfig);
const menuRef = useRef<HTMLUListElement | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -99,9 +99,9 @@ export const LanguageSelectorDesktop = ({ localeName, displayName }) => {
<LanguageIcon width="18px" height="18px" variant="secondary" className="mr-1 ml-1" />
{localeName(currentLocale)}
{isOpen ? (
<ChevronUpTrimmedIcon variant="secondary" className="pl-1" />
<ChevronUpIcon variant="secondary" className="pl-1" />
) : (
<ChevronDownTrimmedIcon variant="secondary" className="pl-1" />
<ChevronDownIcon variant="secondary" className="pl-1" />
)}
</button>
<FocusLock disabled={!isOpen} returnFocus={true}>
Expand All @@ -128,7 +128,10 @@ export const LanguageSelectorDesktop = ({ localeName, displayName }) => {
: `/${availableLocale}${pathname}`
}
locale={availableLocale}
onClick={() => setIsOpen(false)}
onClick={event => {
onChange(event);
setIsOpen(false);
}}
>
{displayName(availableLocale).of(localeName(availableLocale))}
</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { LanguageIcon, CloseIcon } from '@contentful/f36-icons';
import { useCurrentLocale } from 'next-i18n-router/client';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import FocusLock from 'react-focus-lock';
import { useTranslation } from 'react-i18next';
Expand All @@ -11,15 +10,11 @@ import { twMerge } from 'tailwind-merge';
import { Portal } from '@src/components/shared/portal';
import i18nConfig, { locales } from '@src/i18n/config';

export const LanguageSelectorMobile = ({ localeName, displayName }) => {
export const LanguageSelectorMobile = ({ localeName, onChange, displayName }) => {
const currentLocale = useCurrentLocale(i18nConfig);
const pathname = usePathname();
const router = useRouter();
const { t } = useTranslation();
const [showDrawer, setShowDrawer] = useState(false);
// Try to extract and match a locale from a pattern of `/en-US/:slug`
const pathnameHasLocale = locales.includes(pathname.slice(1, 6));
const pathnameWithoutLocale = pathname.slice(6);

useEffect(() => {
const close = e => {
Expand Down Expand Up @@ -75,16 +70,7 @@ export const LanguageSelectorMobile = ({ localeName, displayName }) => {
<select
className="mt-2 block w-full rounded-md border border-gray300 py-2 px-2 text-sm"
defaultValue={currentLocale}
onChange={event => {
const locale = String(event.target.value);
router.push(
pathnameHasLocale
? `/${locale}${pathnameWithoutLocale}`
: `/${locale}${pathname}`,
{},
);
setShowDrawer(!showDrawer);
}}
onChange={onChange}
>
{locales?.map(availableLocale => (
<option key={availableLocale} value={availableLocale}>
Expand Down

0 comments on commit f30ed1f

Please sign in to comment.