Skip to content

Commit

Permalink
feat(core): add locale picker in Header (#1196)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgemoya committed Aug 1, 2024
1 parent 5c02c49 commit b793661
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-eels-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": patch
---

Add locale picker in header.
3 changes: 1 addition & 2 deletions core/components/footer/locale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ export const Locale = async () => {
const locale = (await getLocale()) as LocaleType;

return (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
locales.length > 1 && (
Object.keys(locales).length > 1 && (
<Link className="flex gap-2" href="/store-selector">
<span>{localeLanguageRegionMap[locale].flag}</span>
<span>{localeLanguageRegionMap[locale].region}</span>
Expand Down
9 changes: 9 additions & 0 deletions core/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ShoppingCart, User } from 'lucide-react';
import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';
import { ReactNode, Suspense } from 'react';

import { getSessionCustomerId } from '~/auth';
Expand All @@ -13,6 +15,7 @@ import { Popover } from '../ui/popover';

import { logout } from './_actions/logout';
import { CartLink } from './cart';
import { LocaleSwitcher } from './locale-switcher';

export const HeaderFragment = graphql(
`
Expand Down Expand Up @@ -48,6 +51,9 @@ interface Props {
export const Header = async ({ cart, data }: Props) => {
const customerId = await getSessionCustomerId();

const locale = await getLocale();
const messages = await getMessages({ locale });

/** To prevent the navigation menu from overflowing, we limit the number of categories to 6.
To show a full list of categories, modify the `slice` method to remove the limit.
Will require modification of navigation menu styles to accommodate the additional categories.
Expand Down Expand Up @@ -140,6 +146,9 @@ export const Header = async ({ cart, data }: Props) => {
{cart}
</Suspense>
</p>
<NextIntlClientProvider locale={locale} messages={{ Header: messages.Header ?? {} }}>
<LocaleSwitcher />
</NextIntlClientProvider>
</ComponentsHeader>
);
};
121 changes: 121 additions & 0 deletions core/components/header/locale-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use client';

import { useLocale, useTranslations } from 'next-intl';
import { useMemo, useState } from 'react';

import { localeLanguageRegionMap, locales, LocaleType } from '~/i18n';
import { useRouter } from '~/navigation';

import { Button } from '../ui/button';
import { Popover } from '../ui/popover';
import { Select } from '../ui/select';

type LanguagesByRegionMap = Record<
string,
{
languages: string[];
flag: string;
}
>;

export const LocaleSwitcher = () => {
const locale = useLocale();
const router = useRouter();

const t = useTranslations('Header');

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const selectedLocale = localeLanguageRegionMap[locale as LocaleType];

const [regionSelected, setRegionSelected] = useState(selectedLocale.region);
const [languageSelected, setLanguageSelected] = useState(selectedLocale.language);

const languagesByRegionMap = useMemo(
() =>
Object.values(localeLanguageRegionMap).reduce<LanguagesByRegionMap>(
(acc, { region, language, flag }) => {
if (!acc[region]) {
acc[region] = { languages: [language], flag };
}

if (!acc[region].languages.includes(language)) {
acc[region].languages.push(language);
}

return acc;
},
{},
),
[],
);

const regions = Object.keys(languagesByRegionMap);

const handleOnOpenChange = () => {
setRegionSelected(selectedLocale.region);
setLanguageSelected(selectedLocale.language);
};

const handleRegionChange = (region: string) => {
setRegionSelected(region);
setLanguageSelected(languagesByRegionMap[region]?.languages[0] || '');
};

const handleLanguageChange = (language: string) => {
if (language) {
setLanguageSelected(language);
}
};

const handleOnSubmit = () => {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const keys = Object.keys(localeLanguageRegionMap) as LocaleType[];

const newLocale = keys.find(
(key) =>
localeLanguageRegionMap[key].language === languageSelected &&
localeLanguageRegionMap[key].region === regionSelected,
);

if (newLocale) {
router.replace('/', { locale: newLocale });
}
};

return (
Object.keys(locales).length > 1 && (
<Popover
align="end"
onOpenChange={handleOnOpenChange}
trigger={
<button className="flex h-12 items-center p-3 text-2xl">{selectedLocale.flag}</button>
}
>
<form className="flex flex-col gap-4" onSubmit={handleOnSubmit}>
<p>{t('chooseCountryAndLanguage')}</p>
<Select
onValueChange={handleRegionChange}
options={regions.map((region) => ({
value: region,
label: `${languagesByRegionMap[region]?.flag} ${region}`,
}))}
value={regionSelected}
/>
<Select
onValueChange={handleLanguageChange}
options={
languagesByRegionMap[regionSelected]?.languages.map((language) => ({
value: language,
label: language,
})) || []
}
value={languageSelected}
/>
<Button className="w-auto" type="submit">
{t('goToSite')}
</Button>
</form>
</Popover>
)
);
};
8 changes: 5 additions & 3 deletions core/components/ui/popover/popover.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { ComponentPropsWithoutRef, ReactNode } from 'react';

interface Props extends ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> {
interface Props extends ComponentPropsWithoutRef<typeof PopoverPrimitive.Root> {
align?: 'start' | 'center' | 'end';
className?: string;
sideOffset?: number;
trigger: ReactNode;
}

Expand All @@ -13,14 +16,13 @@ const Popover = ({
trigger,
...props
}: Props) => (
<PopoverPrimitive.Root>
<PopoverPrimitive.Root {...props}>
<PopoverPrimitive.Trigger asChild>{trigger}</PopoverPrimitive.Trigger>
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
align={align}
className="z-50 bg-white p-4 text-base shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
sideOffset={sideOffset}
{...props}
>
{children}
</PopoverPrimitive.Content>
Expand Down
22 changes: 14 additions & 8 deletions core/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ enum LocalePrefixes {
ASNEEDED = 'as-needed', // removes prefix on default locale
}

// Enable locales by including them here
// List includes locales with existing messages support
// Enable locales by including them here.
// List includes locales with existing messages support.
const locales = [
'en',
// 'da',
Expand All @@ -32,17 +32,23 @@ const locales = [
// 'sv',
] as const;

type LocaleLanguageRegionMap = {
[key in LocaleType]: { language: string; region: string; flag: string };
interface LocaleEntry {
language: string;
region: string;
flag: string;
}

type LocalLanguageRegionMap = {
[key in LocaleType]: LocaleEntry;
};

/**
* Custom map of locale to language and region
* Temporary solution until we have a better way to include regions for all locales
* Custom map of locale to language and region.
* Temporary solution until we have a better way to include regions for all locales.
*/
export const localeLanguageRegionMap: LocaleLanguageRegionMap = {
export const localeLanguageRegionMap: LocalLanguageRegionMap = {
en: { language: 'English', region: 'United States', flag: '🇺🇸' },
// da: { language: 'Dansk', region: 'Danmark', flag: '🇩🇰' }
// da: { language: 'Dansk', region: 'Danmark', flag: '🇩🇰' },
// 'es-419': { language: 'Español', region: 'America Latina', flag: '' },
// 'es-AR': { language: 'Español', region: 'Argentina', flag: '🇦🇷' },
// 'es-CL': { language: 'Español', region: 'Chile', flag: '🇨🇱' },
Expand Down
4 changes: 3 additions & 1 deletion core/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,9 @@
}
},
"Header": {
"account": "Your Account"
"account": "Your Account",
"chooseCountryAndLanguage": "Choose your country and language",
"goToSite": "Go to site"
},
"Pagination": {
"next": "Next",
Expand Down

0 comments on commit b793661

Please sign in to comment.