mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-08-06 22:59:07 +08:00
feat: support i18n
This commit is contained in:
@@ -23,6 +23,7 @@ import DownloadIcon from "../icons/download.svg";
|
||||
import { Message, SubmitKey, useChatStore, ChatSession } from "../store";
|
||||
import { showModal } from "./ui-lib";
|
||||
import { copyToClipboard, downloadAs, isIOS } from "../utils";
|
||||
import Locale from '../locales'
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
@@ -77,7 +78,7 @@ export function ChatItem(props: {
|
||||
>
|
||||
<div className={styles["chat-item-title"]}>{props.title}</div>
|
||||
<div className={styles["chat-item-info"]}>
|
||||
<div className={styles["chat-item-count"]}>{props.count} 条对话</div>
|
||||
<div className={styles["chat-item-count"]}>{Locale.ChatItem.ChatItemCount(props.count)}</div>
|
||||
<div className={styles["chat-item-date"]}>{props.time}</div>
|
||||
</div>
|
||||
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
||||
@@ -200,7 +201,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<div className={styles["window-header-title"]}>
|
||||
<div className={styles["window-header-main-title"]}>{session.topic}</div>
|
||||
<div className={styles["window-header-sub-title"]}>
|
||||
与 ChatGPT 的 {session.messages.length} 条对话
|
||||
{Locale.Chat.SubTitle(session.messages.length)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["window-actions"]}>
|
||||
@@ -208,7 +209,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<IconButton
|
||||
icon={<MenuIcon />}
|
||||
bordered
|
||||
title="查看消息列表"
|
||||
title={Locale.Chat.Actions.ChatList}
|
||||
onClick={props?.showSideBar}
|
||||
/>
|
||||
</div>
|
||||
@@ -216,7 +217,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<IconButton
|
||||
icon={<BrainIcon />}
|
||||
bordered
|
||||
title="查看压缩后的历史 Prompt"
|
||||
title={Locale.Chat.Actions.CompressedHistory}
|
||||
onClick={() => {
|
||||
showMemoryPrompt(session)
|
||||
}}
|
||||
@@ -226,7 +227,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<IconButton
|
||||
icon={<ExportIcon />}
|
||||
bordered
|
||||
title="导出聊天记录"
|
||||
title={Locale.Chat.Actions.Export}
|
||||
onClick={() => {
|
||||
exportMessages(session.messages, session.topic)
|
||||
}}
|
||||
@@ -251,7 +252,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<Avatar role={message.role} />
|
||||
</div>
|
||||
{(message.preview || message.streaming) && (
|
||||
<div className={styles["chat-message-status"]}>正在输入…</div>
|
||||
<div className={styles["chat-message-status"]}>{Locale.Chat.Typing}</div>
|
||||
)}
|
||||
<div className={styles["chat-message-item"]}>
|
||||
{(message.preview || message.content.length === 0) &&
|
||||
@@ -283,7 +284,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
<div className={styles["chat-input-panel-inner"]}>
|
||||
<textarea
|
||||
className={styles["chat-input"]}
|
||||
placeholder={`输入消息,${submitKey} 发送`}
|
||||
placeholder={Locale.Chat.Input(submitKey)}
|
||||
rows={3}
|
||||
onInput={(e) => setUserInput(e.currentTarget.value)}
|
||||
value={userInput}
|
||||
@@ -291,7 +292,7 @@ export function Chat(props: { showSideBar?: () => void }) {
|
||||
/>
|
||||
<IconButton
|
||||
icon={<SendWhiteIcon />}
|
||||
text={"发送"}
|
||||
text={Locale.Chat.Send}
|
||||
className={styles["chat-input-send"] + " no-dark"}
|
||||
onClick={onUserSubmit}
|
||||
/>
|
||||
@@ -322,21 +323,21 @@ function exportMessages(messages: Message[], topic: string) {
|
||||
const filename = `${topic}.md`
|
||||
|
||||
showModal({
|
||||
title: "导出聊天记录为 Markdown", children: <div className="markdown-body">
|
||||
title: Locale.Export.Title, children: <div className="markdown-body">
|
||||
<pre className={styles['export-content']}>{mdText}</pre>
|
||||
</div>, actions: [
|
||||
<IconButton key="copy" icon={<CopyIcon />} bordered text="全部复制" onClick={() => copyToClipboard(mdText)} />,
|
||||
<IconButton key="download" icon={<DownloadIcon />} bordered text="下载文件" onClick={() => downloadAs(mdText, filename)} />
|
||||
<IconButton key="copy" icon={<CopyIcon />} bordered text={Locale.Export.Copy} onClick={() => copyToClipboard(mdText)} />,
|
||||
<IconButton key="download" icon={<DownloadIcon />} bordered text={Locale.Export.Download} onClick={() => downloadAs(mdText, filename)} />
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function showMemoryPrompt(session: ChatSession) {
|
||||
showModal({
|
||||
title: `上下文记忆 Prompt (${session.lastSummarizeIndex} of ${session.messages.length})`, children: <div className="markdown-body">
|
||||
<pre className={styles['export-content']}>{session.memoryPrompt || '无'}</pre>
|
||||
title: `${Locale.Memory.Title} (${session.lastSummarizeIndex} of ${session.messages.length})`, children: <div className="markdown-body">
|
||||
<pre className={styles['export-content']}>{session.memoryPrompt || Locale.Memory.EmptyContent}</pre>
|
||||
</div>, actions: [
|
||||
<IconButton key="copy" icon={<CopyIcon />} bordered text="全部复制" onClick={() => copyToClipboard(session.memoryPrompt)} />,
|
||||
<IconButton key="copy" icon={<CopyIcon />} bordered text={Locale.Memory.Copy} onClick={() => copyToClipboard(session.memoryPrompt)} />,
|
||||
]
|
||||
})
|
||||
}
|
||||
@@ -405,7 +406,7 @@ export function Home() {
|
||||
<div>
|
||||
<IconButton
|
||||
icon={<AddIcon />}
|
||||
text={"新的聊天"}
|
||||
text={Locale.Home.NewChat}
|
||||
onClick={createNewSession}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
|
||||
import EmojiPicker, { Emoji, Theme as EmojiTheme } from "emoji-picker-react";
|
||||
import EmojiPicker, { Theme as EmojiTheme } from "emoji-picker-react";
|
||||
|
||||
import styles from "./settings.module.scss";
|
||||
|
||||
@@ -14,6 +14,8 @@ import { IconButton } from "./button";
|
||||
import { SubmitKey, useChatStore, Theme } from "../store";
|
||||
import { Avatar } from "./home";
|
||||
|
||||
import Locale, { changeLang, getLang } from '../locales'
|
||||
|
||||
export function Settings(props: { closeSettings: () => void }) {
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
const [config, updateConfig, resetConfig, clearAllData] = useChatStore((state) => [
|
||||
@@ -27,8 +29,8 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
<>
|
||||
<div className={styles["window-header"]}>
|
||||
<div className={styles["window-header-title"]}>
|
||||
<div className={styles["window-header-main-title"]}>设置</div>
|
||||
<div className={styles["window-header-sub-title"]}>设置选项</div>
|
||||
<div className={styles["window-header-main-title"]}>{Locale.Settings.Title}</div>
|
||||
<div className={styles["window-header-sub-title"]}>{Locale.Settings.SubTitle}</div>
|
||||
</div>
|
||||
<div className={styles["window-actions"]}>
|
||||
<div className={styles["window-action-button"]}>
|
||||
@@ -36,7 +38,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
icon={<ClearIcon />}
|
||||
onClick={clearAllData}
|
||||
bordered
|
||||
title="清除所有数据"
|
||||
title={Locale.Settings.Actions.ClearAll}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["window-action-button"]}>
|
||||
@@ -44,7 +46,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
icon={<ResetIcon />}
|
||||
onClick={resetConfig}
|
||||
bordered
|
||||
title="重置所有选项"
|
||||
title={Locale.Settings.Actions.ResetAll}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles["window-action-button"]}>
|
||||
@@ -52,7 +54,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
icon={<CloseIcon />}
|
||||
onClick={props.closeSettings}
|
||||
bordered
|
||||
title="关闭"
|
||||
title={Locale.Settings.Actions.Close}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,7 +62,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
<div className={styles["settings"]}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>头像</div>
|
||||
<div className={styles["settings-title"]}>{Locale.Settings.Avatar}</div>
|
||||
<Popover
|
||||
onClose={() => setShowEmojiPicker(false)}
|
||||
content={
|
||||
@@ -85,7 +87,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>发送键</div>
|
||||
<div className={styles["settings-title"]}>{Locale.Settings.SendKey}</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={config.submitKey}
|
||||
@@ -106,7 +108,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>主题</div>
|
||||
<div className={styles["settings-title"]}>{Locale.Settings.Theme}</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={config.theme}
|
||||
@@ -126,7 +128,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>紧凑边框</div>
|
||||
<div className={styles["settings-title"]}>{Locale.Settings.TightBorder}</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.tightBorder}
|
||||
@@ -137,10 +139,30 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>{Locale.Settings.Lang.Name}</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={getLang()}
|
||||
onChange={(e) => {
|
||||
changeLang(e.target.value as any)
|
||||
}}
|
||||
>
|
||||
<option value='en' key='en'>
|
||||
{Locale.Settings.Lang.Options.en}
|
||||
</option>
|
||||
|
||||
<option value='cn' key='cn'>
|
||||
{Locale.Settings.Lang.Options.cn}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ListItem>
|
||||
</List>
|
||||
<List>
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>附带历史消息数</div>
|
||||
<div className={styles["settings-title"]}>{Locale.Settings.HistoryCount}</div>
|
||||
<input
|
||||
type="range"
|
||||
title={config.historyMessageCount.toString()}
|
||||
@@ -159,7 +181,7 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>
|
||||
历史消息压缩长度阈值
|
||||
{Locale.Settings.CompressThreshold}
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
@@ -173,21 +195,6 @@ export function Settings(props: { closeSettings: () => void }) {
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>
|
||||
上下文中包含机器人消息
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sendBotMessages}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) => (config.sendBotMessages = e.currentTarget.checked)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</>
|
||||
|
Reference in New Issue
Block a user