diff --git a/apps/e2e/package.json b/apps/e2e/package.json
index 18a06743d..8716f9086 100644
--- a/apps/e2e/package.json
+++ b/apps/e2e/package.json
@@ -12,7 +12,7 @@
"author": "",
"license": "ISC",
"devDependencies": {
- "@playwright/test": "^1.43.0",
+ "@playwright/test": "^1.43.1",
"@types/node": "^20.12.7",
"node-html-parser": "^6.1.13"
}
diff --git a/apps/web/app/(dashboard)/home/(user)/billing/_components/personal-account-checkout-form.tsx b/apps/web/app/(dashboard)/home/(user)/billing/_components/personal-account-checkout-form.tsx
index 7d69a3f36..cd419bd8e 100644
--- a/apps/web/app/(dashboard)/home/(user)/billing/_components/personal-account-checkout-form.tsx
+++ b/apps/web/app/(dashboard)/home/(user)/billing/_components/personal-account-checkout-form.tsx
@@ -2,9 +2,11 @@
import { useState, useTransition } from 'react';
+import dynamic from 'next/dynamic';
+
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
-import { EmbeddedCheckout, PlanPicker } from '@kit/billing-gateway/components';
+import { PlanPicker } from '@kit/billing-gateway/components';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import {
Card,
@@ -20,6 +22,19 @@ import billingConfig from '~/config/billing.config';
import { createPersonalAccountCheckoutSession } from '../server-actions';
+const EmbeddedCheckout = dynamic(
+ async () => {
+ const { EmbeddedCheckout } = await import('@kit/billing-gateway/checkout');
+
+ return {
+ default: EmbeddedCheckout,
+ };
+ },
+ {
+ ssr: false,
+ },
+);
+
export function PersonalAccountCheckoutForm(props: {
customerId: string | null | undefined;
}) {
diff --git a/apps/web/app/(dashboard)/home/[account]/billing/_components/team-account-checkout-form.tsx b/apps/web/app/(dashboard)/home/[account]/billing/_components/team-account-checkout-form.tsx
index cb2d9b21a..3ce782c03 100644
--- a/apps/web/app/(dashboard)/home/[account]/billing/_components/team-account-checkout-form.tsx
+++ b/apps/web/app/(dashboard)/home/[account]/billing/_components/team-account-checkout-form.tsx
@@ -2,9 +2,10 @@
import { useState, useTransition } from 'react';
+import dynamic from 'next/dynamic';
import { useParams } from 'next/navigation';
-import { EmbeddedCheckout, PlanPicker } from '@kit/billing-gateway/components';
+import { PlanPicker } from '@kit/billing-gateway/components';
import {
Card,
CardContent,
@@ -18,6 +19,19 @@ import billingConfig from '~/config/billing.config';
import { createTeamAccountCheckoutSession } from '../server-actions';
+const EmbeddedCheckout = dynamic(
+ async () => {
+ const { EmbeddedCheckout } = await import('@kit/billing-gateway/checkout');
+
+ return {
+ default: EmbeddedCheckout,
+ };
+ },
+ {
+ ssr: false,
+ },
+);
+
export function TeamAccountCheckoutForm(params: {
accountId: string;
customerId: string | null | undefined;
diff --git a/apps/web/components/root-providers.tsx b/apps/web/components/root-providers.tsx
index 546fbd61a..41f6d1f99 100644
--- a/apps/web/components/root-providers.tsx
+++ b/apps/web/components/root-providers.tsx
@@ -1,10 +1,12 @@
'use client';
+import dynamic from 'next/dynamic';
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental';
import { ThemeProvider } from 'next-themes';
-import { CaptchaProvider, CaptchaTokenSetter } from '@kit/auth/captcha/client';
+import { CaptchaProvider } from '@kit/auth/captcha/client';
import { I18nProvider } from '@kit/i18n/provider';
import { AuthChangeListener } from '@kit/supabase/components/auth-change-listener';
@@ -12,24 +14,39 @@ import appConfig from '~/config/app.config';
import authConfig from '~/config/auth.config';
import pathsConfig from '~/config/paths.config';
import { i18nResolver } from '~/lib/i18n/i18n.resolver';
+import { getI18nSettings } from '~/lib/i18n/i18n.settings';
const captchaSiteKey = authConfig.captchaTokenSiteKey;
const queryClient = new QueryClient();
+const CaptchaTokenSetter = dynamic(async () => {
+ if (!captchaSiteKey) {
+ return Promise.resolve(() => null);
+ }
+
+ const { CaptchaTokenSetter } = await import('@kit/auth/captcha/client');
+
+ return {
+ default: CaptchaTokenSetter,
+ };
+});
+
export function RootProviders({
lang,
children,
}: React.PropsWithChildren<{
lang: string;
}>) {
+ const i18nSettings = getI18nSettings(lang);
+
return (
-
-
+
+
+
-
-
+
{children}
-
-
-
+
+
+
);
diff --git a/apps/web/lib/i18n/i18n.resolver.ts b/apps/web/lib/i18n/i18n.resolver.ts
index fe750c69a..b65abf559 100644
--- a/apps/web/lib/i18n/i18n.resolver.ts
+++ b/apps/web/lib/i18n/i18n.resolver.ts
@@ -3,15 +3,9 @@
*
*/
export async function i18nResolver(language: string, namespace: string) {
- try {
- const { default: data } = await import(
- `../../public/locales/${language}/${namespace}.json`
+ const data = await import(
+ `../../public/locales/${language}/${namespace}.json`,
);
- return data as Record;
- } catch (e) {
- console.error('Could not load translation file', e);
-
- return {} as Record;
- }
+ return data as Record;
}
diff --git a/apps/web/lib/i18n/i18n.server.ts b/apps/web/lib/i18n/i18n.server.ts
index 74ad6146b..225db9f08 100644
--- a/apps/web/lib/i18n/i18n.server.ts
+++ b/apps/web/lib/i18n/i18n.server.ts
@@ -1,11 +1,16 @@
import { cookies, headers } from 'next/headers';
import {
- getLanguageCookie,
initializeServerI18n,
parseAcceptLanguageHeader,
} from '@kit/i18n/server';
+import {
+ I18N_COOKIE_NAME,
+ getI18nSettings,
+ languages,
+} from '~/lib/i18n/i18n.settings';
+
import { i18nResolver } from './i18n.resolver';
/**
@@ -18,10 +23,22 @@ import { i18nResolver } from './i18n.resolver';
*/
export function createI18nServerInstance() {
const acceptLanguage = headers().get('accept-language');
- const cookie = getLanguageCookie(cookies());
+ const cookie = cookies().get(I18N_COOKIE_NAME)?.value;
- const language =
- cookie ?? parseAcceptLanguageHeader(acceptLanguage)[0] ?? undefined;
+ let language =
+ cookie ??
+ parseAcceptLanguageHeader(acceptLanguage, languages)[0] ??
+ languages[0];
- return initializeServerI18n(language, i18nResolver);
+ if (!languages.includes(language ?? '')) {
+ console.warn(
+ `Language "${language}" is not supported. Falling back to "${languages[0]}"`,
+ );
+
+ language = languages[0];
+ }
+
+ const settings = getI18nSettings(language);
+
+ return initializeServerI18n(settings, i18nResolver);
}
diff --git a/apps/web/lib/i18n/i18n.settings.ts b/apps/web/lib/i18n/i18n.settings.ts
new file mode 100644
index 000000000..cb35e57eb
--- /dev/null
+++ b/apps/web/lib/i18n/i18n.settings.ts
@@ -0,0 +1,65 @@
+import { InitOptions } from 'i18next';
+
+/**
+ * The default language of the application.
+ * This is used as a fallback language when the selected language is not supported.
+ *
+ */
+const defaultLanguage = process.env.NEXT_PUBLIC_LOCALE ?? 'en';
+
+/**
+ * The list of supported languages.
+ * By default, only the default language is supported.
+ * Add more languages here if needed.
+ */
+export const languages: string[] = [defaultLanguage];
+
+/**
+ * The name of the cookie that stores the selected language.
+ */
+export const I18N_COOKIE_NAME = 'lang';
+
+/**
+ * The default array of Internationalization (i18n) namespaces.
+ * These namespaces are commonly used in the application for translation purposes.
+ *
+ * Add your own namespaces here
+ **/
+export const defaultI18nNamespaces = [
+ 'common',
+ 'auth',
+ 'account',
+ 'teams',
+ 'billing',
+ 'marketing',
+];
+
+/**
+ * Get the i18n settings for the given language and namespaces.
+ * If the language is not supported, it will fall back to the default language.
+ * @param language
+ * @param ns
+ */
+export function getI18nSettings(
+ language: string | undefined,
+ ns: string | string[] = defaultI18nNamespaces,
+): InitOptions {
+ let lng = language ?? defaultLanguage;
+
+ if (!languages.includes(lng)) {
+ console.warn(
+ `Language "${lng}" is not supported. Falling back to "${defaultLanguage}"`,
+ );
+
+ lng = defaultLanguage;
+ }
+
+ return {
+ supportedLngs: languages,
+ fallbackLng: defaultLanguage,
+ lng,
+ fallbackNS: defaultI18nNamespaces,
+ defaultNS: defaultI18nNamespaces,
+ ns,
+ };
+}
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 07a97b997..fa81dfbc3 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -24,7 +24,6 @@ const INTERNAL_PACKAGES = [
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
- swcMinify: true,
/** Enables hot reloading for local packages without a build step */
transpilePackages: INTERNAL_PACKAGES,
pageExtensions: ['ts', 'tsx'],
diff --git a/apps/web/package.json b/apps/web/package.json
index 1cff7b5d9..273613d86 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -7,7 +7,7 @@
"scripts": {
"analyze": "ANALYZE=true pnpm run build",
"build": "pnpm with-env next build",
- "build:test": "NODE_ENV=test next build",
+ "build:test": "pnpm with-env:test next build",
"clean": "git clean -xdf .next .turbo node_modules",
"dev": "pnpm with-env next dev --turbo",
"next:lint": "next lint",
@@ -54,7 +54,7 @@
"@supabase/supabase-js": "^2.42.3",
"@tanstack/react-query": "5.29.0",
"@tanstack/react-query-next-experimental": "^5.29.2",
- "@tanstack/react-table": "^8.15.3",
+ "@tanstack/react-table": "^8.16.0",
"date-fns": "^3.6.0",
"edge-csrf": "^1.0.9",
"i18next": "^23.11.1",
diff --git a/package.json b/package.json
index b69b5fba5..fceeafd8c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
{
"name": "next-supabase-saas-kit-turbo",
"private": true,
+ "sideEffects": false,
"engines": {
"node": ">=v18.17.1"
},
diff --git a/packages/billing/gateway/package.json b/packages/billing/gateway/package.json
index 27744ea62..fc3225d52 100644
--- a/packages/billing/gateway/package.json
+++ b/packages/billing/gateway/package.json
@@ -11,7 +11,8 @@
"prettier": "@kit/prettier-config",
"exports": {
".": "./src/index.ts",
- "./components": "./src/components/index.ts"
+ "./components": "./src/components/index.ts",
+ "./checkout": "./src/components/embedded-checkout.tsx"
},
"peerDependencies": {
"@hookform/resolvers": "3.3.4",
diff --git a/packages/billing/gateway/src/components/index.ts b/packages/billing/gateway/src/components/index.ts
index c673574f0..cb54e3025 100644
--- a/packages/billing/gateway/src/components/index.ts
+++ b/packages/billing/gateway/src/components/index.ts
@@ -1,7 +1,6 @@
export * from './plan-picker';
export * from './current-subscription-card';
export * from './current-lifetime-order-card';
-export * from './embedded-checkout';
export * from './billing-session-status';
export * from './billing-portal-card';
export * from './pricing-table';
diff --git a/packages/cms/keystatic/package.json b/packages/cms/keystatic/package.json
index 0821ffa0b..decd715a2 100644
--- a/packages/cms/keystatic/package.json
+++ b/packages/cms/keystatic/package.json
@@ -24,11 +24,11 @@
"zod": "^3.22.4"
},
"devDependencies": {
- "@kit/cms": "^0.1.0",
+ "@kit/cms": "workspace:^",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
- "@kit/ui": "^0.1.0",
+ "@kit/ui": "workspace:^",
"@types/node": "^20.12.7",
"zod": "^3.22.4"
},
diff --git a/packages/cms/wordpress/package.json b/packages/cms/wordpress/package.json
index 8320eeb93..bf85da340 100644
--- a/packages/cms/wordpress/package.json
+++ b/packages/cms/wordpress/package.json
@@ -18,11 +18,11 @@
"@kit/ui": "workspace:^"
},
"devDependencies": {
- "@kit/cms": "^0.1.0",
+ "@kit/cms": "workspace:^",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
- "@kit/ui": "^0.1.0",
+ "@kit/ui": "workspace:^",
"@types/node": "^20.12.7",
"wp-types": "^3.65.0"
},
diff --git a/packages/database-webhooks/package.json b/packages/database-webhooks/package.json
index a51045427..c3e087123 100644
--- a/packages/database-webhooks/package.json
+++ b/packages/database-webhooks/package.json
@@ -20,14 +20,14 @@
},
"devDependencies": {
"@kit/billing": "workspace:^",
- "@kit/billing-gateway": "^0.1.0",
+ "@kit/billing-gateway": "workspace:^",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/shared": "workspace:^",
"@kit/stripe": "workspace:^",
"@kit/supabase": "workspace:^",
"@kit/tailwind-config": "workspace:*",
- "@kit/team-accounts": "^0.1.0",
+ "@kit/team-accounts": "workspace:^",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:^",
"@supabase/supabase-js": "^2.42.3",
diff --git a/packages/features/accounts/package.json b/packages/features/accounts/package.json
index 5da31079a..298b5e8be 100644
--- a/packages/features/accounts/package.json
+++ b/packages/features/accounts/package.json
@@ -19,16 +19,16 @@
},
"devDependencies": {
"@hookform/resolvers": "^3.3.4",
- "@kit/billing-gateway": "^0.1.0",
- "@kit/email-templates": "^0.1.0",
+ "@kit/billing-gateway": "workspace:^",
+ "@kit/email-templates": "workspace:^",
"@kit/eslint-config": "workspace:*",
- "@kit/mailers": "^0.1.0",
+ "@kit/mailers": "workspace:^",
"@kit/prettier-config": "workspace:*",
- "@kit/shared": "^0.1.0",
- "@kit/supabase": "^0.1.0",
+ "@kit/shared": "workspace:^",
+ "@kit/supabase": "workspace:^",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
- "@kit/ui": "^0.1.0",
+ "@kit/ui": "workspace:^",
"@radix-ui/react-icons": "^1.3.0",
"@supabase/supabase-js": "^2.42.3",
"@tanstack/react-query": "5.29.0",
diff --git a/packages/features/admin/package.json b/packages/features/admin/package.json
index 245f6fa6d..239ab7f38 100644
--- a/packages/features/admin/package.json
+++ b/packages/features/admin/package.json
@@ -27,7 +27,7 @@
"devDependencies": {
"@hookform/resolvers": "^3.3.4",
"@kit/eslint-config": "workspace:*",
- "@kit/next": "^0.1.0",
+ "@kit/next": "workspace:^",
"@kit/prettier-config": "workspace:*",
"@kit/supabase": "workspace:^",
"@kit/tailwind-config": "workspace:*",
@@ -37,7 +37,7 @@
"@makerkit/data-loader-supabase-nextjs": "^1.0.0",
"@supabase/supabase-js": "^2.42.3",
"@tanstack/react-query": "5.29.0",
- "@tanstack/react-table": "^8.15.3",
+ "@tanstack/react-table": "^8.16.0",
"@types/react": "^18.2.77",
"lucide-react": "^0.367.0",
"next": "14.2.0",
diff --git a/packages/features/auth/package.json b/packages/features/auth/package.json
index d24ce4d6e..1826ad867 100644
--- a/packages/features/auth/package.json
+++ b/packages/features/auth/package.json
@@ -21,11 +21,11 @@
"@hookform/resolvers": "^3.3.4",
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
- "@kit/shared": "^0.1.0",
- "@kit/supabase": "^0.1.0",
+ "@kit/shared": "workspace:^",
+ "@kit/supabase": "workspace:^",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
- "@kit/ui": "^0.1.0",
+ "@kit/ui": "workspace:^",
"@marsidev/react-turnstile": "^0.5.4",
"@radix-ui/react-icons": "^1.3.0",
"@supabase/supabase-js": "^2.42.3",
diff --git a/packages/features/team-accounts/package.json b/packages/features/team-accounts/package.json
index b12776ead..53b7ea95f 100644
--- a/packages/features/team-accounts/package.json
+++ b/packages/features/team-accounts/package.json
@@ -17,20 +17,20 @@
},
"devDependencies": {
"@hookform/resolvers": "^3.3.4",
- "@kit/accounts": "^0.1.0",
+ "@kit/accounts": "workspace:^",
"@kit/billing-gateway": "workspace:*",
- "@kit/email-templates": "^0.1.0",
+ "@kit/email-templates": "workspace:^",
"@kit/eslint-config": "workspace:*",
- "@kit/mailers": "^0.1.0",
+ "@kit/mailers": "workspace:^",
"@kit/prettier-config": "workspace:*",
- "@kit/shared": "^0.1.0",
- "@kit/supabase": "^0.1.0",
+ "@kit/shared": "workspace:^",
+ "@kit/supabase": "workspace:^",
"@kit/tailwind-config": "workspace:*",
"@kit/tsconfig": "workspace:*",
- "@kit/ui": "^0.1.0",
+ "@kit/ui": "workspace:^",
"@supabase/supabase-js": "^2.42.3",
"@tanstack/react-query": "5.29.0",
- "@tanstack/react-table": "^8.15.3",
+ "@tanstack/react-table": "^8.16.0",
"@types/react": "^18.2.77",
"@types/react-dom": "^18.2.25",
"class-variance-authority": "^0.7.0",
diff --git a/packages/i18n/src/i18n-provider.tsx b/packages/i18n/src/i18n-provider.tsx
index ac4696088..ee1050f4e 100644
--- a/packages/i18n/src/i18n-provider.tsx
+++ b/packages/i18n/src/i18n-provider.tsx
@@ -1,6 +1,6 @@
'use client';
-import type { i18n } from 'i18next';
+import type { InitOptions, i18n } from 'i18next';
let client: i18n;
@@ -10,28 +10,28 @@ type Resolver = (
) => Promise>;
export function I18nProvider({
- lang,
+ settings,
children,
resolver,
}: React.PropsWithChildren<{
- lang: string;
+ settings: InitOptions;
resolver: Resolver;
}>) {
if (!client) {
- throw withI18nClient(lang, resolver);
+ throw withI18nClient(settings, resolver);
}
return children;
}
-async function withI18nClient(lang: string, resolver: Resolver) {
+async function withI18nClient(settings: InitOptions, resolver: Resolver) {
if (typeof window !== 'undefined') {
const { initializeI18nClient } = await import('./i18n.client');
- client = await initializeI18nClient(lang, resolver);
+ client = await initializeI18nClient(settings, resolver);
} else {
const { initializeServerI18n } = await import('./i18n.server');
- client = await initializeServerI18n(lang, resolver);
+ client = await initializeServerI18n(settings, resolver);
}
}
diff --git a/packages/i18n/src/i18n.client.ts b/packages/i18n/src/i18n.client.ts
index e729680c7..072d68d7c 100644
--- a/packages/i18n/src/i18n.client.ts
+++ b/packages/i18n/src/i18n.client.ts
@@ -1,26 +1,23 @@
-import i18next, { i18n } from 'i18next';
+import i18next, { type InitOptions, i18n } from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import resourcesToBackend from 'i18next-resources-to-backend';
import { initReactI18next } from 'react-i18next';
-import { I18N_COOKIE_NAME, getI18nSettings } from './i18n.settings';
-
+/**
+ * Initialize the i18n instance on the client.
+ * @param settings - the i18n settings
+ * @param resolver - a function that resolves the i18n resources
+ */
export function initializeI18nClient(
- lng: string | undefined,
- i18nResolver: (lang: string, namespace: string) => Promise