diff --git a/src/main/index.ts b/src/main/index.ts
index 4bd293ab..e4958acb 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -37,11 +37,11 @@ function createWindow(): void {
mainWindow.webContents.on('context-menu', () => {
const menu = new Menu()
- menu.append(new MenuItem({ label: 'Copy', role: 'copy' }))
- menu.append(new MenuItem({ label: 'Paste', role: 'paste' }))
- menu.append(new MenuItem({ label: 'Cut', role: 'cut' }))
+ menu.append(new MenuItem({ label: '复制', role: 'copy', sublabel: '⌘ + C' }))
+ menu.append(new MenuItem({ label: '粘贴', role: 'paste', sublabel: '⌘ + V' }))
+ menu.append(new MenuItem({ label: '剪切', role: 'cut', sublabel: '⌘ + X' }))
menu.append(new MenuItem({ type: 'separator' }))
- menu.append(new MenuItem({ label: 'Select All', role: 'selectAll' }))
+ menu.append(new MenuItem({ label: '全选', role: 'selectAll', sublabel: '⌘ + A' }))
menu.popup()
})
@@ -68,7 +68,7 @@ function createWindow(): void {
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
- electronApp.setAppUserModelId('com.electron')
+ electronApp.setAppUserModelId('com.kangfenmao.CherryStudio')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 252a5183..fc1826ee 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -1,5 +1,5 @@
-
+
Cherry Studio
diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx
index 362229b8..d1fad168 100644
--- a/src/renderer/src/App.tsx
+++ b/src/renderer/src/App.tsx
@@ -1,20 +1,20 @@
import '@fontsource/inter'
import store, { persistor } from '@renderer/store'
+import { ConfigProvider } from 'antd'
import { Provider } from 'react-redux'
import { HashRouter, Route, Routes } from 'react-router-dom'
import { PersistGate } from 'redux-persist/integration/react'
import Sidebar from './components/app/Sidebar'
+import TopViewContainer from './components/TopView'
+import { AntdThemeConfig, getAntdLocale } from './config/antd'
+import './i18n'
import AppsPage from './pages/apps/AppsPage'
import HomePage from './pages/home/HomePage'
import SettingsPage from './pages/settings/SettingsPage'
-import { ConfigProvider } from 'antd'
-import TopViewContainer from './components/TopView'
-import { AntdThemeConfig } from './config/antd'
-import './i18n'
function App(): JSX.Element {
return (
-
+
diff --git a/src/renderer/src/components/Popups/AssistantSettingPopup.tsx b/src/renderer/src/components/Popups/AssistantSettingPopup.tsx
index abca8ab4..a3c55c17 100644
--- a/src/renderer/src/components/Popups/AssistantSettingPopup.tsx
+++ b/src/renderer/src/components/Popups/AssistantSettingPopup.tsx
@@ -4,6 +4,7 @@ import { TopView } from '../TopView'
import { Box } from '../Layout'
import { Assistant } from '@renderer/types'
import TextArea from 'antd/es/input/TextArea'
+import { useTranslation } from 'react-i18next'
interface AssistantSettingPopupShowParams {
assistant: Assistant
@@ -18,6 +19,7 @@ const AssistantSettingPopupContainer: React.FC = ({ assistant, resolve })
const [description, setDescription] = useState(assistant.description)
const [prompt, setPrompt] = useState(assistant.prompt)
const [open, setOpen] = useState(true)
+ const { t } = useTranslation()
const onOk = () => {
setOpen(false)
@@ -33,21 +35,30 @@ const AssistantSettingPopupContainer: React.FC = ({ assistant, resolve })
return (
- Name
- setName(e.target.value)} />
+ {t('common.name')}
+ setName(e.target.value)}
+ />
- Description
+ {t('common.description')}
)
}
diff --git a/src/renderer/src/config/antd.ts b/src/renderer/src/config/antd.ts
index 1f640c97..34c3baa5 100644
--- a/src/renderer/src/config/antd.ts
+++ b/src/renderer/src/config/antd.ts
@@ -1,4 +1,6 @@
+import store from '@renderer/store'
import { theme, ThemeConfig } from 'antd'
+import zhCN from 'antd/locale/zh_CN'
export const colorPrimary = '#00b96b'
@@ -9,3 +11,16 @@ export const AntdThemeConfig: ThemeConfig = {
},
algorithm: [theme.darkAlgorithm]
}
+
+export function getAntdLocale() {
+ const language = store.getState().settings.language
+
+ switch (language) {
+ case 'zh-CN':
+ return zhCN
+ case 'en-US':
+ return undefined
+ default:
+ return zhCN
+ }
+}
diff --git a/src/renderer/src/config/assistant.ts b/src/renderer/src/config/assistant.ts
index c5db7d38..4ff28e9f 100644
--- a/src/renderer/src/config/assistant.ts
+++ b/src/renderer/src/config/assistant.ts
@@ -1,31 +1,28 @@
import { SystemAssistant } from '@renderer/types'
export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
- // Article
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D29',
name: '文章总结',
description: '自动总结文章内容,帮助读者从中获取更多的信息',
prompt: '总结下面的文章,给出总结、摘要、观点三个部分内容,其中观点部分要使用列表列出,使用 Markdown 回复',
- group: 'Article'
+ group: '文章'
},
- // Writing
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30',
name: '论文',
description: '根据主题撰写内容翔实、有信服力的论文',
prompt:
'我希望你能作为一名学者行事。你将负责研究一个你选择的主题,并将研究结果以论文或文章的形式呈现出来。你的任务是确定可靠的来源,以结构良好的方式组织材料,并以引用的方式准确记录。',
- group: 'Writing'
+ group: '写作'
},
- // Translation
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D40',
name: '翻译成中文',
description: '你是一个好用的翻译助手, 可以把任何语言翻译成中文',
prompt:
'你是一个好用的翻译助手。请将我的英文翻译成中文,将所有非中文的翻译成中文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合中文的语言习惯。',
- group: 'Translation'
+ group: '翻译'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D41',
@@ -33,16 +30,15 @@ export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
description: '你是一个好用的翻译助手, 可以把任何语言翻译成英文',
prompt:
'你是一个好用的翻译助手。请将我的中文翻译成英文,将所有非中文的翻译成英文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合英文的语言习惯。',
- group: 'Translation'
+ group: '翻译'
},
- // Software Engineer
{
id: '43CEDACF-C9EB-431B-848C-4D08EC26EB90',
name: '软件工程师',
description: '高级软件工程师,可以解答各种技术问题',
prompt:
'你是一个高级软件工程师,你需要帮我解答各种技术难题、设计技术方案以及编写代码。你编写的代码必须可以正常运行,而且没有任何 Bug 和其他问题。',
- group: 'Software Engineer'
+ group: '软件工程师'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2A',
@@ -50,7 +46,7 @@ export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
description: '高级前端工程师,可以解答各种技术问题',
prompt:
'你擅长使用 TypeScript, JavaScript, HMLT, CSS 等编程语言。同时你还会使用 Node.js 及各种包来解决开发中遇到的问题。你还会使用 React, Vue 等前端框架。对于我的问题希望你能给出具体的代码示例,最好能够封装成一个函数方便我复制运行测试。',
- group: 'Software Engineer'
+ group: '软件工程师'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2B',
@@ -58,98 +54,97 @@ export const SYSTEM_ASSISTANTS: SystemAssistant[] = [
description: '高级后端工程师,可以解答各种技术问题',
prompt:
'高级后端工程师,技术难题解答,服务器架构,数据库优化,API设计,网络安全,代码审查,性能调优,微服务,分布式系统,容器技术,持续集成/持续部署(CI/CD)。',
- group: 'Software Engineer'
+ group: '软件工程师'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2D',
name: '测试工程师',
description: '高级测试工程师,可以解答各种测试相关问题',
prompt: '你是一个高级测试工程师,你需要帮我解答各种技术难题',
- group: 'Software Engineer'
+ group: '软件工程师'
},
- // Programming Languages Assistants
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2E',
name: 'Python 工程师',
description: '你是一个高级Python工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Python工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D2F',
name: 'Java 工程师',
description: '你是一个高级Java工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Java工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D30',
name: 'C# 工程师',
description: '你是一个高级C#工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级C#工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D31',
name: 'C++ 工程师',
description: '你是一个高级C++工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级C++工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D32',
name: 'C 工程师',
description: '你是一个高级C工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级C工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D33',
name: 'Go 工程师',
description: '你是一个高级Go工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Go工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D34',
name: 'Rust 工程师',
description: '你是一个高级Rust工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Rust工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D35',
name: 'PHP 工程师',
description: '你是一个高级PHP工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级PHP工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D36',
name: 'Ruby 工程师',
description: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Ruby工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D37',
name: 'Swift 工程师',
description: '你是一个高级Swift工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Swift工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D38',
name: 'Kotlin 工程师',
description: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Kotlin工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
},
{
id: '6B1D8E9F-9B7F-4E2B-8FBB-0F5B6F7B0D39',
name: 'Dart 工程师',
description: '你是一个高级Dart工程师,你需要帮我解答各种技术难题',
prompt: '你是一个高级Dart工程师,你需要帮我解答各种技术难题',
- group: 'Programming Languages'
+ group: '编程语言'
}
]
diff --git a/src/renderer/src/config/constant.ts b/src/renderer/src/config/constant.ts
index d1db0dff..e69de29b 100644
--- a/src/renderer/src/config/constant.ts
+++ b/src/renderer/src/config/constant.ts
@@ -1 +0,0 @@
-export const DEFAULT_TOPIC_NAME = 'Default Topic'
diff --git a/src/renderer/src/config/provider.ts b/src/renderer/src/config/provider.ts
new file mode 100644
index 00000000..7e480247
--- /dev/null
+++ b/src/renderer/src/config/provider.ts
@@ -0,0 +1,73 @@
+export const PROVIDER_CONFIG = {
+ openai: {
+ websites: {
+ official: 'https://openai.com/',
+ apiKey: 'https://platform.openai.com/api-keys',
+ docs: 'https://platform.openai.com/docs',
+ models: 'https://platform.openai.com/docs/models'
+ }
+ },
+ silicon: {
+ websites: {
+ official: 'https://www.siliconflow.cn/',
+ apiKey: 'https://cloud.siliconflow.cn/account/ak',
+ docs: 'https://docs.siliconflow.cn/',
+ models: 'https://docs.siliconflow.cn/docs/model-names'
+ }
+ },
+ deepseek: {
+ websites: {
+ official: 'https://deepseek.com/',
+ apiKey: 'https://platform.deepseek.com/api_keys',
+ docs: 'https://platform.deepseek.com/api-docs/',
+ models: 'https://platform.deepseek.com/api-docs/'
+ }
+ },
+ yi: {
+ websites: {
+ official: 'https://platform.lingyiwanwu.com/',
+ apiKey: 'https://platform.lingyiwanwu.com/apikeys',
+ docs: 'https://platform.lingyiwanwu.com/docs',
+ models: 'https://platform.lingyiwanwu.com/docs#%E6%A8%A1%E5%9E%8B'
+ }
+ },
+ zhipu: {
+ websites: {
+ official: 'https://open.bigmodel.cn/',
+ apiKey: 'https://open.bigmodel.cn/usercenter/apikeys',
+ docs: 'https://open.bigmodel.cn/dev/howuse/introduction',
+ models: 'https://open.bigmodel.cn/modelcenter/square'
+ }
+ },
+ moonshot: {
+ websites: {
+ official: 'https://moonshot.ai/',
+ apiKey: 'https://platform.moonshot.cn/console/api-keys',
+ docs: 'https://platform.moonshot.cn/docs/',
+ models: 'https://platform.moonshot.cn/docs/intro#%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8'
+ }
+ },
+ openrouter: {
+ websites: {
+ official: 'https://openrouter.ai/',
+ apiKey: 'https://openrouter.ai/settings/keys',
+ docs: 'https://openrouter.ai/docs/quick-start',
+ models: 'https://openrouter.ai/docs/models'
+ }
+ },
+ groq: {
+ websites: {
+ official: 'https://groq.com/',
+ apiKey: 'https://console.groq.com/keys',
+ docs: 'https://console.groq.com/docs/quickstart',
+ models: 'https://console.groq.com/docs/models'
+ }
+ },
+ ollama: {
+ websites: {
+ official: 'https://ollama.com/',
+ docs: 'https://github.com/ollama/ollama/tree/main/docs',
+ models: 'https://ollama.com/library'
+ }
+ }
+}
diff --git a/src/renderer/src/hooks/useAppInitEffect.ts b/src/renderer/src/hooks/useAppInitEffect.ts
index 83caa8c2..d8510b88 100644
--- a/src/renderer/src/hooks/useAppInitEffect.ts
+++ b/src/renderer/src/hooks/useAppInitEffect.ts
@@ -11,7 +11,6 @@ export function useAppInitEffect() {
runAsyncFunction(async () => {
const storedImage = await LocalStorage.getImage('avatar')
storedImage && dispatch(setAvatar(storedImage))
- console.debug('Avatar loaded from storage')
})
}, [dispatch])
}
diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts
index 214ac0e6..60776ddb 100644
--- a/src/renderer/src/i18n/index.ts
+++ b/src/renderer/src/i18n/index.ts
@@ -1,28 +1,217 @@
+import store from '@renderer/store'
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
const resources = {
'en-US': {
translation: {
+ common: {
+ avatar: 'Avatar',
+ language: 'Language',
+ model: 'Model',
+ models: 'Models',
+ topics: 'Topics',
+ docs: 'Docs',
+ and: 'and',
+ assistant: 'Assistant',
+ name: 'Name',
+ description: 'Description',
+ prompt: 'Prompt',
+ rename: 'Rename',
+ delete: 'Delete',
+ edit: 'Edit',
+ duplicate: 'Duplicate',
+ copy: 'Copy',
+ regenerate: 'Regenerate',
+ provider: 'Provider'
+ },
+ button: {
+ add: 'Add',
+ manage: 'Manage',
+ select_model: 'Select Model'
+ },
+ message: {
+ copied: 'Copied!',
+ 'assistant.added.content': 'Assistant added successfully',
+ 'message.delete.title': 'Delete Message',
+ 'message.delete.content': 'Are you sure you want to delete this message?',
+ 'error.enter.api.key': 'Please enter your API key first',
+ 'error.enter.api.host': 'Please enter your API host first',
+ 'error.enter.model': 'Please select a model first',
+ 'api.connection.failed': 'Connection failed',
+ 'api.connection.success': 'Connection successful'
+ },
+ assistant: {
+ 'default.name': 'Default Assistant',
+ 'default.description': "Hello, I'm Default Assistant. You can start chatting with me right away",
+ 'default.topic.name': 'Default Topic',
+ 'topics.title': 'Topics',
+ 'topics.hide_topics': 'Hide Topics',
+ 'topics.show_topics': 'Show Topics',
+ 'topics.auto_rename': 'Auto Rename',
+ 'topics.edit.title': 'Rename',
+ 'topics.edit.placeholder': 'Enter new name',
+ 'topics.delete.all.title': 'Delete all topics',
+ 'topics.delete.all.content': 'Are you sure you want to delete all topics?',
+ 'input.new_chat': ' New Chat ',
+ 'input.topics': ' Topics ',
+ 'input.clear': 'Clear',
+ 'input.expand': 'Expand',
+ 'input.collapse': 'Collapse',
+ 'input.clear.title': 'Clear all messages?',
+ 'input.clear.content': 'Are you sure to clear all messages?',
+ 'input.placeholder': 'Type your message here...',
+ 'input.send': 'Send'
+ },
+ apps: {
+ title: 'Agents'
+ },
+ provider: {
+ openai: 'OpenAI',
+ deepseek: 'DeepSeek',
+ moonshot: 'Moonshot',
+ silicon: 'SiliconFlow',
+ openrouter: 'OpenRouter',
+ yi: 'Lingyiwanwu',
+ zhipu: 'BigModel',
+ groq: 'Groq',
+ ollama: 'Ollama'
+ },
settings: {
title: 'Settings',
general: 'General',
provider: 'Model Provider',
model: 'Model Settings',
assistant: 'Default Assistant',
- about: 'About'
+ about: 'About',
+ 'general.title': 'General Settings',
+ 'provider.api_key': 'API Key',
+ 'provider.check': 'Check',
+ 'provider.get_api_key': 'Get API Key',
+ 'provider.api_host': 'API Host',
+ 'provider.docs_check': 'Check',
+ 'provider.docs_more_details': 'for more details',
+ 'provider.search_placeholder': 'Search model id or name',
+ 'models.default_assistant_model': 'Default Assistant Model',
+ 'models.topic_naming_model': 'Topic Naming Model',
+ 'models.add.add_model': 'Add Model',
+ 'models.add.provider_name.placeholder': 'Provider Name',
+ 'models.add.model_id.placeholder': 'Required e.g. gpt-3.5-turbo',
+ 'models.add.model_id': 'Model ID',
+ 'models.add.model_id.tooltip': 'Example: gpt-3.5-turbo',
+ 'models.add.model_name': 'Model Name',
+ 'models.add.model_name.placeholder': 'Optional e.g. GPT-4',
+ 'models.add.group_name': 'Group Name',
+ 'models.add.group_name.tooltip': 'Optional e.g. ChatGPT',
+ 'models.add.group_name.placeholder': 'Optional e.g. ChatGPT',
+ 'assistant.title': 'Default Assistant',
+ 'about.description': 'A powerful AI assistant for producer'
}
}
},
'zh-CN': {
translation: {
+ common: {
+ avatar: '头像',
+ language: '语言',
+ model: '模型',
+ models: '模型',
+ topics: '话题',
+ docs: '文档',
+ and: '和',
+ assistant: '智能体',
+ name: '名称',
+ description: '描述',
+ prompt: '提示词',
+ rename: '重命名',
+ delete: '删除',
+ edit: '编辑',
+ duplicate: '复制',
+ copy: '复制',
+ regenerate: '重新生成',
+ provider: '提供商'
+ },
+ button: {
+ add: '添加',
+ manage: '管理',
+ select_model: '选择模型'
+ },
+ message: {
+ copied: '已复制!',
+ 'assistant.added.content': '智能体添加成功',
+ 'message.delete.title': '删除消息',
+ 'message.delete.content': '确定要删除此消息吗?',
+ 'error.enter.api.key': '请输入您的 API 密钥',
+ 'error.enter.api.host': '请输入您的 API 地址',
+ 'error.enter.model': '请选择一个模型',
+ 'api.connection.failed': '连接失败',
+ 'api.connection.successful': '连接成功'
+ },
+ assistant: {
+ 'default.name': '默认助手',
+ 'default.description': '你可以随时随地和我聊天',
+ 'default.topic.name': '默认话题',
+ 'topics.title': '话题',
+ 'topics.hide_topics': '隐藏话题',
+ 'topics.show_topics': '显示话题',
+ 'topics.auto_rename': 'AI 重命名',
+ 'topics.edit.title': '重命名',
+ 'topics.edit.placeholder': '输入新名称',
+ 'topics.delete.all.title': '删除所有话题',
+ 'topics.delete.all.content': '确定要删除所有话题吗?',
+ 'input.new_chat': ' 新聊天 ',
+ 'input.topics': ' 话题 ',
+ 'input.clear': '清除',
+ 'input.expand': '展开',
+ 'input.collapse': '收起',
+ 'input.clear.title': '清除所有消息?',
+ 'input.clear.content': '确定要清除所有消息吗?',
+ 'input.placeholder': '在这里输入消息...',
+ 'input.send': '发送'
+ },
+ apps: {
+ title: '智能体'
+ },
+ provider: {
+ openai: 'OpenAI',
+ deepseek: '深度求索',
+ moonshot: '月之暗面',
+ silicon: '硅基流动',
+ openrouter: 'OpenRouter',
+ yi: '零一万物',
+ zhipu: '智谱AI',
+ groq: 'Groq',
+ ollama: 'Ollama'
+ },
settings: {
title: '设置',
general: '常规',
provider: '模型提供商',
model: '模型设置',
assistant: '默认助手',
- about: '关于'
+ about: '关于',
+ 'general.title': '常规设置',
+ 'provider.api_key': 'API 密钥',
+ 'provider.check': '检查',
+ 'provider.get_api_key': '点击这里获取密钥',
+ 'provider.api_host': 'API 地址',
+ 'provider.docs_check': '查看',
+ 'provider.docs_more_details': '获取更多详情',
+ 'provider.search_placeholder': '搜索模型 ID 或名称',
+ 'models.default_assistant_model': '默认助手模型',
+ 'models.topic_naming_model': '话题命名模型',
+ 'models.add.add_model': '添加模型',
+ 'models.add.provider_name.placeholder': '必填 例如 OpenAI',
+ 'models.add.model_id.placeholder': '必填 例如 gpt-3.5-turbo',
+ 'models.add.model_id': '模型 ID',
+ 'models.add.model_id.tooltip': '例如 gpt-3.5-turbo',
+ 'models.add.model_name': '模型名称',
+ 'models.add.model_name.placeholder': '例如 GPT-3.5',
+ 'models.add.group_name': '分组名称',
+ 'models.add.group_name.tooltip': '例如 ChatGPT',
+ 'models.add.group_name.placeholder': '例如 ChatGPT',
+ 'assistant.title': '默认助手',
+ 'about.description': '一个为创造者而生的 AI 助手'
}
}
}
@@ -30,7 +219,7 @@ const resources = {
i18n.use(initReactI18next).init({
resources,
- lng: 'en-US',
+ lng: store.getState().settings.language || 'en-US',
fallbackLng: 'en-US',
interpolation: {
escapeValue: false
diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx
index 0e3e42f0..8e379104 100644
--- a/src/renderer/src/pages/apps/AppsPage.tsx
+++ b/src/renderer/src/pages/apps/AppsPage.tsx
@@ -9,12 +9,14 @@ import { SystemAssistant } from '@renderer/types'
import { getDefaultAssistant } from '@renderer/services/assistant'
import { useAssistants } from '@renderer/hooks/useAssistant'
import { colorPrimary } from '@renderer/config/antd'
+import { useTranslation } from 'react-i18next'
const { Title } = Typography
const AppsPage: FC = () => {
const { assistants, addAssistant } = useAssistants()
const assistantGroups = groupBy(SYSTEM_ASSISTANTS, 'group')
+ const { t } = useTranslation()
const onAddAssistant = (assistant: SystemAssistant) => {
addAssistant({
@@ -22,7 +24,7 @@ const AppsPage: FC = () => {
...assistant
})
window.message.success({
- content: 'Assistant added successfully',
+ content: t('message.assistant.added.content'),
key: 'assistant-added',
style: { marginTop: '5vh' }
})
@@ -31,7 +33,7 @@ const AppsPage: FC = () => {
return (
- Assistant Market
+ {t('apps.title')}
{Object.keys(assistantGroups).map((group) => (
diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx
index 92d6e247..abfb4cc7 100644
--- a/src/renderer/src/pages/home/HomePage.tsx
+++ b/src/renderer/src/pages/home/HomePage.tsx
@@ -8,12 +8,14 @@ import { uuid } from '@renderer/utils'
import { useShowRightSidebar } from '@renderer/hooks/useStore'
import { Tooltip } from 'antd'
import Navigation from './components/Navigation'
+import { useTranslation } from 'react-i18next'
const HomePage: FC = () => {
const { assistants, addAssistant } = useAssistants()
const [activeAssistant, setActiveAssistant] = useState(assistants[0])
const { showRightSidebar, setShowRightSidebar } = useShowRightSidebar()
const { defaultAssistant } = useDefaultAssistant()
+ const { t } = useTranslation()
const onCreateAssistant = () => {
const assistant = { ...defaultAssistant, id: uuid() }
@@ -31,7 +33,10 @@ const HomePage: FC = () => {
-
+
diff --git a/src/renderer/src/pages/home/components/Assistants.tsx b/src/renderer/src/pages/home/components/Assistants.tsx
index 5045a558..74254894 100644
--- a/src/renderer/src/pages/home/components/Assistants.tsx
+++ b/src/renderer/src/pages/home/components/Assistants.tsx
@@ -8,6 +8,7 @@ import { droppableReorder, uuid } from '@renderer/utils'
import { Dropdown, MenuProps } from 'antd'
import { last } from 'lodash'
import { FC, useRef } from 'react'
+import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface Props {
@@ -20,6 +21,8 @@ const Assistants: FC = ({ activeAssistant, setActiveAssistant, onCreateAs
const { assistants, removeAssistant, updateAssistant, addAssistant, updateAssistants } = useAssistants()
const targetAssistant = useRef(null)
+ const { t } = useTranslation()
+
const onDelete = (assistant: Assistant) => {
removeAssistant(assistant.id)
setTimeout(() => {
@@ -30,7 +33,7 @@ const Assistants: FC = ({ activeAssistant, setActiveAssistant, onCreateAs
const items: MenuProps['items'] = [
{
- label: 'Edit',
+ label: t('common.edit'),
key: 'edit',
icon: ,
async onClick() {
@@ -41,7 +44,7 @@ const Assistants: FC = ({ activeAssistant, setActiveAssistant, onCreateAs
}
},
{
- label: 'Duplicate',
+ label: t('common.duplicate'),
key: 'duplicate',
icon: ,
async onClick() {
@@ -52,7 +55,7 @@ const Assistants: FC = ({ activeAssistant, setActiveAssistant, onCreateAs
},
{ type: 'divider' },
{
- label: 'Delete',
+ label: t('common.delete'),
key: 'delete',
icon: ,
danger: true,
diff --git a/src/renderer/src/pages/home/components/ChatSettings.tsx b/src/renderer/src/pages/home/components/ChatSettings.tsx
deleted file mode 100644
index 0d9f5a02..00000000
--- a/src/renderer/src/pages/home/components/ChatSettings.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { FC } from 'react'
-import styled from 'styled-components'
-
-const ChatSettings: FC = () => {
- return (
-
- Chat Settings
-
- )
-}
-
-const Container = styled.div`
- width: 300px;
-`
-
-export default ChatSettings
diff --git a/src/renderer/src/pages/home/components/CodeBlock.tsx b/src/renderer/src/pages/home/components/CodeBlock.tsx
index b8900a34..f350581d 100644
--- a/src/renderer/src/pages/home/components/CodeBlock.tsx
+++ b/src/renderer/src/pages/home/components/CodeBlock.tsx
@@ -3,6 +3,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import styled from 'styled-components'
import { CopyOutlined } from '@ant-design/icons'
+import { useTranslation } from 'react-i18next'
interface CodeBlockProps {
children: string
@@ -13,9 +14,11 @@ interface CodeBlockProps {
const CodeBlock: React.FC = ({ children, className, ...rest }) => {
const match = /language-(\w+)/.exec(className || '')
+ const { t } = useTranslation()
+
const onCopy = () => {
navigator.clipboard.writeText(children)
- window.message.success({ content: 'Copied!', key: 'copy-code' })
+ window.message.success({ content: t('message.copied'), key: 'copy-code' })
}
return match ? (
diff --git a/src/renderer/src/pages/home/components/Inputbar.tsx b/src/renderer/src/pages/home/components/Inputbar.tsx
index aae564ed..59e40e48 100644
--- a/src/renderer/src/pages/home/components/Inputbar.tsx
+++ b/src/renderer/src/pages/home/components/Inputbar.tsx
@@ -20,6 +20,8 @@ import SendMessageSetting from './SendMessageSetting'
import { useSettings } from '@renderer/hooks/useSettings'
import dayjs from 'dayjs'
import { useAppSelector } from '@renderer/store'
+import { getDefaultTopic } from '@renderer/services/assistant'
+import { useTranslation } from 'react-i18next'
interface Props {
assistant: Assistant
@@ -35,6 +37,8 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => {
const generating = useAppSelector((state) => state.runtime.generating)
const inputRef = useRef(null)
+ const { t } = useTranslation()
+
const sendMessage = () => {
if (generating) {
return
@@ -75,11 +79,7 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => {
}
const addNewTopic = useCallback(() => {
- const topic: Topic = {
- id: uuid(),
- name: 'Default Topic',
- messages: []
- }
+ const topic: Topic = getDefaultTopic()
addTopic(topic)
setActiveTopic(topic)
}, [addTopic, setActiveTopic])
@@ -116,21 +116,21 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => {
-
+
-
+
-
+
= ({ assistant, setActiveTopic }) => {
-
+
setExpend(!expended)}>
{expended ? : }
@@ -158,12 +158,11 @@ const Inputbar: FC = ({ assistant, setActiveTopic }) => {
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handleKeyDown}
- placeholder="Type your message here..."
+ placeholder={t('assistant.input.placeholder')}
autoFocus
contextMenu="true"
variant="borderless"
styles={{ textarea: { paddingLeft: 0 } }}
- allowClear
ref={inputRef}
/>
diff --git a/src/renderer/src/pages/home/components/Message.tsx b/src/renderer/src/pages/home/components/Message.tsx
index 4160deb6..2e071d44 100644
--- a/src/renderer/src/pages/home/components/Message.tsx
+++ b/src/renderer/src/pages/home/components/Message.tsx
@@ -11,6 +11,7 @@ import { getModelLogo } from '@renderer/services/provider'
import Logo from '@renderer/assets/images/logo.png'
import { SyncOutlined } from '@ant-design/icons'
import { firstLetter } from '@renderer/utils'
+import { useTranslation } from 'react-i18next'
interface Props {
message: Message
@@ -22,21 +23,22 @@ interface Props {
const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) => {
const avatar = useAvatar()
+ const { t } = useTranslation()
const isLastMessage = index === 0
const canRegenerate = isLastMessage && message.role === 'assistant'
const onCopy = () => {
navigator.clipboard.writeText(message.content)
- window.message.success({ content: 'Copied!', key: 'copy-message' })
+ window.message.success({ content: t('message.copied'), key: 'copy-message' })
}
const onDelete = async () => {
const confirmed = await window.modal.confirm({
icon: null,
- title: 'Delete Message',
- content: 'Are you sure you want to delete this message?',
- okText: 'Delete',
+ title: t('message.message.delete.title'),
+ content: t('message.message.delete.content'),
+ okText: t('common.delete'),
okType: 'danger'
})
confirmed && onDeleteMessage?.(message)
@@ -80,14 +82,14 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) =
)}
-
+
-
+
{canRegenerate && (
-
+
)}
diff --git a/src/renderer/src/pages/home/components/Messages.tsx b/src/renderer/src/pages/home/components/Messages.tsx
index 81c7e76f..58bf35cf 100644
--- a/src/renderer/src/pages/home/components/Messages.tsx
+++ b/src/renderer/src/pages/home/components/Messages.tsx
@@ -7,10 +7,10 @@ import MessageItem from './Message'
import { reverse } from 'lodash'
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
import { useAssistant } from '@renderer/hooks/useAssistant'
-import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant'
import { runAsyncFunction } from '@renderer/utils'
import LocalStorage from '@renderer/services/storage'
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
+import { t } from 'i18next'
interface Props {
assistant: Assistant
@@ -47,7 +47,7 @@ const Messages: FC = ({ assistant, topic }) => {
)
const autoRenameTopic = useCallback(async () => {
- if (topic.name === DEFAULT_TOPIC_NAME && messages.length >= 2) {
+ if (topic.name === t('assistant.default.topic.name') && messages.length >= 2) {
const summaryText = await fetchMessagesSummary({ messages, assistant })
summaryText && updateTopic({ ...topic, name: summaryText })
}
diff --git a/src/renderer/src/pages/home/components/Navigation.tsx b/src/renderer/src/pages/home/components/Navigation.tsx
index 00914bce..42c5166c 100644
--- a/src/renderer/src/pages/home/components/Navigation.tsx
+++ b/src/renderer/src/pages/home/components/Navigation.tsx
@@ -5,6 +5,7 @@ import { useProviders } from '@renderer/hooks/useProvider'
import { Assistant } from '@renderer/types'
import { Button, Dropdown, MenuProps } from 'antd'
import { FC } from 'react'
+import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface Props {
@@ -14,12 +15,13 @@ interface Props {
const Navigation: FC = ({ activeAssistant }) => {
const { providers } = useProviders()
const { model, setModel } = useAssistant(activeAssistant.id)
+ const { t } = useTranslation()
const items: MenuProps['items'] = providers
.filter((p) => p.models.length > 0)
.map((p) => ({
key: p.id,
- label: p.name,
+ label: t(`provider.${p.id}`),
type: 'group',
children: p.models.map((m) => ({
key: m.id,
@@ -34,7 +36,7 @@ const Navigation: FC = ({ activeAssistant }) => {
{activeAssistant?.name}
diff --git a/src/renderer/src/pages/home/components/SendMessageSetting.tsx b/src/renderer/src/pages/home/components/SendMessageSetting.tsx
index 53033cb4..bd4f67f6 100644
--- a/src/renderer/src/pages/home/components/SendMessageSetting.tsx
+++ b/src/renderer/src/pages/home/components/SendMessageSetting.tsx
@@ -2,21 +2,23 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { Dropdown, MenuProps } from 'antd'
import { FC, PropsWithChildren } from 'react'
import { ArrowUpOutlined, EnterOutlined } from '@ant-design/icons'
+import { useTranslation } from 'react-i18next'
interface Props extends PropsWithChildren {}
const SendMessageSetting: FC = ({ children }) => {
const { sendMessageShortcut, setSendMessageShortcut } = useSettings()
+ const { t } = useTranslation()
const sendSettingItems: MenuProps['items'] = [
{
- label: 'Enter Send',
+ label: `Enter ${t('assistant.input.send')}`,
key: 'Enter',
icon: ,
onClick: () => setSendMessageShortcut('Enter')
},
{
- label: 'Shift + Enter Send',
+ label: `Shift+Enter ${t('assistant.input.send')}`,
key: 'Shift+Enter',
icon: ,
onClick: () => setSendMessageShortcut('Shift+Enter')
diff --git a/src/renderer/src/pages/home/components/Topics.tsx b/src/renderer/src/pages/home/components/Topics.tsx
index 63a309c2..b2cb5e38 100644
--- a/src/renderer/src/pages/home/components/Topics.tsx
+++ b/src/renderer/src/pages/home/components/Topics.tsx
@@ -10,6 +10,7 @@ import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/ico
import LocalStorage from '@renderer/services/storage'
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import { droppableReorder } from '@renderer/utils'
+import { useTranslation } from 'react-i18next'
interface Props {
assistant: Assistant
@@ -21,10 +22,11 @@ const Topics: FC = ({ assistant, activeTopic, setActiveTopic }) => {
const { showRightSidebar } = useShowRightSidebar()
const { removeTopic, updateTopic, removeAllTopics, updateTopics } = useAssistant(assistant.id)
const currentTopic = useRef(null)
+ const { t } = useTranslation()
const topicMenuItems: MenuProps['items'] = [
{
- label: 'Auto Rename',
+ label: t('assistant.topics.auto_rename'),
key: 'auto-rename',
icon: ,
async onClick() {
@@ -40,13 +42,13 @@ const Topics: FC = ({ assistant, activeTopic, setActiveTopic }) => {
}
},
{
- label: 'Rename',
+ label: t('common.rename'),
key: 'rename',
icon: ,
async onClick() {
const name = await PromptPopup.show({
- title: 'Rename Topic',
- message: 'Please enter the new name',
+ title: t('assistant.topics.edit.title'),
+ message: t('assistant.topics.edit.placeholder'),
defaultValue: currentTopic.current?.name || ''
})
if (name && currentTopic.current && currentTopic.current?.name !== name) {
@@ -59,7 +61,7 @@ const Topics: FC = ({ assistant, activeTopic, setActiveTopic }) => {
if (assistant.topics.length > 1) {
topicMenuItems.push({ type: 'divider' })
topicMenuItems.push({
- label: 'Delete',
+ label: t('common.delete'),
danger: true,
key: 'delete',
icon: ,
@@ -87,11 +89,13 @@ const Topics: FC = ({ assistant, activeTopic, setActiveTopic }) => {
return (
- Topics ({assistant.topics.length})
+
+ {t('assistant.topics.title')} ({assistant.topics.length})
+
{
const [version, setVersion] = useState('')
+ const { t } = useTranslation()
useEffect(() => {
runAsyncFunction(async () => {
@@ -20,7 +22,7 @@ const AboutSettings: FC = () => {
Cherry Studio (v{version})
- A powerful AI assistant for producer.
+ {t('settings.about.description')}
)
}
diff --git a/src/renderer/src/pages/settings/AssistantSettings.tsx b/src/renderer/src/pages/settings/AssistantSettings.tsx
index 5db8e1c9..454f3319 100644
--- a/src/renderer/src/pages/settings/AssistantSettings.tsx
+++ b/src/renderer/src/pages/settings/AssistantSettings.tsx
@@ -3,31 +3,34 @@ import { SettingContainer, SettingDivider, SettingSubtitle, SettingTitle } from
import { Input } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
+import { useTranslation } from 'react-i18next'
const AssistantSettings: FC = () => {
const { defaultAssistant, updateDefaultAssistant } = useDefaultAssistant()
+ const { t } = useTranslation()
+
return (
- Default Assistant
+ {t('settings.assistant.title')}
- Name
+ {t('common.name')}
updateDefaultAssistant({ ...defaultAssistant, name: e.target.value })}
/>
- Description
+ {t('common.description')}