Skip to content

Commit

Permalink
feat(core); add locale picker in Header
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgemoya committed Jul 31, 2024
1 parent d01b4e0 commit dc42a1e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 31 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
2 changes: 2 additions & 0 deletions core/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,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 @@ -140,6 +141,7 @@ export const Header = async ({ cart, data }: Props) => {
{cart}
</Suspense>
</p>
<LocaleSwitcher />
</ComponentsHeader>
);
};
118 changes: 118 additions & 0 deletions core/components/header/locale-switcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use client';

import { useLocale } 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();

// 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] || ''); // FIX?
};

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
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>Choose your country and language</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,
})) || [] // FIX?
}
value={languageSelected}
/>
<Button className="w-auto" type="submit">
Go to site
</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
58 changes: 32 additions & 26 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,35 +32,41 @@ 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: '🇩🇰' }
// '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: '🇨🇱' },
// 'es-CO': { language: 'Español', region: 'Colombia', flag: '🇨🇴' },
// 'es-LA': { language: 'Español', region: 'America Latina', flag: '' },
// 'es-MX': { language: 'Español', region: 'México', flag: '🇲🇽' },
// 'es-PE': { language: 'Español', region: 'Perú', flag: '🇵🇪' },
// es: { language: 'Español', region: 'España', flag: '🇪🇸' },
// it: { language: 'Italiano', region: 'Italia', flag: '🇮🇹' },
// nl: { language: 'Nederlands', region: 'Nederland', flag: '🇳🇱' },
// pl: { language: 'Polski', region: 'Polska', flag: '🇵🇱' },
// pt: { language: 'Português', region: 'Portugal', flag: '🇵🇹' },
// de: { language: 'Deutsch', region: 'Deutschland', flag: '🇩🇪' },
// fr: { language: 'Français', region: 'France', flag: '🇫🇷' },
// ja: { language: '日本語', region: '日本', flag: '🇯🇵' },
// no: { language: 'Norsk', region: 'Norge', flag: '🇳🇴' },
// 'pt-BR': { language: 'Português', region: 'Brasil', flag: '🇧🇷' },
// sv: { language: 'Svenska', region: 'Sverige', 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: '🇨🇱' },
// 'es-CO': { language: 'Español', region: 'Colombia', flag: '🇨🇴' },
// 'es-LA': { language: 'Español', region: 'America Latina', flag: '' },
// 'es-MX': { language: 'Español', region: 'México', flag: '🇲🇽' },
// 'es-PE': { language: 'Español', region: 'Perú', flag: '🇵🇪' },
// es: { language: 'Español', region: 'España', flag: '🇪🇸' },
// it: { language: 'Italiano', region: 'Italia', flag: '🇮🇹' },
// nl: { language: 'Nederlands', region: 'Nederland', flag: '🇳🇱' },
// pl: { language: 'Polski', region: 'Polska', flag: '🇵🇱' },
// pt: { language: 'Português', region: 'Portugal', flag: '🇵🇹' },
// de: { language: 'Deutsch', region: 'Deutschland', flag: '🇩🇪' },
// fr: { language: 'Français', region: 'France', flag: '🇫🇷' },
// ja: { language: '日本語', region: '日本', flag: '🇯🇵' },
// no: { language: 'Norsk', region: 'Norge', flag: '🇳🇴' },
// 'pt-BR': { language: 'Português', region: 'Brasil', flag: '🇧🇷' },
// sv: { language: 'Svenska', region: 'Sverige', flag: '🇸🇪' },
};

type LocalePrefixesType = `${LocalePrefixes}`;
Expand Down

0 comments on commit dc42a1e

Please sign in to comment.