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);