From dc42a1e913ddf4f86e9a59737fbb63b2899fc306 Mon Sep 17 00:00:00 2001 From: Jorge Moya Date: Wed, 31 Jul 2024 14:13:07 -0500 Subject: [PATCH] feat(core); add locale picker in Header --- .changeset/empty-eels-sing.md | 5 + core/components/footer/locale.tsx | 3 +- core/components/header/index.tsx | 2 + core/components/header/locale-switcher.tsx | 118 +++++++++++++++++++++ core/components/ui/popover/popover.tsx | 8 +- core/i18n.ts | 58 +++++----- 6 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 .changeset/empty-eels-sing.md create mode 100644 core/components/header/locale-switcher.tsx diff --git a/.changeset/empty-eels-sing.md b/.changeset/empty-eels-sing.md new file mode 100644 index 0000000000..24811c581e --- /dev/null +++ b/.changeset/empty-eels-sing.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/catalyst-core": patch +--- + +Add locale picker in header. diff --git a/core/components/footer/locale.tsx b/core/components/footer/locale.tsx index 17a126f785..6535ffbcbe 100644 --- a/core/components/footer/locale.tsx +++ b/core/components/footer/locale.tsx @@ -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 && ( {localeLanguageRegionMap[locale].flag} {localeLanguageRegionMap[locale].region} diff --git a/core/components/header/index.tsx b/core/components/header/index.tsx index b72122a195..c66291a8aa 100644 --- a/core/components/header/index.tsx +++ b/core/components/header/index.tsx @@ -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( ` @@ -140,6 +141,7 @@ export const Header = async ({ cart, data }: Props) => { {cart}

+ ); }; diff --git a/core/components/header/locale-switcher.tsx b/core/components/header/locale-switcher.tsx new file mode 100644 index 0000000000..f95c902a45 --- /dev/null +++ b/core/components/header/locale-switcher.tsx @@ -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( + (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 && ( + {selectedLocale.flag} + } + > +
+

Choose your country and language

+ ({ + value: language, + label: language, + })) || [] // FIX? + } + value={languageSelected} + /> + +
+
+ ) + ); +}; diff --git a/core/components/ui/popover/popover.tsx b/core/components/ui/popover/popover.tsx index 259f5009f7..bc9ebabf87 100644 --- a/core/components/ui/popover/popover.tsx +++ b/core/components/ui/popover/popover.tsx @@ -1,7 +1,10 @@ import * as PopoverPrimitive from '@radix-ui/react-popover'; import { ComponentPropsWithoutRef, ReactNode } from 'react'; -interface Props extends ComponentPropsWithoutRef { +interface Props extends ComponentPropsWithoutRef { + align?: 'start' | 'center' | 'end'; + className?: string; + sideOffset?: number; trigger: ReactNode; } @@ -13,14 +16,13 @@ const Popover = ({ trigger, ...props }: Props) => ( - + {trigger} {children} diff --git a/core/i18n.ts b/core/i18n.ts index 55eae6fd3c..0805909a12 100644 --- a/core/i18n.ts +++ b/core/i18n.ts @@ -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', @@ -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}`;