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

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