diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8fcbab4b0b..791fee1573 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -55,7 +55,7 @@ jobs: # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # - name: Install Playwright (used for Storybook and E2E tests) - # run: pnpx playwright install --with-deps + # run: npx playwright install --with-deps # - name: Run storybook tests # run: pnpm test-storybook:ci diff --git a/.husky/pre-commit b/.husky/pre-commit index 4d61930c9e..ac500ecd27 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -5,4 +5,4 @@ if [ "$branch" = "main" ]; then exit 1 fi -pnpx lint-staged +npx lint-staged diff --git a/package.json b/package.json index 6d06549e35..70bd51de77 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "check-types": "tsc --noEmit --pretty", "test": "vitest run", "test:e2e": "playwright test", - "dev:spotlight": "pnpx @spotlightjs/spotlight", + "dev:spotlight": "npx @spotlightjs/spotlight", "dev:next": "next dev --turbopack", "dev": "run-p dev:*", "build": "next build", @@ -117,7 +117,7 @@ "prettier --write" ], "src/**/*.{ts,tsx}": [ - "pnpx eslint --max-warnings=0 --fix" + "npx eslint --max-warnings=0 --fix" ] } } diff --git a/src/actions/locale.ts b/src/actions/locale.ts index 30d3ddd38e..c5c5649b44 100644 --- a/src/actions/locale.ts +++ b/src/actions/locale.ts @@ -1,6 +1,6 @@ 'use server'; -import type { Locale } from '@/types/locale'; +import type { Locale } from '@/lib/locale'; import { cookies } from 'next/headers'; export const setLocale = async (locale: Locale) => { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a396d13bb3..5782bc7e4c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,10 +3,10 @@ import '@/styles/globals.css'; import type { Metadata } from 'next'; import { NextIntlClientProvider } from 'next-intl'; import { getLocale } from 'next-intl/server'; -import { enUS, frFR } from '@clerk/localizations'; import { ClerkProvider } from '@clerk/nextjs'; -import { ThemeProvider } from '@/components/layout/ThemeProvider'; +import { getClerkLocale } from '@/lib/locale'; +import { ThemeProvider } from '@/components/layout'; export const metadata: Metadata = { icons: [ @@ -41,13 +41,8 @@ export default async function RootLayout({ suppressHydrationWarning > - - + + {children} diff --git a/src/components/layout/LocaleSwitcher.tsx b/src/components/layout/LocaleSwitcher.tsx index 1ca8dc1adc..0a90055c73 100644 --- a/src/components/layout/LocaleSwitcher.tsx +++ b/src/components/layout/LocaleSwitcher.tsx @@ -4,7 +4,7 @@ import { useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { useLocale } from 'next-intl'; -import { Locales, isLocale } from '@/types/locale'; +import { Locales, isLocale } from '@/lib/locale'; import { setLocale } from '@/actions/locale'; import { Button, diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index f84adfd68d..e62750e6d6 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -2,5 +2,6 @@ export { Navbar } from './Navbar'; export { Footer } from './Footer'; export { Section } from './Section'; export { LocaleSwitcher } from './LocaleSwitcher'; +export { ThemeProvider } from './ThemeProvider'; export { ThemeSwitcher } from './ThemeSwitcher'; export { Logo } from './Logo'; diff --git a/src/i18n/request.ts b/src/i18n/request.ts index 5ced133ae6..db49c6243d 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -1,5 +1,5 @@ import { cookies } from 'next/headers'; -import { isLocale } from '@/types/locale'; +import { isLocale } from '@/lib/locale'; import { getRequestConfig } from 'next-intl/server'; export default getRequestConfig(async (params) => { diff --git a/src/lib/locale.test.ts b/src/lib/locale.test.ts new file mode 100644 index 0000000000..6ef7477d73 --- /dev/null +++ b/src/lib/locale.test.ts @@ -0,0 +1,81 @@ +// npx vitest run src/lib/locale.test.ts + +import { enUS, frFR } from '@clerk/localizations'; + +import { locales, isLocale, Locales, getClerkLocale, Locale } from './locale'; + +describe('locale module', () => { + describe('locales', () => { + it('should contain supported locales', () => { + expect(locales).toEqual(['en', 'fr']); + }); + }); + + describe('isLocale', () => { + it('should return true for valid locales', () => { + expect(isLocale('en')).toBe(true); + expect(isLocale('fr')).toBe(true); + }); + + it('should return false for invalid locales', () => { + expect(isLocale('es')).toBe(false); + expect(isLocale('de')).toBe(false); + expect(isLocale('')).toBe(false); + expect(isLocale(undefined)).toBe(false); + }); + + it('should properly type-narrow the input', () => { + const testLocale = (locale: string | undefined): Locale | null => { + if (isLocale(locale)) { + const validLocale: Locale = locale; + return validLocale; + } + + return null; + }; + + expect(testLocale('en')).toBe('en'); + expect(testLocale('invalid')).toBe(null); + }); + }); + + describe('Locales', () => { + it('should map locale codes to display names', () => { + expect(Locales).toEqual({ + en: 'English', + fr: 'Français', + }); + }); + + it('should have an entry for each supported locale', () => { + locales.forEach((locale) => { + expect(Locales[locale]).toBeDefined(); + }); + }); + }); + + describe('getClerkLocale', () => { + it('should return enUS for "en" locale', () => { + expect(getClerkLocale('en')).toBe(enUS); + }); + + it('should return frFR for "fr" locale', () => { + expect(getClerkLocale('fr')).toBe(frFR); + }); + + it('should return enUS for invalid locales', () => { + expect(getClerkLocale('es')).toBe(enUS); + expect(getClerkLocale('de')).toBe(enUS); + expect(getClerkLocale('')).toBe(enUS); + // @ts-expect-error Testing with invalid type + expect(getClerkLocale(undefined)).toBe(enUS); + // @ts-expect-error Testing with invalid type + expect(getClerkLocale(null)).toBe(enUS); + }); + + it('should handle case sensitivity correctly', () => { + expect(getClerkLocale('EN')).toBe(enUS); + expect(getClerkLocale('Fr')).toBe(enUS); + }); + }); +}); diff --git a/src/types/locale.ts b/src/lib/locale.ts similarity index 54% rename from src/types/locale.ts rename to src/lib/locale.ts index bb181afaa0..ee8716c846 100644 --- a/src/types/locale.ts +++ b/src/lib/locale.ts @@ -1,3 +1,5 @@ +import { enUS, frFR } from '@clerk/localizations'; + export const locales = ['en', 'fr'] as const; export type Locale = (typeof locales)[number]; @@ -9,3 +11,16 @@ export const Locales: Record = { en: 'English', fr: 'Français', }; + +export const getClerkLocale = (locale: string) => { + if (!isLocale(locale)) { + return enUS; + } + + switch (locale) { + case 'fr': + return frFR; + default: + return enUS; + } +};