From 1e940939d390f94c880ff1d4e56898f22b01ef51 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Fri, 5 May 2023 18:41:59 +0100 Subject: [PATCH 1/6] i18n --- app/{ => [locale]}/(auth)/layout.tsx | 0 app/{ => [locale]}/(auth)/login/page.tsx | 0 app/{ => [locale]}/(auth)/register/page.tsx | 0 app/{ => [locale]}/(dashboard)/layout.tsx | 0 .../(dashboard)/profile/info/page.tsx | 0 .../(dashboard)/profile/info/record/page.tsx | 0 .../profile/info/settings/page.tsx | 0 .../(dashboard)/profile/team/page.tsx | 0 app/{ => [locale]}/(landing)/layout.tsx | 2 +- .../(landing)/mode/chat/page.tsx | 4 +- .../(landing)/mode/code/page.tsx | 4 +- .../(landing)/mode/file/page.tsx | 4 +- app/{ => [locale]}/(landing)/page.tsx | 2 +- .../(share)/s/[shareID]/page.tsx | 0 app/{ => [locale]}/(share)/s/layout.tsx | 0 app/{ => [locale]}/fonts.ts | 0 app/{ => [locale]}/layout.tsx | 24 ++++- app/{ => [locale]}/not-found.tsx | 0 app/{ => [locale]}/providers.tsx | 0 app/{ => [locale]}/robots.ts | 0 components/auth/form.tsx | 20 ++-- components/auth/header.tsx | 6 +- components/dashboard/nav.tsx | 6 +- components/dashboard/profile-info-form.tsx | 24 +++-- .../dashboard/profile-settings-form.tsx | 40 ++----- components/dashboard/record/button.tsx | 8 +- components/dashboard/record/card.tsx | 6 +- components/dashboard/side.tsx | 6 +- components/dashboard/team/card.tsx | 32 ++++-- components/dashboard/team/create-button.tsx | 14 ++- components/dashboard/team/join-button.tsx | 12 ++- .../landing/{mode => main}/chat-content.tsx | 14 ++- .../landing/{mode => main}/chat-head.tsx | 4 + .../landing/{mode => main}/chat-main.tsx | 12 ++- .../landing/{mode => main}/code-main.tsx | 6 +- .../landing/{mode => main}/file-main.tsx | 8 +- .../landing/{ => main}/header-settings.tsx | 48 +++++---- components/landing/{ => main}/header.tsx | 8 +- .../landing/{mode => main}/input-area.tsx | 16 +-- .../settings.tsx => main/main-settings.tsx} | 26 +++-- .../landing/{ => side}/side-app-settings.tsx | 88 +++++++++------ .../landing/{ => side}/side-history.tsx | 6 +- .../landing/{ => side}/side-user-settings.tsx | 4 + components/landing/{ => side}/side.tsx | 15 +-- hooks/store.ts | 2 - locales/en.json | 102 ++++++++++++++++++ locales/zh-CN.json | 101 +++++++++++++++++ locales/zh-HK.json | 101 +++++++++++++++++ middleware.ts | 14 +++ package.json | 1 + yarn.lock | 86 +++++++++++++++ 51 files changed, 691 insertions(+), 185 deletions(-) rename app/{ => [locale]}/(auth)/layout.tsx (100%) rename app/{ => [locale]}/(auth)/login/page.tsx (100%) rename app/{ => [locale]}/(auth)/register/page.tsx (100%) rename app/{ => [locale]}/(dashboard)/layout.tsx (100%) rename app/{ => [locale]}/(dashboard)/profile/info/page.tsx (100%) rename app/{ => [locale]}/(dashboard)/profile/info/record/page.tsx (100%) rename app/{ => [locale]}/(dashboard)/profile/info/settings/page.tsx (100%) rename app/{ => [locale]}/(dashboard)/profile/team/page.tsx (100%) rename app/{ => [locale]}/(landing)/layout.tsx (86%) rename app/{ => [locale]}/(landing)/mode/chat/page.tsx (74%) rename app/{ => [locale]}/(landing)/mode/code/page.tsx (74%) rename app/{ => [locale]}/(landing)/mode/file/page.tsx (74%) rename app/{ => [locale]}/(landing)/page.tsx (54%) rename app/{ => [locale]}/(share)/s/[shareID]/page.tsx (100%) rename app/{ => [locale]}/(share)/s/layout.tsx (100%) rename app/{ => [locale]}/fonts.ts (100%) rename app/{ => [locale]}/layout.tsx (83%) rename app/{ => [locale]}/not-found.tsx (100%) rename app/{ => [locale]}/providers.tsx (100%) rename app/{ => [locale]}/robots.ts (100%) rename components/landing/{mode => main}/chat-content.tsx (93%) rename components/landing/{mode => main}/chat-head.tsx (95%) rename components/landing/{mode => main}/chat-main.tsx (98%) rename components/landing/{mode => main}/code-main.tsx (98%) rename components/landing/{mode => main}/file-main.tsx (98%) rename components/landing/{ => main}/header-settings.tsx (90%) rename components/landing/{ => main}/header.tsx (95%) rename components/landing/{mode => main}/input-area.tsx (96%) rename components/landing/{mode/settings.tsx => main/main-settings.tsx} (88%) rename components/landing/{ => side}/side-app-settings.tsx (92%) rename components/landing/{ => side}/side-history.tsx (98%) rename components/landing/{ => side}/side-user-settings.tsx (92%) rename components/landing/{ => side}/side.tsx (96%) create mode 100644 locales/en.json create mode 100644 locales/zh-CN.json create mode 100644 locales/zh-HK.json create mode 100644 middleware.ts diff --git a/app/(auth)/layout.tsx b/app/[locale]/(auth)/layout.tsx similarity index 100% rename from app/(auth)/layout.tsx rename to app/[locale]/(auth)/layout.tsx diff --git a/app/(auth)/login/page.tsx b/app/[locale]/(auth)/login/page.tsx similarity index 100% rename from app/(auth)/login/page.tsx rename to app/[locale]/(auth)/login/page.tsx diff --git a/app/(auth)/register/page.tsx b/app/[locale]/(auth)/register/page.tsx similarity index 100% rename from app/(auth)/register/page.tsx rename to app/[locale]/(auth)/register/page.tsx diff --git a/app/(dashboard)/layout.tsx b/app/[locale]/(dashboard)/layout.tsx similarity index 100% rename from app/(dashboard)/layout.tsx rename to app/[locale]/(dashboard)/layout.tsx diff --git a/app/(dashboard)/profile/info/page.tsx b/app/[locale]/(dashboard)/profile/info/page.tsx similarity index 100% rename from app/(dashboard)/profile/info/page.tsx rename to app/[locale]/(dashboard)/profile/info/page.tsx diff --git a/app/(dashboard)/profile/info/record/page.tsx b/app/[locale]/(dashboard)/profile/info/record/page.tsx similarity index 100% rename from app/(dashboard)/profile/info/record/page.tsx rename to app/[locale]/(dashboard)/profile/info/record/page.tsx diff --git a/app/(dashboard)/profile/info/settings/page.tsx b/app/[locale]/(dashboard)/profile/info/settings/page.tsx similarity index 100% rename from app/(dashboard)/profile/info/settings/page.tsx rename to app/[locale]/(dashboard)/profile/info/settings/page.tsx diff --git a/app/(dashboard)/profile/team/page.tsx b/app/[locale]/(dashboard)/profile/team/page.tsx similarity index 100% rename from app/(dashboard)/profile/team/page.tsx rename to app/[locale]/(dashboard)/profile/team/page.tsx diff --git a/app/(landing)/layout.tsx b/app/[locale]/(landing)/layout.tsx similarity index 86% rename from app/(landing)/layout.tsx rename to app/[locale]/(landing)/layout.tsx index 982198a0..a083ad60 100644 --- a/app/(landing)/layout.tsx +++ b/app/[locale]/(landing)/layout.tsx @@ -1,4 +1,4 @@ -import LandingSide from '@/components/landing/side'; +import LandingSide from '@/components/landing/side/side'; import { getCurrentUserProfile } from '@/lib/auth/session'; diff --git a/app/(landing)/mode/chat/page.tsx b/app/[locale]/(landing)/mode/chat/page.tsx similarity index 74% rename from app/(landing)/mode/chat/page.tsx rename to app/[locale]/(landing)/mode/chat/page.tsx index 1299f7b5..6f77b0f6 100644 --- a/app/(landing)/mode/chat/page.tsx +++ b/app/[locale]/(landing)/mode/chat/page.tsx @@ -3,8 +3,8 @@ import store from '@/hooks/store'; import { useAtomValue } from 'jotai'; -import LandingHeader from '@/components/landing/header'; -import ChatMain from '@/components/landing/mode/chat-main'; +import LandingHeader from '@/components/landing/main/header'; +import ChatMain from '@/components/landing/main/chat-main'; export default function ChatModePage() { const isHiddenSide = useAtomValue(store.isHiddenSideAtom); diff --git a/app/(landing)/mode/code/page.tsx b/app/[locale]/(landing)/mode/code/page.tsx similarity index 74% rename from app/(landing)/mode/code/page.tsx rename to app/[locale]/(landing)/mode/code/page.tsx index 19cfd492..696c2a40 100644 --- a/app/(landing)/mode/code/page.tsx +++ b/app/[locale]/(landing)/mode/code/page.tsx @@ -3,8 +3,8 @@ import store from '@/hooks/store'; import { useAtomValue } from 'jotai'; -import LandingHeader from '@/components/landing/header'; -import CodeMain from '@/components/landing/mode/code-main'; +import LandingHeader from '@/components/landing/main/header'; +import CodeMain from '@/components/landing/main/code-main'; export default function CodeModePage() { const isHiddenSide = useAtomValue(store.isHiddenSideAtom); diff --git a/app/(landing)/mode/file/page.tsx b/app/[locale]/(landing)/mode/file/page.tsx similarity index 74% rename from app/(landing)/mode/file/page.tsx rename to app/[locale]/(landing)/mode/file/page.tsx index 579d16d7..9d8dc2c9 100644 --- a/app/(landing)/mode/file/page.tsx +++ b/app/[locale]/(landing)/mode/file/page.tsx @@ -3,8 +3,8 @@ import store from '@/hooks/store'; import { useAtomValue } from 'jotai'; -import LandingHeader from '@/components/landing/header'; -import FileMain from '@/components/landing/mode/file-main'; +import LandingHeader from '@/components/landing/main/header'; +import FileMain from '@/components/landing/main/file-main'; export default function FileModePage() { const isHiddenSide = useAtomValue(store.isHiddenSideAtom); diff --git a/app/(landing)/page.tsx b/app/[locale]/(landing)/page.tsx similarity index 54% rename from app/(landing)/page.tsx rename to app/[locale]/(landing)/page.tsx index a378b85f..d4f9bde9 100644 --- a/app/(landing)/page.tsx +++ b/app/[locale]/(landing)/page.tsx @@ -1,6 +1,6 @@ import dynamic from 'next/dynamic'; -const ChatMode = dynamic(() => import('@/app/(landing)/mode/chat/page'), {}); +const ChatMode = dynamic(() => import('@/app/[locale]/(landing)/mode/chat/page'), {}); export default function LandingPage() { return ; diff --git a/app/(share)/s/[shareID]/page.tsx b/app/[locale]/(share)/s/[shareID]/page.tsx similarity index 100% rename from app/(share)/s/[shareID]/page.tsx rename to app/[locale]/(share)/s/[shareID]/page.tsx diff --git a/app/(share)/s/layout.tsx b/app/[locale]/(share)/s/layout.tsx similarity index 100% rename from app/(share)/s/layout.tsx rename to app/[locale]/(share)/s/layout.tsx diff --git a/app/fonts.ts b/app/[locale]/fonts.ts similarity index 100% rename from app/fonts.ts rename to app/[locale]/fonts.ts diff --git a/app/layout.tsx b/app/[locale]/layout.tsx similarity index 83% rename from app/layout.tsx rename to app/[locale]/layout.tsx index 44c74990..3f3d9325 100644 --- a/app/layout.tsx +++ b/app/[locale]/layout.tsx @@ -2,9 +2,13 @@ import '@/styles/globals.css'; import '@/styles/markdown.css'; import 'tippy.js/dist/tippy.css'; -import { rubik } from '@/app/fonts'; +import { NextIntlClientProvider } from 'next-intl'; -import { Providers } from '@/app/providers'; +import NotFound from '@/app/[locale]/not-found'; + +import { rubik } from '@/app/[locale]/fonts'; + +import { Providers } from '@/app/[locale]/providers'; import { Analytics } from '@vercel/analytics/react'; @@ -13,9 +17,17 @@ import { ClientCommand } from '@/components/client/command'; import { siteConfig } from '@/config/site.config'; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export default async function RootLayout({ children, params: { locale } }: { children: React.ReactNode; params: { locale: string } }) { + let messages; + + try { + messages = (await import(`../../locales/${locale}.json`)).default; + } catch (error) { + NotFound(); + } + return ( - + {siteConfig.title} @@ -63,7 +75,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - {children} + + {children} + diff --git a/app/not-found.tsx b/app/[locale]/not-found.tsx similarity index 100% rename from app/not-found.tsx rename to app/[locale]/not-found.tsx diff --git a/app/providers.tsx b/app/[locale]/providers.tsx similarity index 100% rename from app/providers.tsx rename to app/[locale]/providers.tsx diff --git a/app/robots.ts b/app/[locale]/robots.ts similarity index 100% rename from app/robots.ts rename to app/[locale]/robots.ts diff --git a/components/auth/form.tsx b/components/auth/form.tsx index 52cde75f..243dae40 100644 --- a/components/auth/form.tsx +++ b/components/auth/form.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import Link from 'next/link'; +import { useTranslations } from 'next-intl'; + import { signIn } from 'next-auth/react'; import { toast } from 'react-hot-toast'; @@ -15,6 +17,8 @@ import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; const AuthForm = ({ login }: { login: boolean }) => { + const t = useTranslations('auth'); + const [email, setEmail] = useState(''); const handleEmailSubmit = async () => { @@ -32,8 +36,6 @@ const AuthForm = ({ login }: { login: boolean }) => { callbackUrl: '/profile', }); - console.log(emailSignIn); - if (emailSignIn?.error) { return toast.error('Failed to send email'); } @@ -46,21 +48,21 @@ const AuthForm = ({ login }: { login: boolean }) => {
{login ? ( <> -

Sign In

+

{t('Sign In')}

- New User?{' '} + {t('New User?')}{' '} - Sign up + {t('Sign up')}

) : ( <> -

Register

+

{t('Register')}

- Already have an account with us?{' '} + {t('Already have an account with us?')}{' '} - Log In + {t('Log In')}

@@ -78,7 +80,7 @@ const AuthForm = ({ login }: { login: boolean }) => { className='dark:bg-stone-600' />
{/*
diff --git a/components/auth/header.tsx b/components/auth/header.tsx index 8ee1fea1..9e199478 100644 --- a/components/auth/header.tsx +++ b/components/auth/header.tsx @@ -4,6 +4,8 @@ import Image from 'next/image'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { Button } from '@/components/ui/button'; import { IoArrowBackOutline } from 'react-icons/io5'; @@ -12,11 +14,13 @@ import { siteConfig } from '@/config/site.config'; const AuthHeader = () => { const router = useRouter(); + const t = useTranslations('auth'); + return (
{siteConfig.title} diff --git a/components/dashboard/nav.tsx b/components/dashboard/nav.tsx index 5935ea6a..13f0531b 100644 --- a/components/dashboard/nav.tsx +++ b/components/dashboard/nav.tsx @@ -2,6 +2,8 @@ import { usePathname } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { signOut } from 'next-auth/react'; import { RxAvatar } from 'react-icons/rx'; @@ -16,6 +18,8 @@ import { Separator } from '@/components/ui/separator'; const DashboardNav = ({ user }: { user: any }) => { const pathname = usePathname(); + const t = useTranslations('dashboard'); + const breadcrumbs = pathname ?.split('/') .filter((path) => path !== '') @@ -58,7 +62,7 @@ const DashboardNav = ({ user }: { user: any }) => { }) } > - Sign out + {t('Sign out')} diff --git a/components/dashboard/profile-info-form.tsx b/components/dashboard/profile-info-form.tsx index 1ef8813e..1c30d34b 100644 --- a/components/dashboard/profile-info-form.tsx +++ b/components/dashboard/profile-info-form.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { User } from '@prisma/client'; import { toast } from 'react-hot-toast'; @@ -22,6 +24,8 @@ import { signOut } from 'next-auth/react'; const ProfileInfoForm = ({ user }: any) => { const router = useRouter(); + const t = useTranslations('dashboard'); + const [name, setName] = useState(user.name); const [email, setEmail] = useState(user.email); const [image, setImage] = useState(user.image); @@ -80,21 +84,21 @@ const ProfileInfoForm = ({ user }: any) => {
-

Full Name

+

{t('Full Name')}

setName(e.target.value)} className='dark:border-stone-400 dark:bg-stone-500' />
-

Email Address

+

{t('Email Address')}

setEmail(e.target.value)} className='dark:border-stone-400 dark:bg-stone-500' />
-

Avatar

+

{t('Avatar')}

setImage(e.target.value)} className='dark:border-stone-400 dark:bg-stone-500' />
@@ -102,20 +106,22 @@ const ProfileInfoForm = ({ user }: any) => {
-

Danger Zoom

-

If you want to permanently remove your account from {siteConfig.title}. Please click the button below, please note, This could not be undone.

+

{t('Danger Zoom')}

+

+ {t('If you want to permanently remove your account from')} {siteConfig.title}. {t('Please click the button below, please note, This could not be undone.')} +

- Confirming Deletion - Please note: This could not be undone. + {t('Confirming Deletion')} + {t('Please note: This could not be undone.')} diff --git a/components/dashboard/profile-settings-form.tsx b/components/dashboard/profile-settings-form.tsx index 47b82f47..67af8b33 100644 --- a/components/dashboard/profile-settings-form.tsx +++ b/components/dashboard/profile-settings-form.tsx @@ -2,6 +2,8 @@ import { useState } from 'react'; +import { useTranslations } from 'next-intl'; + import { User } from '@prisma/client'; import { toast } from 'react-hot-toast'; @@ -12,10 +14,11 @@ import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Separator } from '@/components/ui/separator'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; const ProfileSettingsForm = ({ user }: { user: User }) => { + const t = useTranslations('dashboard'); + const [openAIKey, setOpenAIKey] = useState(user.openAIKey); const [allowRecordCloudSync, setAllowRecordCloudSync] = useState(user.allowRecordCloudSync); @@ -59,30 +62,16 @@ const ProfileSettingsForm = ({ user }: { user: User }) => {
+
- - -
-
- +
@@ -91,18 +80,3 @@ const ProfileSettingsForm = ({ user }: { user: User }) => { }; export default ProfileSettingsForm; - -const ProfileLanguageSelectItems = [ - { - label: 'English', - value: 'en', - }, - { - label: '简体中文', - value: 'zh-CN', - }, - { - label: '繁體中文', - value: 'zh-HK', - }, -]; diff --git a/components/dashboard/record/button.tsx b/components/dashboard/record/button.tsx index c9102503..74232139 100644 --- a/components/dashboard/record/button.tsx +++ b/components/dashboard/record/button.tsx @@ -2,6 +2,8 @@ import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { Record } from '@prisma/client'; import { toast } from 'react-hot-toast'; @@ -12,6 +14,8 @@ import { siteConfig } from '@/config/site.config'; const RecordButton = ({ records }: { records: Record[] }) => { const router = useRouter(); + const t = useTranslations('dashboard'); + const handleExport = async () => { const data = records.map((record) => { return { @@ -54,10 +58,10 @@ const RecordButton = ({ records }: { records: Record[] }) => { return (
); diff --git a/components/dashboard/record/card.tsx b/components/dashboard/record/card.tsx index b40c41d7..4788fa7d 100644 --- a/components/dashboard/record/card.tsx +++ b/components/dashboard/record/card.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { Record } from '@prisma/client'; import { toast } from 'react-hot-toast'; @@ -17,6 +19,8 @@ import { Button } from '@/components/ui/button'; const RecordCard = ({ record }: { record: Record }) => { const router = useRouter(); + const t = useTranslations('dashboard'); + const [enableShare, setEnableShare] = useState(false); const onSwitchShare = async (value: boolean) => { @@ -96,7 +100,7 @@ const RecordCard = ({ record }: { record: Record }) => {
-

Share

+

{t('Share')}

{enableShare && (
diff --git a/components/dashboard/team/card.tsx b/components/dashboard/team/card.tsx index 24d29264..e1265d63 100644 --- a/components/dashboard/team/card.tsx +++ b/components/dashboard/team/card.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { Team } from '@prisma/client'; import { toast } from 'react-hot-toast'; @@ -18,6 +20,8 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D const TeamCard = ({ team }: { team: Team & { isAuthor: boolean } }) => { const router = useRouter(); + const t = useTranslations('dashboard'); + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isQuitDialogOpen, setIsQuitDialogOpen] = useState(false); @@ -137,12 +141,14 @@ const TeamCard = ({ team }: { team: Team & { isAuthor: boolean } }) => { - Confirm to quit Team {team.name}? - This action cannot be undone. + + {t('Confirm to quit Team')} {team.name}? + + {t('This action cannot be undone.')} @@ -158,15 +164,17 @@ const TeamCard = ({ team }: { team: Team & { isAuthor: boolean } }) => { - Edit Team: {team.name} + + {t('Edit Team')}: {team.name} +
- + setName(e.target.value)} />
- + setAccessCode(e.target.value)} />
@@ -180,7 +188,7 @@ const TeamCard = ({ team }: { team: Team & { isAuthor: boolean } }) => {
@@ -193,12 +201,16 @@ const TeamCard = ({ team }: { team: Team & { isAuthor: boolean } }) => { - Confirm to delete Team {team.name}? - This action cannot be undone. This will permanently delete your team and remove your data from our database. + + {t('Confirm to delete Team')} {team.name}? + + + {t('This action cannot be undone')}. {t('This will permanently delete your team and remove your data from our database.')} + diff --git a/components/dashboard/team/create-button.tsx b/components/dashboard/team/create-button.tsx index ceeaba99..eeb23802 100644 --- a/components/dashboard/team/create-button.tsx +++ b/components/dashboard/team/create-button.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import { MdOutlineAdd } from 'react-icons/md'; @@ -16,6 +18,8 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT const CreateButton = () => { const router = useRouter(); + const t = useTranslations('dashboard'); + const [isDialogOpen, setIsDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -97,20 +101,20 @@ const CreateButton = () => { - Create a Team + {t('Create Team')}
- + setName(e.target.value)} />
- + setAccessCode(e.target.value)} />
@@ -124,7 +128,7 @@ const CreateButton = () => {
diff --git a/components/dashboard/team/join-button.tsx b/components/dashboard/team/join-button.tsx index 2d78e580..2a307824 100644 --- a/components/dashboard/team/join-button.tsx +++ b/components/dashboard/team/join-button.tsx @@ -4,6 +4,8 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import { GrStatusGoodSmall } from 'react-icons/gr'; @@ -16,6 +18,8 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT const JoinButton = () => { const router = useRouter(); + const t = useTranslations('dashboard'); + const [isDialogOpen, setIsDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -71,22 +75,22 @@ const JoinButton = () => { - Join a Team + {t('Join Team')}
- + setAccessCode(e.target.value)} />
diff --git a/components/landing/mode/chat-content.tsx b/components/landing/main/chat-content.tsx similarity index 93% rename from components/landing/mode/chat-content.tsx rename to components/landing/main/chat-content.tsx index b4ba2411..6fbe3a5d 100644 --- a/components/landing/mode/chat-content.tsx +++ b/components/landing/main/chat-content.tsx @@ -1,5 +1,7 @@ import { useEffect, useRef } from 'react'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import store from '@/hooks/store'; @@ -22,6 +24,8 @@ const MainContent = ({ reGenerate: (index: number) => void; onEdit: (index: number) => void; }) => { + const t = useTranslations('landing.main'); + const ttsConfig = useAtomValue(store.textToSpeechConfigAtom); const endOfMessageRef = useRef(null); @@ -78,14 +82,14 @@ const MainContent = ({ onClick={() => onEdit(index)} > - Edit + {t('Edit')} )} @@ -101,21 +105,21 @@ const MainContent = ({ onClick={() => onCopy(index)} > - Copy + {t('Copy')} )} diff --git a/components/landing/mode/chat-head.tsx b/components/landing/main/chat-head.tsx similarity index 95% rename from components/landing/mode/chat-head.tsx rename to components/landing/main/chat-head.tsx index e3d998db..ec948e26 100644 --- a/components/landing/mode/chat-head.tsx +++ b/components/landing/main/chat-head.tsx @@ -1,5 +1,7 @@ import { useEffect, useState } from 'react'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import BarLoader from 'react-spinners/BarLoader'; @@ -19,6 +21,8 @@ const ContentHead = ({ waitingSystemResponse: boolean; conversations: AppMessageProps[]; }) => { + const t = useTranslations('landing.main'); + const [tokens, setTokens] = useState(0); useEffect(() => { diff --git a/components/landing/mode/chat-main.tsx b/components/landing/main/chat-main.tsx similarity index 98% rename from components/landing/mode/chat-main.tsx rename to components/landing/main/chat-main.tsx index 7e155501..7008b6d5 100644 --- a/components/landing/mode/chat-main.tsx +++ b/components/landing/main/chat-main.tsx @@ -4,6 +4,8 @@ import { useEffect, useState, useRef } from 'react'; import { useSearchParams } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import store from '@/hooks/store'; @@ -11,11 +13,11 @@ import { useAtom, useAtomValue } from 'jotai'; import { setLocalStorage } from '@/hooks/setLocalStorage'; -import ContentHead from '@/components/landing/mode/chat-head'; -import InputArea from '@/components/landing/mode/input-area'; -import MainContent from '@/components/landing/mode/chat-content'; +import ContentHead from '@/components/landing/main/chat-head'; +import InputArea from '@/components/landing/main/input-area'; +import MainContent from '@/components/landing/main/chat-content'; -import ModeSettings from '@/components/landing/mode/settings'; +import ModeSettings from '@/components/landing/main/main-settings'; import generateHash from '@/utils/app/generateHash'; @@ -33,6 +35,8 @@ const ChatMain = () => { const share = searchParams?.get('share'); + const t = useTranslations('landing.main'); + // Conversation Config const isNoContextConversation = useAtomValue(store.noContextConversationAtom); const enableStreamMessages = useAtomValue(store.enableStreamMessagesAtom); diff --git a/components/landing/mode/code-main.tsx b/components/landing/main/code-main.tsx similarity index 98% rename from components/landing/mode/code-main.tsx rename to components/landing/main/code-main.tsx index 41fc1b4f..0545f3ed 100644 --- a/components/landing/mode/code-main.tsx +++ b/components/landing/main/code-main.tsx @@ -2,6 +2,8 @@ import { useState } from 'react'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import store from '@/hooks/store'; @@ -16,6 +18,8 @@ import generateHash from '@/utils/app/generateHash'; import { setLocalStorage } from '@/hooks/setLocalStorage'; const CodeMain = () => { + const t = useTranslations('landing.main'); + const [userMessageInput, setUserMessageInput] = useState(''); const [codeMode, setCodeMode] = useState('Explain'); @@ -247,7 +251,7 @@ const CodeMain = () => { )}
{systemResponse}
diff --git a/components/landing/mode/file-main.tsx b/components/landing/main/file-main.tsx similarity index 98% rename from components/landing/mode/file-main.tsx rename to components/landing/main/file-main.tsx index bee67234..f1579fde 100644 --- a/components/landing/mode/file-main.tsx +++ b/components/landing/main/file-main.tsx @@ -2,6 +2,8 @@ import { useRef, useState } from 'react'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import store from '@/hooks/store'; @@ -10,8 +12,8 @@ import { useAtom, useAtomValue } from 'jotai'; import { GrCircleInformation } from 'react-icons/gr'; import { AiOutlineCloudUpload } from 'react-icons/ai'; -import InputArea from '@/components/landing/mode/input-area'; -import MainContent from '@/components/landing/mode/chat-content'; +import InputArea from '@/components/landing/main/input-area'; +import MainContent from '@/components/landing/main/chat-content'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; @@ -21,6 +23,8 @@ import { renderMarkdownMessage, renderUserMessage } from '@/utils/app/renderMess import { setLocalStorage } from '@/hooks/setLocalStorage'; const FileMain = () => { + const t = useTranslations('landing.main'); + const [conversations, setConversations] = useState([]); const [conversationID, setConversationID] = useState(generateHash(16)); diff --git a/components/landing/header-settings.tsx b/components/landing/main/header-settings.tsx similarity index 90% rename from components/landing/header-settings.tsx rename to components/landing/main/header-settings.tsx index db651188..41ef9d46 100644 --- a/components/landing/header-settings.tsx +++ b/components/landing/main/header-settings.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from 'react'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import store from '@/hooks/store'; @@ -12,22 +14,21 @@ import Tippy from '@tippyjs/react'; import { MdInfoOutline } from 'react-icons/md'; import { AiOutlineLoading3Quarters } from 'react-icons/ai'; import { BiImport, BiExport, BiBrush } from 'react-icons/bi'; -import { TbSettingsFilled, TbCircleArrowRightFilled } from 'react-icons/tb'; +import { TbSettingsFilled } from 'react-icons/tb'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { Switch } from '@/components/ui/switch'; -import { Checkbox } from '@/components/ui/checkbox'; -import { Textarea } from '@/components/ui/textarea'; import { Separator } from '@/components/ui/separator'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from '@/components/ui/dialog'; const HeaderSettings = () => { + const t = useTranslations('landing.main'); + const [isDialogOpen, setIsDialogOpen] = useState(false); // Text To Speech @@ -202,27 +203,27 @@ const HeaderSettings = () => { - Advanced Conversation Settings + {t('Advanced Conversation Settings')} You are using {serviceProvider}. - Text To Speech - Web Search - Advanced + {t('Text To Speech')} + {t('Web Search')} + {t('Advanced')} {voices && voices.length > 0 ? (
- +
- + setTTSSpeed(value)} />
- + setTTSSample(e.target.value)} /> -
) : (
-

Text to Speech is not supported in your browser.

+

{t('Text to Speech is not supported in your browser')}

)}
- + setServiceProvider(value as ServiceProviderProps)}> @@ -322,11 +326,11 @@ const SideAppSettings = ({ user }: { user: User | null }) => { {service.status !== 1 && (service.status == 0 ? ( - Planned + {t('Planned')} ) : ( - Beta + {t('Beta')} ))} @@ -340,10 +344,10 @@ const SideAppSettings = ({ user }: { user: User | null }) => {
@@ -378,6 +382,8 @@ const OpenAICard = ({ useCloudSettings: boolean; setUseCloudSettings: (useCloudSettings: boolean) => void; }) => { + const t = useTranslations('landing.side'); + if (user && useCloudSettings) { setApiKey(user?.openAIKey || ''); setApiEndpoint('https://api.openai.com'); @@ -393,7 +399,7 @@ const OpenAICard = ({ <> - Good to know + {t('Goodwill Reminders')} You need to provide the{' '} @@ -408,7 +414,7 @@ const OpenAICard = ({ {user && (
- + setUseCloudSettings(!useCloudSettings)} />
)} @@ -444,9 +450,9 @@ const OpenAICard = ({
setApiTemperature(e)} />
-

Stable

-

Standard

-

Creative

+

{t('Stable')}

+

{t('Standard')}

+

{t('Creative')}

@@ -465,11 +471,13 @@ const HuggingFaceCard = ({ setAccessToken: (accessToken: string) => void; setHuggingFaceModel: (huggingFaceModel: string) => void; }) => { + const t = useTranslations('landing.side'); + return ( <> - Good to know + {t('Goodwill Reminders')} You need to provide the{' '} @@ -479,7 +487,7 @@ const HuggingFaceCard = ({
- +
- + setAccessToken(e.target.value)} />
@@ -518,11 +526,13 @@ const ClaudeCard = ({ setClaudeAPIModel: (claudeAPIModel: OpenAIModel) => void; setClaudeAPITemperature: (claudeAPITemperature: number) => void; }) => { + const t = useTranslations('landing.side'); + return ( <> - Good to know + {t('Goodwill Reminders')} You need to provide the{' '} @@ -537,7 +547,7 @@ const ClaudeCard = ({
- + setClaudeAPIKey(e.target.value)} />
- +
setClaudeAPITemperature(e)} />
-

Stable

-

Standard

-

Creative

+

{t('Stable')}

+

{t('Standard')}

+

{t('Creative')}

@@ -596,11 +608,13 @@ const AzureCard = ({ setAzureAPITemperature: (azureAPITemperature: number) => void; setAzureAPIDeploymentName: (azureAPIDeploymentName: string) => void; }) => { + const t = useTranslations('landing.side'); + return ( <> - Good to know + {t('Goodwill Reminders')} You need to provide the{' '} @@ -615,7 +629,7 @@ const AzureCard = ({
- + setAzureAPIKey(e.target.value)} />
- + setAzureAPIEndpoint(e.target.value)} />
- + setAzureAPIDeploymentName(e.target.value)} />
- +
setAzureAPITemperature(e)} />
@@ -660,14 +676,16 @@ const AzureCard = ({ }; const CustomCard = () => { + const t = useTranslations('landing.side'); + return ( <>
- +
- +
@@ -675,11 +693,13 @@ const CustomCard = () => { }; const TeamCard = ({ accessCode, setAccessCode }: { accessCode: string; setAccessCode: (accessCode: string) => void }) => { + const t = useTranslations('landing.side'); + return ( <> - Good to know + {t('Goodwill Reminders')} This is a feature for teams. You can create a team in dashboard. However, this feature is only available for fully setup deployment.
@@ -701,11 +721,13 @@ const CohereCard = ({ setCohereAPIKey: (cohereAPIKey: string) => void; setCohereModel: (cohereModel: string) => void; }) => { + const t = useTranslations('landing.side'); + return ( <> - Good to know + {t('Goodwill Reminders')} You need to provide the{' '} @@ -740,10 +762,12 @@ const CohereCard = ({ }; const ExtensionCard = ({}: {}) => { + const t = useTranslations('landing.side'); + return ( <>
- +
diff --git a/components/landing/side-history.tsx b/components/landing/side/side-history.tsx similarity index 98% rename from components/landing/side-history.tsx rename to components/landing/side/side-history.tsx index fc856205..199d1b94 100644 --- a/components/landing/side-history.tsx +++ b/components/landing/side/side-history.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { toast } from 'react-hot-toast'; import { TiPinOutline, TiDeleteOutline, TiBrush } from 'react-icons/ti'; @@ -13,6 +15,8 @@ import { Input } from '@/components/ui/input'; const SideHistory = () => { const router = useRouter(); + const t = useTranslations('landing.side'); + const [userInput, setUserInput] = useState(''); const [histories, setHistories] = useState([]); @@ -117,7 +121,7 @@ const SideHistory = () => {
{ setUserInput(e.target.value); diff --git a/components/landing/side-user-settings.tsx b/components/landing/side/side-user-settings.tsx similarity index 92% rename from components/landing/side-user-settings.tsx rename to components/landing/side/side-user-settings.tsx index d8f958de..08bf3338 100644 --- a/components/landing/side-user-settings.tsx +++ b/components/landing/side/side-user-settings.tsx @@ -2,12 +2,16 @@ import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { FiUser } from 'react-icons/fi'; import { IoLogInOutline } from 'react-icons/io5'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; const SideUserSettings = () => { + const t = useTranslations('landing.side'); + const router = useRouter(); return ( diff --git a/components/landing/side.tsx b/components/landing/side/side.tsx similarity index 96% rename from components/landing/side.tsx rename to components/landing/side/side.tsx index 3109eca6..edb007d7 100644 --- a/components/landing/side.tsx +++ b/components/landing/side/side.tsx @@ -1,10 +1,10 @@ 'use client'; -import { useEffect, useState } from 'react'; - import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { useTranslations } from 'next-intl'; + import { signOut } from 'next-auth/react'; import { User } from '@prisma/client'; @@ -38,16 +38,17 @@ import { import { siteConfig, sidebarMoreMenu } from '@/config/site.config'; -import SideHistory from '@/components/landing/side-history'; -import SideAppSettings from '@/components/landing/side-app-settings'; -import SideUserSettings from '@/components/landing/side-user-settings'; +import SideHistory from '@/components/landing/side/side-history'; +import SideAppSettings from '@/components/landing/side/side-app-settings'; +import SideUserSettings from '@/components/landing/side/side-user-settings'; const LandingSide = ({ className, user }: { className?: string; user: User | null }) => { const router = useRouter(); const { theme, setTheme } = useTheme(); - // const [theme, setTheme] = useAtom(store.themeAtom); + const t = useTranslations('landing.side'); + const [language, setLanguage] = useAtom(store.languageAtom); const isHiddenSide = useAtom(store.isHiddenSideAtom)[0]; @@ -69,7 +70,7 @@ const LandingSide = ({ className, user }: { className?: string; user: User | nul onClick={() => (location.href = '')} > - New Conversation + {t('New Conversation')}
diff --git a/hooks/store.ts b/hooks/store.ts index 8ab4fab7..a97ad5b8 100644 --- a/hooks/store.ts +++ b/hooks/store.ts @@ -5,7 +5,6 @@ import { atomWithStorage } from 'jotai/utils'; // Layout const isHiddenSideAtom = atom(false); -const themeAtom = atomWithStorage('theme', 'light'); const languageAtom = atomWithStorage('language', 'en'); // ------------------ Conversation Config ------------------ @@ -82,7 +81,6 @@ const claudeConfigAtom = atomWithStorage('claudeConfig', { // eslint-disable-next-line import/no-anonymous-default-export export default { isHiddenSideAtom, - themeAtom, languageAtom, isSendKeyEnterAtom, enableStreamMessagesAtom, diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 00000000..d380ab3b --- /dev/null +++ b/locales/en.json @@ -0,0 +1,102 @@ +{ + "landing": { + "side": { + "title": "Chat Chat", + "description": "Unlock next-level conversations with AI", + "New Conversation": "New Conversation", + "Search History": "Search History", + "App Settings": "App Settings", + "AI Service Provider": "AI Service Provider", + "Goodwill Reminders": "Goodwill Reminders", + "Stable": "Stable", + "Beta": "Beta", + "Standard": "Standard", + "Creative": "Creative", + "Reset": "Reset", + "Save": "Save", + "Planned": "Planned", + "Login": "Login", + "Language": "Language", + "System": "System", + "Dark": "Dark", + "Light": "Light" + }, + "main": { + "Edit": "Edit", + "Copy": "Copy", + "Regenerate": "Regenerate", + "Speech": "Speech", + "Process": "Process", + "Conversation Settings": "Conversation Settings", + "Advanced Conversation Settings": "Advanced Conversation Settings", + "Text To Speech": "Text To Speech", + "Web Search": "Web Search", + "Advanced": "Advanced", + "Voice": "Voice", + "Speaking Speed": "Speaking Speed", + "Sample": "Sample", + "Speak": "Speak", + "Text to Speech is not supported in your browser": "Text to Speech is not supported in your browser", + "Search Engine": "Search Engine", + "Send Message using Enter Key": "Send Message using Enter Key", + "History": "History", + "Clear": "Clear", + "Import": "Import", + "Export": "Export", + "Cancel": "Cancel", + "Save": "Save", + "plugins": "plugins", + "no context": "no context", + "Stop Generating": "Stop Generating", + "Share": "Share", + "this conversation": "this conversation", + "Current Model": "Current Model", + "Temperature": "Temperature", + "Stream Messages": "Stream Messages", + "No Context Mode": "No Context Mode", + "Plugins": "Plugins", + "Beta": "Beta" + } + }, + "auth": { + "Home": "Home", + "Sign In": "Sign In", + "Register": "Register", + "Sign In With Email": "Sign In With Email", + "Login In": "Login In", + "New User?": "New User?", + "Already have an account with us?": "Already have an account with us?", + "Sign up": "Sign up" + }, + "dashboard": { + "Back": "Back", + "Language": "Language", + "Auto upload your records to the cloud": "Auto upload your records to the cloud", + "Full Name": "Full Name", + "Email Address": "Email Address", + "Avatar": "Avatar", + "Confirm": "Confirm", + "Confirming Deletion": "Confirming Deletion", + "Delete Account": "Delete Account", + "Please note: This could not be undone.": "Please note: This could not be undone.", + "Export All": "Export All", + "Delete All": "Delete All", + "Share": "Share", + "Confirm to quit Team": "Confirm to quit Team", + "This action cannot be undone.": "This action cannot be undone.", + "Edit Team": "Edit Team", + "Name": "Name", + "Access Code": "Access Code", + "Save": "Save", + "Confirm to delete Team": "Confirm to delete Team", + "This will permanently delete your team and remove your data from our database.": "This will permanently delete your team and remove your data from our database.", + "Create Team": "Create Team", + "Join Team": "Join Team", + "Join": "Join", + "Create": "Create", + "Sign out": "Sign out", + "Danger Zoom": "Danger Zoom", + "If you want to permanently remove your account from": "If you want to permanently remove your account from", + "Please click the button below, please note, This could not be undone.": "Please click the button below, please note, This could not be undone." + } +} \ No newline at end of file diff --git a/locales/zh-CN.json b/locales/zh-CN.json new file mode 100644 index 00000000..9eeffb47 --- /dev/null +++ b/locales/zh-CN.json @@ -0,0 +1,101 @@ +{ + "landing": { + "side": { + "title": "Chat Chat", + "description": "解锁与人工智能的下一级对话", + "New Conversation": "新建对话", + "Search History": "搜索历史", + "App Settings": "应用设置", + "AI Service Provider": "人工智能服务提供商", + "Goodwill Reminders": "友好提示", + "Stable": "稳定版", + "Beta": "测试版", + "Standard": "标准版", + "Creative": "创意版", + "Reset": "重置", + "Save": "保存", + "Planned": "计划中", + "Login": "登录", + "Language": "语言", + "System": "系统", + "Dark": "暗色模式", + "Light": "亮色模式" + }, + "main": { + "Edit": "编辑", + "Copy": "复制", + "Regenerate": "重新生成", + "Speech": "语音", + "Process": "处理", + "Conversation Settings": "对话设置", + "Advanced Conversation Settings": "高级对话设置", + "Text To Speech": "文本转语音", + "Web Search": "网页搜索", + "Advanced": "高级", + "Voice": "声音", + "Speaking Speed": "语速", + "Sample": "示例", + "Speak": "朗读", + "Text to Speech is not supported in your browser": "您的浏览器不支持文本转语音。", + "Search Engine": "搜索引擎", + "Send Message using Enter Key": "使用回车键发送消息", + "History": "历史记录", + "Clear": "清空", + "Import": "导入", + "Export": "导出", + "Cancel": "取消", + "Save": "保存", + "plugins": "插件", + "no context": "无上下文", + "Stop Generating": "停止生成", + "Share": "分享", + "this conversation": "此对话", + "Current Model": "当前模型", + "Temperature": "温度", + "Stream Messages": "消息流", + "No Context Mode": "无上下文模式", + "Plugins": "插件", + "Beta": "测试版" + } + }, + "auth": { + "Home": "首页", + "Sign In": "登录", + "Register": "注册", + "Sign In With Email": "使用电子邮件登录", + "Login In": "登录", + "New User?": "新用户?", + "Already have an account with us?": "已经有账号了?", + "Sign up": "注册" + }, + "dashboard": { + "Back": "返回", + "Language": "语言", + "Auto upload your records to the cloud": "自动上传您的记录到云端", + "Full Name": "姓名", + "Email Address": "电子邮件地址", + "Avatar": "头像", + "Confirm": "确认", + "Confirming Deletion": "确认删除", + "Delete Account": "删除账户", + "Please note: This could not be undone.": "请注意:此操作无法撤销。", + "Export All": "全部导出", + "Delete All": "全部删除", + "Share": "分享", + "Confirm to quit Team": "确认退出团队", + "This action cannot be undone.": "此操作无法撤销。", + "Edit Team": "编辑团队", + "Name": "名称", + "Access Code": "访问码", + "Save": "保存", + "Confirm to delete Team": "确认删除团队", + "This action cannot be undone. This will permanently delete your team and remove your data from our database.": "此操作无法撤销。这将永久删除您的团队并将您的数据从我们的数据库中删除。", + "Create Team": "创建团队", + "Join Team": "加入团队", + "Join": "加入", + "Create": "创建", + "Sign out": "退出", + "If you want to permanently remove your account from": "如果您想永久删除您的账户", + "Please click the button below, please note, This could not be undone": "请点击下面的按钮,注意,此操作无法撤销" + } +} \ No newline at end of file diff --git a/locales/zh-HK.json b/locales/zh-HK.json new file mode 100644 index 00000000..35c9a664 --- /dev/null +++ b/locales/zh-HK.json @@ -0,0 +1,101 @@ +{ + "landing": { + "side": { + "title": "Chat Chat", + "description": "解鎖與人工智能的下一級對話", + "New Conversation": "新建對話", + "Search History": "搜索歷史", + "App Settings": "應用程式設定", + "AI Service Provider": "人工智能服務提供商", + "Goodwill Reminders": "友好提示", + "Stable": "穩定版", + "Beta": "測試版", + "Standard": "標準版", + "Creative": "創意版", + "Reset": "重設", + "Save": "儲存", + "Planned": "計劃中", + "Login": "登入", + "Language": "語言", + "System": "系統", + "Dark": "暗色模式", + "Light": "亮色模式" + }, + "main": { + "Edit": "編輯", + "Copy": "複製", + "Regenerate": "重新生成", + "Speech": "語音", + "Process": "處理", + "Conversation Settings": "對話設定", + "Advanced Conversation Settings": "高級對話設定", + "Text To Speech": "文本轉語音", + "Web Search": "網頁搜索", + "Advanced": "高級", + "Voice": "聲音", + "Speaking Speed": "語速", + "Sample": "示例", + "Speak": "朗讀", + "Text to Speech is not supported in your browser": "您的瀏覽器不支持文本轉語音。", + "Search Engine": "搜索引擎", + "Send Message using Enter Key": "使用回車鍵發送消息", + "History": "歷史記錄", + "Clear": "清空", + "Import": "導入", + "Export": "匯出", + "Cancel": "取消", + "Save": "儲存", + "plugins": "插件", + "no context": "無上下文", + "Stop Generating": "停止生成", + "Share": "分享", + "this conversation": "這個對話", + "Current Model": "當前模型", + "Temperature": "溫度", + "Stream Messages": "即時消息", + "No Context Mode": "無上下文模式", + "Plugins": "插件", + "Beta": "測試版" + } + }, + "auth": { + "Home": "主頁", + "Sign In": "登入", + "Register": "註冊", + "Sign In With Email": "使用電子郵件登入", + "Login In": "登入", + "New User?": "新用戶?", + "Already have an account with us?": "已經有帳戶了嗎?", + "Sign up": "註冊" + }, + "dashboard": { + "Back": "返回", + "Language": "語言", + "Auto upload your records to the cloud": "自動上載您的記錄到雲端", + "Full Name": "全名", + "Email Address": "電郵地址", + "Avatar": "頭像", + "Confirm": "確認", + "Confirming Deletion": "確認刪除", + "Delete Account": "刪除帳戶", + "Please note: This could not be undone.": "請注意:此操作無法撤銷。", + "Export All": "全部匯出", + "Delete All": "全部刪除", + "Share": "分享", + "Confirm to quit Team": "確認退出團隊", + "This action cannot be undone": "此操作無法撤銷", + "Edit Team": "編輯團隊", + "Name": "名稱", + "Access Code": "存取碼", + "Save": "儲存", + "Confirm to delete Team": "確認刪除團隊", + "This will permanently delete your team and remove your data from our database": "此操作無法撤銷。這將永久刪除您的團隊並將您的數據從我們的數據庫中刪除", + "Create Team": "建立團隊", + "Join Team": "加入團隊", + "Join": "加入", + "Create": "建立", + "Sign out": "登出", + "If you want to permanently remove your account from": "如果您想永久刪除您的帳戶", + "Please click the button below, please note, This could not be undone": "請點擊下面的按鈕,請注意,此操作無法撤銷。" + } +} \ No newline at end of file diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 00000000..f750f96f --- /dev/null +++ b/middleware.ts @@ -0,0 +1,14 @@ +import createMiddleware from 'next-intl/middleware'; + +export default createMiddleware({ + // A list of all locales that are supported + locales: ['en', 'zh-CN', 'zh-HK'], + + // If this locale is matched, pathnames work without a prefix (e.g. `/about`) + defaultLocale: 'en', +}); + +export const config = { + // Skip all paths that should not be internationalized + matcher: ['/((?!api|_next|.*\\..*).*)'], +}; diff --git a/package.json b/package.json index f6908b48..4104f027 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "lucide-react": "0.154.0", "next": "13.4.1", "next-auth": "^4.22.0", + "next-intl": "^2.13.2", "next-pwa": "^5.6.0", "next-themes": "^0.2.1", "nodemailer": "^6.9.1", diff --git a/yarn.lock b/yarn.lock index 5874c827..7cf8d41c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -981,6 +981,60 @@ "@floating-ui/dom" "^0.5.3" use-isomorphic-layout-effect "^1.1.1" +"@formatjs/ecma402-abstract@1.11.4": + version "1.11.4" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz#b962dfc4ae84361f9f08fbce411b4e4340930eda" + integrity sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw== + dependencies: + "@formatjs/intl-localematcher" "0.2.25" + tslib "^2.1.0" + +"@formatjs/ecma402-abstract@^1.11.4": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.15.0.tgz#0a285a5dc69889e15d53803bd5036272e23e5a18" + integrity sha512-7bAYAv0w4AIao9DNg0avfOLTCPE9woAgs6SpXuMq11IN3A+l+cq8ghczwqSZBM11myvPSJA7vLn72q0rJ0QK6Q== + dependencies: + "@formatjs/intl-localematcher" "0.2.32" + tslib "^2.4.0" + +"@formatjs/fast-memoize@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz#e6f5aee2e4fd0ca5edba6eba7668e2d855e0fc21" + integrity sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg== + dependencies: + tslib "^2.1.0" + +"@formatjs/icu-messageformat-parser@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz#a54293dd7f098d6a6f6a084ab08b6d54a3e8c12d" + integrity sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw== + dependencies: + "@formatjs/ecma402-abstract" "1.11.4" + "@formatjs/icu-skeleton-parser" "1.3.6" + tslib "^2.1.0" + +"@formatjs/icu-skeleton-parser@1.3.6": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz#4ce8c0737d6f07b735288177049e97acbf2e8964" + integrity sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg== + dependencies: + "@formatjs/ecma402-abstract" "1.11.4" + tslib "^2.1.0" + +"@formatjs/intl-localematcher@0.2.25": + version "0.2.25" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz#60892fe1b271ec35ba07a2eb018a2dd7bca6ea3a" + integrity sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA== + dependencies: + tslib "^2.1.0" + +"@formatjs/intl-localematcher@0.2.32": + version "0.2.32" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz#00d4d307cd7d514b298e15a11a369b86c8933ec1" + integrity sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ== + dependencies: + tslib "^2.4.0" + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz" @@ -3659,6 +3713,16 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" +intl-messageformat@^9.3.18: + version "9.13.0" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.13.0.tgz#97360b73bd82212e4f6005c712a4a16053165468" + integrity sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw== + dependencies: + "@formatjs/ecma402-abstract" "1.11.4" + "@formatjs/fast-memoize" "1.2.1" + "@formatjs/icu-messageformat-parser" "2.1.0" + tslib "^2.1.0" + invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -4707,6 +4771,11 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + next-auth@^4.22.0: version "4.22.1" resolved "https://registry.npmjs.org/next-auth/-/next-auth-4.22.1.tgz" @@ -4722,6 +4791,15 @@ next-auth@^4.22.0: preact-render-to-string "^5.1.19" uuid "^8.3.2" +next-intl@^2.13.2: + version "2.13.2" + resolved "https://registry.yarnpkg.com/next-intl/-/next-intl-2.13.2.tgz#664e95e1a300c5a41d82a7bfce112fc6de137336" + integrity sha512-wDkAP8IWcK23XMcQxUbGmoXowMkBMAbxD5OTeFLALVelNqx4+aZ/dkLRNmXcBBn1UUzp9gc6k6FrA0B4FVkf1Q== + dependencies: + "@formatjs/intl-localematcher" "0.2.32" + negotiator "0.6.3" + use-intl "^2.13.2" + next-pwa@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/next-pwa/-/next-pwa-5.6.0.tgz#f7b1960c4fdd7be4253eb9b41b612ac773392bf4" @@ -6207,6 +6285,14 @@ use-composed-ref@^1.3.0: resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz" integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== +use-intl@^2.13.2: + version "2.13.2" + resolved "https://registry.yarnpkg.com/use-intl/-/use-intl-2.13.2.tgz#17316374269800f3489ca576eb0bf390c99bd204" + integrity sha512-68MUzhKV9Q58DkNqjM87fSIPpBW3T5OI9SRYeOSrs/1za25btnt2Pmjo598ZA3Nd/Nw3IL317hoIkP9nIAqd8Q== + dependencies: + "@formatjs/ecma402-abstract" "^1.11.4" + intl-messageformat "^9.3.18" + use-isomorphic-layout-effect@^1.1.1: version "1.1.2" resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz" From 64a360efa4ce6e3f3648b80dde3f5823b4fc28d9 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Fri, 5 May 2023 20:33:11 +0100 Subject: [PATCH 2/6] i18n --- app/[locale]/layout.tsx | 73 ++----------------- app/layout.tsx | 72 ++++++++++++++++++ app/{[locale] => }/not-found.tsx | 0 app/{[locale] => }/robots.ts | 0 components/dashboard/nav.tsx | 8 +- components/dashboard/profile-info-form.tsx | 4 +- components/dashboard/side.tsx | 12 ++- components/dashboard/team/card.tsx | 4 +- components/landing/main/chat-main.tsx | 2 +- components/landing/main/main-settings.tsx | 4 +- components/landing/side/side-app-settings.tsx | 26 +++---- components/landing/side/side.tsx | 18 +++-- locales/en.json | 29 ++++++-- locales/zh-CN.json | 40 ++++++---- locales/zh-HK.json | 38 +++++++--- 15 files changed, 191 insertions(+), 139 deletions(-) create mode 100644 app/layout.tsx rename app/{[locale] => }/not-found.tsx (100%) rename app/{[locale] => }/robots.ts (100%) diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 3f3d9325..0ec082e7 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -4,18 +4,7 @@ import 'tippy.js/dist/tippy.css'; import { NextIntlClientProvider } from 'next-intl'; -import NotFound from '@/app/[locale]/not-found'; - -import { rubik } from '@/app/[locale]/fonts'; - -import { Providers } from '@/app/[locale]/providers'; - -import { Analytics } from '@vercel/analytics/react'; - -import { HotToaster } from '@/components/client/toaster'; -import { ClientCommand } from '@/components/client/command'; - -import { siteConfig } from '@/config/site.config'; +import NotFound from '@/app/not-found'; export default async function RootLayout({ children, params: { locale } }: { children: React.ReactNode; params: { locale: string } }) { let messages; @@ -23,64 +12,12 @@ export default async function RootLayout({ children, params: { locale } }: { chi try { messages = (await import(`../../locales/${locale}.json`)).default; } catch (error) { - NotFound(); + return ; } return ( - - - {siteConfig.title} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -