diff --git a/apps/web/app/(marketing)/(legal)/cookie-policy/page.tsx b/apps/web/app/(marketing)/(legal)/cookie-policy/page.tsx
index fe40b667b..f7257871f 100644
--- a/apps/web/app/(marketing)/(legal)/cookie-policy/page.tsx
+++ b/apps/web/app/(marketing)/(legal)/cookie-policy/page.tsx
@@ -1,12 +1,23 @@
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
-function CookiePolicyPage() {
+export async function generateMetadata() {
+ const { t } = await createI18nServerInstance();
+
+ return {
+ title: t('marketing.cookiePolicy'),
+ };
+}
+
+async function CookiePolicyPage() {
+ const { t } = await createI18nServerInstance();
+
return (
diff --git a/apps/web/app/(marketing)/(legal)/privacy-policy/page.tsx b/apps/web/app/(marketing)/(legal)/privacy-policy/page.tsx
index d8f3d94f5..3fbff2c0e 100644
--- a/apps/web/app/(marketing)/(legal)/privacy-policy/page.tsx
+++ b/apps/web/app/(marketing)/(legal)/privacy-policy/page.tsx
@@ -1,11 +1,22 @@
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
-function PrivacyPolicyPage() {
+export async function generateMetadata() {
+ const { t } = await createI18nServerInstance();
+
+ return {
+ title: t('marketing.privacyPolicy'),
+ };
+}
+
+async function PrivacyPolicyPage() {
+ const { t } = await createI18nServerInstance();
+
return (
);
diff --git a/apps/web/app/(marketing)/(legal)/terms-of-service/page.tsx b/apps/web/app/(marketing)/(legal)/terms-of-service/page.tsx
index 4993eaede..d81cfdbe9 100644
--- a/apps/web/app/(marketing)/(legal)/terms-of-service/page.tsx
+++ b/apps/web/app/(marketing)/(legal)/terms-of-service/page.tsx
@@ -1,11 +1,22 @@
import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
-function TermsOfServicePage() {
+export async function generateMetadata() {
+ const { t } = await createI18nServerInstance();
+
+ return {
+ title: t('marketing.termsOfService'),
+ };
+}
+
+async function TermsOfServicePage() {
+ const { t } = await createI18nServerInstance();
+
return (
);
diff --git a/apps/web/app/(marketing)/_components/site-footer.tsx b/apps/web/app/(marketing)/_components/site-footer.tsx
index a6df3ab6d..279cedaec 100644
--- a/apps/web/app/(marketing)/_components/site-footer.tsx
+++ b/apps/web/app/(marketing)/_components/site-footer.tsx
@@ -90,7 +90,7 @@ export function SiteFooter() {
-
+
diff --git a/apps/web/app/(marketing)/contact/page.tsx b/apps/web/app/(marketing)/contact/page.tsx
new file mode 100644
index 000000000..bfb60c67c
--- /dev/null
+++ b/apps/web/app/(marketing)/contact/page.tsx
@@ -0,0 +1,25 @@
+import { SitePageHeader } from '~/(marketing)/_components/site-page-header';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
+import { withI18n } from '~/lib/i18n/with-i18n';
+
+export async function generateMetadata() {
+ const { t } = await createI18nServerInstance();
+
+ return {
+ title: t('marketing.contact'),
+ };
+}
+
+async function ContactPage() {
+ const { t } = await createI18nServerInstance();
+
+ return (
+
+ );
+}
+
+export default withI18n(ContactPage);
diff --git a/apps/web/app/server-sitemap.xml/route.ts b/apps/web/app/server-sitemap.xml/route.ts
index ed6756b41..b4e3ecb3b 100644
--- a/apps/web/app/server-sitemap.xml/route.ts
+++ b/apps/web/app/server-sitemap.xml/route.ts
@@ -24,7 +24,14 @@ export async function GET() {
}
function getSiteUrls() {
- const urls = ['/', 'faq', 'pricing'];
+ const urls = [
+ '/',
+ '/faq',
+ '/pricing',
+ '/contact',
+ '/terms-of-service',
+ '/privacy-policy',
+ ];
return urls.map((url) => {
return {
diff --git a/apps/web/app/update-password/page.tsx b/apps/web/app/update-password/page.tsx
index 59f99188d..af47c17a4 100644
--- a/apps/web/app/update-password/page.tsx
+++ b/apps/web/app/update-password/page.tsx
@@ -7,8 +7,17 @@ import { getSupabaseServerComponentClient } from '@kit/supabase/server-component
import { AppLogo } from '~/components/app-logo';
import pathsConfig from '~/config/paths.config';
+import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import { withI18n } from '~/lib/i18n/with-i18n';
+export const generateMetadata = async () => {
+ const { t } = await createI18nServerInstance();
+
+ return {
+ title: t('auth.updatePassword'),
+ };
+};
+
async function PasswordResetPage() {
const client = getSupabaseServerComponentClient();
const auth = await requireUser(client);
diff --git a/apps/web/public/locales/en/account.json b/apps/web/public/locales/en/account.json
index 7380ca916..cd64b82a6 100644
--- a/apps/web/public/locales/en/account.json
+++ b/apps/web/public/locales/en/account.json
@@ -5,18 +5,12 @@
"emailTabTabSubheading": "Update your email address",
"passwordTab": "Password",
"passwordTabSubheading": "Update your password",
- "manageConnectedAccounts": "Connected Accounts",
- "manageConnectedAccountsSubheading": "Manage your connected accounts",
- "connectedAccounts": "Connected Accounts",
"homePage": "Home",
"billingTab": "Billing",
"settingsTab": "Settings",
"authenticationTab": "Authentication",
"multiFactorAuth": "Multi-Factor Authentication",
- "multiFactorAuthDescription": "Set up Multi-Factor Authentication method to further secure your account",
- "connectedAccountsSubheading": "Below are the accounts linked to your profile",
- "availableProviders": "Available Providers",
- "availableProvidersSubheading": "Click on the providers below to link your profile to the provider",
+ "multiFactorAuthDescription": "Set up Multi-Factor Authentication method to further secure your account",
"updateProfileSuccess": "Profile successfully updated",
"updateProfileError": "Encountered an error. Please try again",
"updatePasswordSuccess": "Password update request successful",
@@ -141,5 +135,7 @@
"deleteProfileConfirmationInputLabel": "Type DELETE to confirm",
"deleteAccountErrorHeading": "Sorry, we couldn't delete your account",
"needsReauthentication": "Reauthentication Required",
- "needsReauthenticationDescription": "You need to reauthenticate to change your password. Please sign out and sign in again to change your password."
+ "needsReauthenticationDescription": "You need to reauthenticate to change your password. Please sign out and sign in again to change your password.",
+ "language": "Language",
+ "languageDescription": "Choose your preferred language"
}
diff --git a/apps/web/public/locales/en/auth.json b/apps/web/public/locales/en/auth.json
index 1c002aae6..6a25d5626 100644
--- a/apps/web/public/locales/en/auth.json
+++ b/apps/web/public/locales/en/auth.json
@@ -4,6 +4,7 @@
"signInHeading": "Sign in to your account",
"signIn": "Sign In",
"getStarted": "Get started",
+ "updatePassword": "Update Password",
"signOut": "Sign out",
"signingIn": "Signing in...",
"signingUp": "Signing up...",
diff --git a/apps/web/public/locales/en/marketing.json b/apps/web/public/locales/en/marketing.json
index 03a472806..31edb3330 100644
--- a/apps/web/public/locales/en/marketing.json
+++ b/apps/web/public/locales/en/marketing.json
@@ -14,7 +14,7 @@
"about": "About",
"product": "Product",
"legal": "Legal",
- "tos": "Terms of Service",
+ "termsOfService": "Terms of Service",
"cookiePolicy": "Cookie Policy",
"privacyPolicy": "Privacy Policy"
}
diff --git a/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx b/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx
index c1d3f8c92..63658a7f0 100644
--- a/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx
+++ b/packages/features/accounts/src/components/personal-account-settings/account-settings-container.tsx
@@ -1,5 +1,7 @@
'use client';
+import { useTranslation } from 'react-i18next';
+
import {
Card,
CardContent,
@@ -8,6 +10,7 @@ import {
CardTitle,
} from '@kit/ui/card';
import { If } from '@kit/ui/if';
+import { LanguageSelector } from '@kit/ui/language-selector';
import { Trans } from '@kit/ui/trans';
import { AccountDangerZone } from './account-danger-zone';
@@ -28,6 +31,8 @@ export function PersonalAccountSettingsContainer(
};
}>,
) {
+ const supportsLanguageSelection = useSupportMultiLanguage();
+
return (
@@ -62,6 +67,24 @@ export function PersonalAccountSettingsContainer(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -130,3 +153,8 @@ export function PersonalAccountSettingsContainer(
);
}
+
+function useSupportMultiLanguage() {
+ const { i18n } = useTranslation();
+ return i18n.options.supportedLngs && i18n.options.supportedLngs.length > 1;
+}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 197dbf47e..0f4864603 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -124,7 +124,8 @@
"./loading-overlay": "./src/makerkit/loading-overlay.tsx",
"./profile-avatar": "./src/makerkit/profile-avatar.tsx",
"./mode-toggle": "./src/makerkit/mode-toggle.tsx",
- "./enhanced-data-table": "./src/makerkit/data-table.tsx"
+ "./enhanced-data-table": "./src/makerkit/data-table.tsx",
+ "./language-selector": "./src/makerkit/language-selector.tsx"
},
"typesVersions": {
"*": {
diff --git a/packages/ui/src/makerkit/language-dropdown-switcher.tsx b/packages/ui/src/makerkit/language-selector.tsx
similarity index 87%
rename from packages/ui/src/makerkit/language-dropdown-switcher.tsx
rename to packages/ui/src/makerkit/language-selector.tsx
index 275a88457..848833cc1 100644
--- a/packages/ui/src/makerkit/language-dropdown-switcher.tsx
+++ b/packages/ui/src/makerkit/language-selector.tsx
@@ -2,8 +2,6 @@
import { useCallback, useMemo, useState } from 'react';
-import { useRouter } from 'next/navigation';
-
import { useTranslation } from 'react-i18next';
import {
@@ -14,11 +12,12 @@ import {
SelectValue,
} from '../shadcn/select';
-export const LanguageDropdownSwitcher: React.FC<{
+export function LanguageSelector({
+ onChange,
+}: {
onChange?: (locale: string) => unknown;
-}> = ({ onChange }) => {
+}) {
const { i18n } = useTranslation();
- const router = useRouter();
const { language: currentLanguage, options } = i18n;
@@ -43,10 +42,8 @@ export const LanguageDropdownSwitcher: React.FC<{
}
await i18n.changeLanguage(locale);
-
- return router.refresh();
},
- [i18n, onChange, router],
+ [i18n, onChange],
);
return (
@@ -73,7 +70,7 @@ export const LanguageDropdownSwitcher: React.FC<{
);
-};
+}
function capitalize(lang: string) {
return lang.slice(0, 1).toUpperCase() + lang.slice(1);