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}
+ }
+ >
+
+
+ )
+ );
+};
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}`;