feat: add language switcher (#1337)

## Description

Web changes:

- Enabled i18n for web
- Add option to change language in command menu
- Add option to change language in menu-switcher

Web and marketing changes:

- Stop setting 'en' preference into cookie if the user's language is not
supported
- Dropped middleware changes
- Rotated cookie from 'i18n' to 'language'

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced a language switcher in the footer for improved language
selection.
	- Added dynamic language change functionality in the command menu.
- Implemented a dropdown menu item for quick access to the language
switcher.

- **Bug Fixes**
- Resolved issues related to language change notifications and state
management.

- **Translations**
- Added new translation entries for improved language support, including
"Search languages..." in English and German.
	- Updated existing translations to enhance clarity and accuracy.

- **Chores**
	- Simplified internationalization handling in middleware.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: github-actions <github-actions@documenso.com>
This commit is contained in:
David Nguyen
2024-09-11 13:22:43 +10:00
committed by GitHub
parent 7b06b68572
commit 357bdd374f
18 changed files with 241 additions and 196 deletions

View File

@ -1,6 +1,6 @@
'use client';
import type { HTMLAttributes } from 'react';
import { type HTMLAttributes, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
@ -9,15 +9,15 @@ import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { FaXTwitter } from 'react-icons/fa6';
import { LiaDiscord } from 'react-icons/lia';
import { LuGithub } from 'react-icons/lu';
import { LuGithub, LuLanguages } from 'react-icons/lu';
import LogoImage from '@documenso/assets/logo.png';
import { cn } from '@documenso/ui/lib/utils';
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
import { I18nSwitcher } from '~/components/(marketing)/i18n-switcher';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
// import { StatusWidgetContainer } from './status-widget-container';
import { LanguageSwitcherDialog } from '@documenso/ui/components/common/language-switcher-dialog';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { ThemeSwitcher } from '@documenso/ui/primitives/theme-switcher';
export type FooterProps = HTMLAttributes<HTMLDivElement>;
@ -44,7 +44,9 @@ const FOOTER_LINKS = [
];
export const Footer = ({ className, ...props }: FooterProps) => {
const { _ } = useLingui();
const { _, i18n } = useLingui();
const [languageSwitcherOpen, setLanguageSwitcherOpen] = useState(false);
return (
<div className={cn('border-t py-12', className)} {...props}>
@ -97,13 +99,22 @@ export const Footer = ({ className, ...props }: FooterProps) => {
</p>
<div className="flex flex-row-reverse items-center sm:flex-row">
<I18nSwitcher className="text-muted-foreground ml-2 rounded-full font-normal sm:mr-2" />
<Button
className="text-muted-foreground ml-2 rounded-full font-normal sm:mr-2"
variant="ghost"
onClick={() => setLanguageSwitcherOpen(true)}
>
<LuLanguages className="mr-1.5 h-4 w-4" />
{SUPPORTED_LANGUAGES[i18n.locale]?.full || i18n.locale}
</Button>
<div className="flex flex-wrap">
<ThemeSwitcher />
</div>
</div>
</div>
<LanguageSwitcherDialog open={languageSwitcherOpen} setOpen={setLanguageSwitcherOpen} />
</div>
);
};

View File

@ -1,71 +0,0 @@
import { useState } from 'react';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { CheckIcon } from 'lucide-react';
import { LuLanguages } from 'react-icons/lu';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { switchI18NLanguage } from '@documenso/lib/server-only/i18n/switch-i18n-language';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import {
CommandDialog,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@documenso/ui/primitives/command';
type I18nSwitcherProps = {
className?: string;
};
export const I18nSwitcher = ({ className }: I18nSwitcherProps) => {
const { i18n, _ } = useLingui();
const [open, setOpen] = useState(false);
const [value, setValue] = useState(i18n.locale);
const setLanguage = async (lang: string) => {
setValue(lang);
setOpen(false);
await dynamicActivate(i18n, lang);
await switchI18NLanguage(lang);
};
return (
<>
<Button className={className} variant="ghost" onClick={() => setOpen(true)}>
<LuLanguages className="mr-1.5 h-4 w-4" />
{SUPPORTED_LANGUAGES[value]?.full || i18n.locale}
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder={_(msg`Search languages...`)} />
<CommandList>
<CommandGroup>
{Object.values(SUPPORTED_LANGUAGES).map((language) => (
<CommandItem
key={language.short}
value={language.full}
onSelect={async () => setLanguage(language.short)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
value === language.short ? 'opacity-100' : 'opacity-0',
)}
/>
{SUPPORTED_LANGUAGES[language.short].full}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
</>
);
};

View File

@ -1,39 +0,0 @@
import { cookies } from 'next/headers';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { extractLocaleData } from '@documenso/lib/utils/i18n';
export default function middleware(req: NextRequest) {
const { lang } = extractLocaleData({
headers: req.headers,
cookies: cookies(),
});
const response = NextResponse.next();
response.cookies.set('i18n', lang);
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - ingest (analytics)
* - site.webmanifest
*/
{
source: '/((?!api|_next/static|_next/image|ingest|favicon|site.webmanifest).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
};

View File

@ -7,10 +7,11 @@ import { useRouter } from 'next/navigation';
import type { MessageDescriptor } from '@lingui/core';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Loader, Monitor, Moon, Sun } from 'lucide-react';
import { CheckIcon, Loader, Monitor, Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { useHotkeys } from 'react-hotkeys-hook';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import {
DOCUMENTS_PAGE_SHORTCUT,
SETTINGS_PAGE_SHORTCUT,
@ -20,7 +21,10 @@ import {
DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
SKIP_QUERY_BATCH_META,
} from '@documenso/lib/constants/trpc';
import { switchI18NLanguage } from '@documenso/lib/server-only/i18n/switch-i18n-language';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import {
CommandDialog,
CommandEmpty,
@ -31,6 +35,7 @@ import {
CommandShortcut,
} from '@documenso/ui/primitives/command';
import { THEMES_TYPE } from '@documenso/ui/primitives/constants';
import { useToast } from '@documenso/ui/primitives/use-toast';
const DOCUMENTS_PAGES = [
{
@ -207,6 +212,9 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
<Commands push={push} pages={SETTINGS_PAGES} />
</CommandGroup>
<CommandGroup className="mx-2 p-0 pb-2" heading={_(msg`Preferences`)}>
<CommandItem className="-mx-2 -my-1 rounded-lg" onSelect={() => addPage('language')}>
Change language
</CommandItem>
<CommandItem className="-mx-2 -my-1 rounded-lg" onSelect={() => addPage('theme')}>
Change theme
</CommandItem>
@ -218,7 +226,9 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
)}
</>
)}
{currentPage === 'theme' && <ThemeCommands setTheme={setTheme} />}
{currentPage === 'language' && <LanguageCommands />}
</CommandList>
</CommandDialog>
);
@ -269,3 +279,46 @@ const ThemeCommands = ({ setTheme }: { setTheme: (_theme: string) => void }) =>
</CommandItem>
));
};
const LanguageCommands = () => {
const { i18n, _ } = useLingui();
const { toast } = useToast();
const [isLoading, setIsLoading] = useState(false);
const setLanguage = async (lang: string) => {
if (isLoading || lang === i18n.locale) {
return;
}
setIsLoading(true);
try {
await dynamicActivate(i18n, lang);
await switchI18NLanguage(lang);
} catch (err) {
toast({
title: _(msg`An unknown error occurred`),
variant: 'destructive',
description: _(msg`Unable to change the language at this time. Please try again later.`),
});
}
setIsLoading(false);
};
return Object.values(SUPPORTED_LANGUAGES).map((language) => (
<CommandItem
disabled={isLoading}
key={language.full}
onSelect={async () => setLanguage(language.short)}
className="-my-1 mx-2 rounded-lg first:mt-2 last:mb-2"
>
<CheckIcon
className={cn('mr-2 h-4 w-4', i18n.locale === language.short ? 'opacity-100' : 'opacity-0')}
/>
{language.full}
</CommandItem>
));
};

View File

@ -1,5 +1,7 @@
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
@ -17,6 +19,7 @@ import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
import type { User } from '@documenso/prisma/client';
import { trpc } from '@documenso/trpc/react';
import { LanguageSwitcherDialog } from '@documenso/ui/components/common/language-switcher-dialog';
import { cn } from '@documenso/ui/lib/utils';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
@ -41,6 +44,8 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
const pathname = usePathname();
const [languageSwitcherOpen, setLanguageSwitcherOpen] = useState(false);
const isUserAdmin = isAdmin(user);
const { data: teamsQueryResult } = trpc.team.getTeams.useQuery(undefined, {
@ -274,6 +279,13 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
</DropdownMenuItem>
)}
<DropdownMenuItem
className="text-muted-foreground px-4 py-2"
onClick={() => setLanguageSwitcherOpen(true)}
>
<Trans>Language</Trans>
</DropdownMenuItem>
<DropdownMenuItem
className="text-destructive/90 hover:!text-destructive px-4 py-2"
onSelect={async () =>
@ -285,6 +297,8 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
<Trans>Sign Out</Trans>
</DropdownMenuItem>
</DropdownMenuContent>
<LanguageSwitcherDialog open={languageSwitcherOpen} setOpen={setLanguageSwitcherOpen} />
</DropdownMenu>
);
};

View File

@ -5,7 +5,6 @@ import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
import { TEAM_URL_ROOT_REGEX } from '@documenso/lib/constants/teams';
import { extractLocaleData } from '@documenso/lib/utils/i18n';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
async function middleware(req: NextRequest): Promise<NextResponse> {
@ -96,12 +95,7 @@ async function middleware(req: NextRequest): Promise<NextResponse> {
export default async function middlewareWrapper(req: NextRequest) {
const response = await middleware(req);
const { lang } = extractLocaleData({
headers: req.headers,
cookies: cookies(),
});
response.cookies.set('i18n', lang);
// Can place anything that needs to be set on the response here.
return response;
}

View File

@ -10,7 +10,7 @@ export const NEXT_PRIVATE_INTERNAL_WEBAPP_URL = process.env.NEXT_PRIVATE_INTERNA
export const IS_APP_MARKETING = process.env.NEXT_PUBLIC_PROJECT === 'marketing';
export const IS_APP_WEB = process.env.NEXT_PUBLIC_PROJECT === 'web';
export const IS_BILLING_ENABLED = () => env('NEXT_PUBLIC_FEATURE_BILLING_ENABLED') === 'true';
export const IS_APP_WEB_I18N_ENABLED = false;
export const IS_APP_WEB_I18N_ENABLED = true;
export const APP_FOLDER = () => (IS_APP_MARKETING ? 'marketing' : 'web');

View File

@ -4,5 +4,5 @@ import { cookies } from 'next/headers';
// eslint-disable-next-line @typescript-eslint/require-await
export const switchI18NLanguage = async (lang: string) => {
cookies().set('i18n', lang);
cookies().set('language', lang);
};

View File

@ -522,6 +522,10 @@ msgstr "Speichern"
msgid "Save Template"
msgstr "Vorlage speichern"
#: packages/ui/components/common/language-switcher-dialog.tsx:34
msgid "Search languages..."
msgstr "Sprachen suchen..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
msgid "Select"
msgstr "Auswählen"

View File

@ -430,8 +430,8 @@ msgid "Save $60 or $120"
msgstr "Sparen Sie $60 oder $120"
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
msgid "Search languages..."
msgstr "Sprachen suchen..."
#~ msgid "Search languages..."
#~ msgstr "Sprachen suchen..."
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."

File diff suppressed because one or more lines are too long

View File

@ -303,7 +303,7 @@ msgstr "Das Hinzufügen und Entfernen von Sitzplätzen wird Ihre Rechnung entspr
msgid "Admin Actions"
msgstr "Admin-Aktionen"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:257
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:262
msgid "Admin panel"
msgstr "Admin-Panel"
@ -311,7 +311,7 @@ msgstr "Admin-Panel"
msgid "All"
msgstr "Alle"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:37
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:42
msgid "All documents"
msgstr "Alle Dokumente"
@ -327,7 +327,7 @@ msgstr "Alle eingefügten Unterschriften werden annulliert"
msgid "All recipients will be notified"
msgstr "Alle Empfänger werden benachrichtigt"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:52
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:57
msgid "All templates"
msgstr "Alle Vorlagen"
@ -481,6 +481,7 @@ msgstr "Ein Fehler ist aufgetreten, während dein Dokument hochgeladen wurde."
#: apps/web/src/app/(dashboard)/settings/profile/delete-account-dialog.tsx:63
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:98
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-transfer-status.tsx:54
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:301
#: apps/web/src/components/(dashboard)/settings/token/delete-token-dialog.tsx:97
#: apps/web/src/components/(dashboard)/settings/webhooks/delete-webhook-dialog.tsx:88
#: apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx:100
@ -793,7 +794,7 @@ msgstr "Betrachten abschließen"
msgid "Completed"
msgstr "Abgeschlossen"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:43
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:48
msgid "Completed documents"
msgstr "Abgeschlossene Dokumente"
@ -927,8 +928,8 @@ msgstr "Jetzt erstellen"
msgid "Create one automatically"
msgstr "Einen automatisch erstellen"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:176
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:246
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:181
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:251
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:138
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:146
msgid "Create team"
@ -1006,7 +1007,7 @@ msgstr "Aktueller Plan: {0}"
msgid "Daily"
msgstr "Täglich"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:255
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:265
msgid "Dark Mode"
msgstr "Dunkelmodus"
@ -1316,7 +1317,7 @@ msgstr "Dokument wird dauerhaft gelöscht"
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:110
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166
#: apps/web/src/app/not-found.tsx:21
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:200
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205
#: apps/web/src/components/(dashboard)/layout/desktop-nav.tsx:18
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:35
#: apps/web/src/components/ui/user-profile-timur.tsx:60
@ -1358,7 +1359,7 @@ msgstr "Zertifikat herunterladen"
msgid "Draft"
msgstr "Entwurf"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:41
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:46
msgid "Draft documents"
msgstr "Entwurfdokumente"
@ -1683,7 +1684,7 @@ msgstr "Wenn Ihre Authenticator-App keine QR-Codes unterstützt, können Sie sta
msgid "Inbox"
msgstr "Posteingang"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:47
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:52
msgid "Inbox documents"
msgstr "Posteingang Dokumente"
@ -1773,6 +1774,10 @@ msgstr "Es scheint, dass kein Token bereitgestellt wurde. Wenn Sie versuchen, Ih
msgid "It seems that there is no token provided. Please check your email and try again."
msgstr "Es scheint, dass kein Token bereitgestellt wurde. Bitte überprüfen Sie Ihre E-Mail und versuchen Sie es erneut."
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:286
msgid "Language"
msgstr ""
#: apps/web/src/components/(dashboard)/period-selector/period-selector.tsx:61
msgid "Last 14 days"
msgstr "Die letzten 14 Tage"
@ -1810,7 +1815,7 @@ msgstr "Verlassen"
msgid "Leave team"
msgstr "Team verlassen"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:254
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:264
msgid "Light Mode"
msgstr "Lichtmodus"
@ -1900,7 +1905,7 @@ msgstr "Abonnements verwalten"
msgid "Manage team subscription."
msgstr "Teamabonnement verwalten."
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:163
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:168
msgid "Manage teams"
msgstr "Teams verwalten"
@ -2054,7 +2059,7 @@ msgstr "Keine aktuellen Aktivitäten"
msgid "No recipients"
msgstr "Keine Empfänger"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:195
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:200
msgid "No results found."
msgstr "Keine Ergebnisse gefunden."
@ -2146,7 +2151,7 @@ msgid "Otherwise, the document will be created as a draft."
msgstr "Andernfalls wird das Dokument als Entwurf erstellt."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:86
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:76
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:81
msgid "Owner"
msgstr "Besitzer"
@ -2191,7 +2196,7 @@ msgstr "Passkeys ermöglichen das Anmelden und die Authentifizierung mit biometr
msgid "Passkeys are not supported on this browser"
msgstr "Passkeys werden von diesem Browser nicht unterstützt"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:65
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:70
#: apps/web/src/components/forms/password.tsx:123
#: apps/web/src/components/forms/reset-password.tsx:110
#: apps/web/src/components/forms/signin.tsx:344
@ -2225,7 +2230,7 @@ msgstr "Zahlung überfällig"
msgid "Pending"
msgstr "Ausstehend"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:46
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:51
msgid "Pending documents"
msgstr "Ausstehende Dokumente"
@ -2241,11 +2246,11 @@ msgstr "Ausstehende Einladungen"
msgid "Pending team deleted."
msgstr "Ausstehendes Team gelöscht."
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:129
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:134
msgid "Personal"
msgstr "Persönlich"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:72
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:77
msgid "Personal Account"
msgstr "Persönliches Konto"
@ -2329,7 +2334,7 @@ msgstr "Bitte versuchen Sie es später noch einmal."
msgid "Please type <0>{0}</0> to confirm."
msgstr "Bitte geben Sie <0>{0}</0> ein, um zu bestätigen."
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:209
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:214
msgid "Preferences"
msgstr "Einstellungen"
@ -2347,7 +2352,7 @@ msgid "Private templates can only be modified and viewed by you."
msgstr "Private Vorlagen können nur von Ihnen bearbeitet und angezeigt werden."
#: apps/web/src/app/(dashboard)/settings/profile/page.tsx:28
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:64
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:69
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:36
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:39
msgid "Profile"
@ -2666,8 +2671,8 @@ msgid "Set a password"
msgstr "Ein Passwort festlegen"
#: apps/web/src/app/(dashboard)/settings/layout.tsx:20
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:60
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:206
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:65
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:211
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:47
msgid "Settings"
msgstr "Einstellungen"
@ -2751,7 +2756,7 @@ msgstr "Einloggen"
msgid "Sign in to your account"
msgstr "Melden Sie sich bei Ihrem Konto an"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:285
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:297
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:84
msgid "Sign Out"
msgstr "Ausloggen"
@ -2957,7 +2962,7 @@ msgstr "Erfolg"
msgid "Successfully created passkey"
msgstr "Passkey erfolgreich erstellt"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:256
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:266
msgid "System Theme"
msgstr "Systemthema"
@ -3042,7 +3047,7 @@ msgstr "Team-Eigentum übertragen!"
msgid "Team Public Profile"
msgstr "Öffentliches Profil des Teams"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:272
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:277
msgid "Team settings"
msgstr "Teameinstellungen"
@ -3068,7 +3073,7 @@ msgid "Team URL"
msgstr "Team-URL"
#: apps/web/src/app/(dashboard)/settings/teams/page.tsx:25
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:157
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:162
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:43
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:64
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:67
@ -3114,7 +3119,7 @@ msgstr "Template saved"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view.tsx:60
#: apps/web/src/app/(dashboard)/templates/templates-page-view.tsx:55
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:203
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:208
#: apps/web/src/components/(dashboard)/layout/desktop-nav.tsx:22
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:39
msgid "Templates"
@ -3522,7 +3527,7 @@ msgstr "Type"
msgid "Type 'delete' to confirm"
msgstr "Type 'delete' to confirm"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:181
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:186
msgid "Type a command or search..."
msgstr "Type a command or search..."
@ -3530,6 +3535,10 @@ msgstr "Type a command or search..."
msgid "Uh oh! Looks like you're missing a token"
msgstr "Uh oh! Looks like you're missing a token"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:303
msgid "Unable to change the language at this time. Please try again later."
msgstr ""
#: apps/web/src/components/forms/2fa/recovery-code-list.tsx:31
msgid "Unable to copy recovery code"
msgstr "Unable to copy recovery code"
@ -3728,7 +3737,7 @@ msgstr "Benutzer-ID"
msgid "User profiles are here!"
msgstr "Benutzerprofile sind hier!"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:264
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:269
msgid "User settings"
msgstr "Benutzereinstellungen"
@ -4360,7 +4369,7 @@ msgstr "Ihr Dokument wurde erfolgreich hochgeladen."
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
msgstr "Ihr Dokument wurde erfolgreich hochgeladen. Sie werden zur Vorlagenseite weitergeleitet."
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:215
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:223
msgid "Your documents"
msgstr "Ihre Dokumente"

View File

@ -517,6 +517,10 @@ msgstr "Save"
msgid "Save Template"
msgstr "Save Template"
#: packages/ui/components/common/language-switcher-dialog.tsx:34
msgid "Search languages..."
msgstr "Search languages..."
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
msgid "Select"
msgstr "Select"

View File

@ -425,8 +425,8 @@ msgid "Save $60 or $120"
msgstr "Save $60 or $120"
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
msgid "Search languages..."
msgstr "Search languages..."
#~ msgid "Search languages..."
#~ msgstr "Search languages..."
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."

File diff suppressed because one or more lines are too long

View File

@ -298,7 +298,7 @@ msgstr "Adding and removing seats will adjust your invoice accordingly."
msgid "Admin Actions"
msgstr "Admin Actions"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:257
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:262
msgid "Admin panel"
msgstr "Admin panel"
@ -306,7 +306,7 @@ msgstr "Admin panel"
msgid "All"
msgstr "All"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:37
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:42
msgid "All documents"
msgstr "All documents"
@ -322,7 +322,7 @@ msgstr "All inserted signatures will be voided"
msgid "All recipients will be notified"
msgstr "All recipients will be notified"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:52
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:57
msgid "All templates"
msgstr "All templates"
@ -476,6 +476,7 @@ msgstr "An error occurred while uploading your document."
#: apps/web/src/app/(dashboard)/settings/profile/delete-account-dialog.tsx:63
#: apps/web/src/app/(signing)/sign/[token]/complete/claim-account.tsx:98
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-transfer-status.tsx:54
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:301
#: apps/web/src/components/(dashboard)/settings/token/delete-token-dialog.tsx:97
#: apps/web/src/components/(dashboard)/settings/webhooks/delete-webhook-dialog.tsx:88
#: apps/web/src/components/(teams)/dialogs/add-team-email-dialog.tsx:100
@ -788,7 +789,7 @@ msgstr "Complete Viewing"
msgid "Completed"
msgstr "Completed"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:43
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:48
msgid "Completed documents"
msgstr "Completed documents"
@ -922,8 +923,8 @@ msgstr "Create now"
msgid "Create one automatically"
msgstr "Create one automatically"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:176
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:246
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:181
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:251
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:138
#: apps/web/src/components/(teams)/dialogs/create-team-dialog.tsx:146
msgid "Create team"
@ -1003,7 +1004,7 @@ msgstr "Current plan: {0}"
msgid "Daily"
msgstr "Daily"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:255
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:265
msgid "Dark Mode"
msgstr "Dark Mode"
@ -1313,7 +1314,7 @@ msgstr "Document will be permanently deleted"
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:110
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166
#: apps/web/src/app/not-found.tsx:21
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:200
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205
#: apps/web/src/components/(dashboard)/layout/desktop-nav.tsx:18
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:35
#: apps/web/src/components/ui/user-profile-timur.tsx:60
@ -1355,7 +1356,7 @@ msgstr "Download Certificate"
msgid "Draft"
msgstr "Draft"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:41
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:46
msgid "Draft documents"
msgstr "Draft documents"
@ -1682,7 +1683,7 @@ msgstr "If your authenticator app does not support QR codes, you can use the fol
msgid "Inbox"
msgstr "Inbox"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:47
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:52
msgid "Inbox documents"
msgstr "Inbox documents"
@ -1772,6 +1773,10 @@ msgstr "It seems that there is no token provided, if you are trying to verify yo
msgid "It seems that there is no token provided. Please check your email and try again."
msgstr "It seems that there is no token provided. Please check your email and try again."
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:286
msgid "Language"
msgstr "Language"
#: apps/web/src/components/(dashboard)/period-selector/period-selector.tsx:61
msgid "Last 14 days"
msgstr "Last 14 days"
@ -1809,7 +1814,7 @@ msgstr "Leave"
msgid "Leave team"
msgstr "Leave team"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:254
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:264
msgid "Light Mode"
msgstr "Light Mode"
@ -1899,7 +1904,7 @@ msgstr "Manage subscriptions"
msgid "Manage team subscription."
msgstr "Manage team subscription."
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:163
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:168
msgid "Manage teams"
msgstr "Manage teams"
@ -2053,7 +2058,7 @@ msgstr "No recent activity"
msgid "No recipients"
msgstr "No recipients"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:195
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:200
msgid "No results found."
msgstr "No results found."
@ -2145,7 +2150,7 @@ msgid "Otherwise, the document will be created as a draft."
msgstr "Otherwise, the document will be created as a draft."
#: apps/web/src/app/(dashboard)/admin/documents/document-results.tsx:86
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:76
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:81
msgid "Owner"
msgstr "Owner"
@ -2190,7 +2195,7 @@ msgstr "Passkeys allow you to sign in and authenticate using biometrics, passwor
msgid "Passkeys are not supported on this browser"
msgstr "Passkeys are not supported on this browser"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:65
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:70
#: apps/web/src/components/forms/password.tsx:123
#: apps/web/src/components/forms/reset-password.tsx:110
#: apps/web/src/components/forms/signin.tsx:344
@ -2224,7 +2229,7 @@ msgstr "Payment overdue"
msgid "Pending"
msgstr "Pending"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:46
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:51
msgid "Pending documents"
msgstr "Pending documents"
@ -2240,11 +2245,11 @@ msgstr "Pending invitations"
msgid "Pending team deleted."
msgstr "Pending team deleted."
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:129
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:134
msgid "Personal"
msgstr "Personal"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:72
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:77
msgid "Personal Account"
msgstr "Personal Account"
@ -2328,7 +2333,7 @@ msgstr "Please try again later."
msgid "Please type <0>{0}</0> to confirm."
msgstr "Please type <0>{0}</0> to confirm."
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:209
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:214
msgid "Preferences"
msgstr "Preferences"
@ -2346,7 +2351,7 @@ msgid "Private templates can only be modified and viewed by you."
msgstr "Private templates can only be modified and viewed by you."
#: apps/web/src/app/(dashboard)/settings/profile/page.tsx:28
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:64
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:69
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:36
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:39
msgid "Profile"
@ -2665,8 +2670,8 @@ msgid "Set a password"
msgstr "Set a password"
#: apps/web/src/app/(dashboard)/settings/layout.tsx:20
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:60
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:206
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:65
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:211
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:47
msgid "Settings"
msgstr "Settings"
@ -2750,7 +2755,7 @@ msgstr "Sign In"
msgid "Sign in to your account"
msgstr "Sign in to your account"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:285
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:297
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:84
msgid "Sign Out"
msgstr "Sign Out"
@ -2956,7 +2961,7 @@ msgstr "Success"
msgid "Successfully created passkey"
msgstr "Successfully created passkey"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:256
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:266
msgid "System Theme"
msgstr "System Theme"
@ -3041,7 +3046,7 @@ msgstr "Team ownership transferred!"
msgid "Team Public Profile"
msgstr "Team Public Profile"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:272
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:277
msgid "Team settings"
msgstr "Team settings"
@ -3067,7 +3072,7 @@ msgid "Team URL"
msgstr "Team URL"
#: apps/web/src/app/(dashboard)/settings/teams/page.tsx:25
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:157
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:162
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:43
#: apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx:64
#: apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx:67
@ -3113,7 +3118,7 @@ msgstr "Template saved"
#: apps/web/src/app/(dashboard)/templates/[id]/template-page-view.tsx:60
#: apps/web/src/app/(dashboard)/templates/templates-page-view.tsx:55
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:203
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:208
#: apps/web/src/components/(dashboard)/layout/desktop-nav.tsx:22
#: apps/web/src/components/(dashboard)/layout/mobile-navigation.tsx:39
msgid "Templates"
@ -3521,7 +3526,7 @@ msgstr "Type"
msgid "Type 'delete' to confirm"
msgstr "Type 'delete' to confirm"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:181
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:186
msgid "Type a command or search..."
msgstr "Type a command or search..."
@ -3529,6 +3534,10 @@ msgstr "Type a command or search..."
msgid "Uh oh! Looks like you're missing a token"
msgstr "Uh oh! Looks like you're missing a token"
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:303
msgid "Unable to change the language at this time. Please try again later."
msgstr "Unable to change the language at this time. Please try again later."
#: apps/web/src/components/forms/2fa/recovery-code-list.tsx:31
msgid "Unable to copy recovery code"
msgstr "Unable to copy recovery code"
@ -3727,7 +3736,7 @@ msgstr "User ID"
msgid "User profiles are here!"
msgstr "User profiles are here!"
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:264
#: apps/web/src/components/(dashboard)/layout/menu-switcher.tsx:269
msgid "User settings"
msgstr "User settings"
@ -4359,7 +4368,7 @@ msgstr "Your document has been uploaded successfully."
msgid "Your document has been uploaded successfully. You will be redirected to the template page."
msgstr "Your document has been uploaded successfully. You will be redirected to the template page."
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:215
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:223
msgid "Your documents"
msgstr "Your documents"

View File

@ -36,7 +36,7 @@ const parseLanguageFromLocale = (locale: string): SupportedLanguageCodes | null
export const extractLocaleDataFromCookies = (
cookies: ReadonlyRequestCookies,
): SupportedLanguageCodes | null => {
const preferredLocale = cookies.get('i18n')?.value || '';
const preferredLocale = cookies.get('language')?.value || '';
const language = parseLanguageFromLocale(preferredLocale || '');

View File

@ -0,0 +1,57 @@
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { CheckIcon } from 'lucide-react';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { switchI18NLanguage } from '@documenso/lib/server-only/i18n/switch-i18n-language';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
import {
CommandDialog,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@documenso/ui/primitives/command';
type LanguageSwitcherDialogProps = {
open: boolean;
setOpen: (_open: boolean) => void;
};
export const LanguageSwitcherDialog = ({ open, setOpen }: LanguageSwitcherDialogProps) => {
const { i18n, _ } = useLingui();
const setLanguage = async (lang: string) => {
setOpen(false);
await dynamicActivate(i18n, lang);
await switchI18NLanguage(lang);
};
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder={_(msg`Search languages...`)} />
<CommandList>
<CommandGroup>
{Object.values(SUPPORTED_LANGUAGES).map((language) => (
<CommandItem
key={language.short}
value={language.full}
onSelect={async () => setLanguage(language.short)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
i18n.locale === language.short ? 'opacity-100' : 'opacity-0',
)}
/>
{SUPPORTED_LANGUAGES[language.short].full}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
);
};