feat: add translate selection (#1010)

* feat: add translate selection

* chore: add default translate value

* feat: optimize trigger translation shotcut and add TanslateLanguageVarious

* fix

* fix: add database migrate version
This commit is contained in:
Chen Tao 2025-02-10 13:19:46 +08:00 committed by GitHub
parent f3940159b3
commit 3d8748a61a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 85 additions and 15 deletions

View File

@ -1,5 +1,6 @@
import { LoadingOutlined, TranslationOutlined } from '@ant-design/icons' import { LoadingOutlined, TranslationOutlined } from '@ant-design/icons'
import { useDefaultModel } from '@renderer/hooks/useAssistant' import { useDefaultModel } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { fetchTranslate } from '@renderer/services/ApiService' import { fetchTranslate } from '@renderer/services/ApiService'
import { getDefaultTopic, getDefaultTranslateAssistant } from '@renderer/services/AssistantService' import { getDefaultTopic, getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
import { getUserMessage } from '@renderer/services/MessagesService' import { getUserMessage } from '@renderer/services/MessagesService'
@ -20,6 +21,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
const { t } = useTranslation() const { t } = useTranslation()
const { translateModel } = useDefaultModel() const { translateModel } = useDefaultModel()
const [isTranslating, setIsTranslating] = useState(false) const [isTranslating, setIsTranslating] = useState(false)
const { targetLanguage } = useSettings()
const translateConfirm = () => { const translateConfirm = () => {
return window?.modal?.confirm({ return window?.modal?.confirm({
@ -49,7 +51,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
setIsTranslating(true) setIsTranslating(true)
try { try {
const assistant = getDefaultTranslateAssistant('english', text) const assistant = getDefaultTranslateAssistant(targetLanguage, text)
const message = getUserMessage({ const message = getUserMessage({
assistant, assistant,
topic: getDefaultTopic('default'), topic: getDefaultTopic('default'),
@ -75,7 +77,10 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
}, [isLoading]) }, [isLoading])
return ( return (
<Tooltip placement="top" title={t('chat.input.translate')} arrow> <Tooltip
placement="top"
title={t('chat.input.translate', { target_language: t(`languages.${targetLanguage.toString()}`) })}
arrow>
<ToolbarButton onClick={handleTranslate} disabled={disabled || isTranslating} style={style} type="text"> <ToolbarButton onClick={handleTranslate} disabled={disabled || isTranslating} style={style} type="text">
{isTranslating ? <LoadingOutlined spin /> : <TranslationOutlined />} {isTranslating ? <LoadingOutlined spin /> : <TranslationOutlined />}
</ToolbarButton> </ToolbarButton>

View File

@ -3,13 +3,14 @@ import {
SendMessageShortcut, SendMessageShortcut,
setSendMessageShortcut as _setSendMessageShortcut, setSendMessageShortcut as _setSendMessageShortcut,
setSidebarIcons, setSidebarIcons,
setTargetLanguage,
setTheme, setTheme,
SettingsState, SettingsState,
setTopicPosition, setTopicPosition,
setTray, setTray,
setWindowStyle setWindowStyle
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { SidebarIcon, ThemeMode } from '@renderer/types' import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
export function useSettings() { export function useSettings() {
const settings = useAppSelector((state) => state.settings) const settings = useAppSelector((state) => state.settings)
@ -30,6 +31,9 @@ export function useSettings() {
setWindowStyle(windowStyle: 'transparent' | 'opaque') { setWindowStyle(windowStyle: 'transparent' | 'opaque') {
dispatch(setWindowStyle(windowStyle)) dispatch(setWindowStyle(windowStyle))
}, },
setTargetLanguage(targetLanguage: TranslateLanguageVarious) {
dispatch(setTargetLanguage(targetLanguage))
},
setTopicPosition(topicPosition: 'left' | 'right') { setTopicPosition(topicPosition: 'left' | 'right') {
dispatch(setTopicPosition(topicPosition)) dispatch(setTopicPosition(topicPosition))
}, },

View File

@ -86,7 +86,7 @@
"input.send": "Send", "input.send": "Send",
"input.settings": "Settings", "input.settings": "Settings",
"input.topics": " Topics ", "input.topics": " Topics ",
"input.translate": "Translate to English", "input.translate": "Translate to {{target_language}}",
"input.upload": "Upload image or document file", "input.upload": "Upload image or document file",
"input.web_search": "Enable web search", "input.web_search": "Enable web search",
"input.knowledge_base": "Knowledge Base", "input.knowledge_base": "Knowledge Base",
@ -462,6 +462,12 @@
"display.custom.css": "Custom CSS", "display.custom.css": "Custom CSS",
"display.custom.css.placeholder": "/* Put custom CSS here */", "display.custom.css.placeholder": "/* Put custom CSS here */",
"input.auto_translate_with_space": "Quickly translate with 3 spaces", "input.auto_translate_with_space": "Quickly translate with 3 spaces",
"input.target_language": "Target language",
"input.target_language.chinese": "Simplified Chinese",
"input.target_language.chinese-traditional": "Traditional Chinese",
"input.target_language.english": "English",
"input.target_language.japanese": "Japanese",
"input.target_language.russian": "Russian",
"messages.divider": "Show divider between messages", "messages.divider": "Show divider between messages",
"messages.input.paste_long_text_as_file": "Paste long text as file", "messages.input.paste_long_text_as_file": "Paste long text as file",
"messages.input.send_shortcuts": "Send shortcuts", "messages.input.send_shortcuts": "Send shortcuts",

View File

@ -81,7 +81,7 @@
"input.send": "送信", "input.send": "送信",
"input.settings": "設定", "input.settings": "設定",
"input.topics": " トピック ", "input.topics": " トピック ",
"input.translate": "英語に翻訳", "input.translate": "{{target_language}}に翻訳",
"input.upload": "画像またはドキュメントをアップロード", "input.upload": "画像またはドキュメントをアップロード",
"input.web_search": "ウェブ検索を有効にする", "input.web_search": "ウェブ検索を有効にする",
"input.knowledge_base": "ナレッジベース", "input.knowledge_base": "ナレッジベース",
@ -453,6 +453,12 @@
"display.minApp.disabled": "非表示ミニプログラム", "display.minApp.disabled": "非表示ミニプログラム",
"display.minApp.empty": "非表示にしたいアプレットを左からここまでドラッグします", "display.minApp.empty": "非表示にしたいアプレットを左からここまでドラッグします",
"input.auto_translate_with_space": "スペースを3回押して翻訳", "input.auto_translate_with_space": "スペースを3回押して翻訳",
"input.target_language": "目標言語",
"input.target_language.chinese": "簡体字中国語",
"input.target_language.chinese-traditional": "繁体字中国語",
"input.target_language.english": "英語",
"input.target_language.japanese": "日本語",
"input.target_language.russian": "ロシア語",
"messages.divider": "メッセージ間に区切り線を表示", "messages.divider": "メッセージ間に区切り線を表示",
"messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け", "messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け",
"messages.input.send_shortcuts": "送信ショートカット", "messages.input.send_shortcuts": "送信ショートカット",

View File

@ -81,7 +81,7 @@
"input.send": "Отправить", "input.send": "Отправить",
"input.settings": "Настройки", "input.settings": "Настройки",
"input.topics": " Топики ", "input.topics": " Топики ",
"input.translate": "Перевести на английский", "input.translate": "Перевести на {{target_language}}",
"input.upload": "Загрузить изображение или документ", "input.upload": "Загрузить изображение или документ",
"input.web_search": "Включить веб-поиск", "input.web_search": "Включить веб-поиск",
"input.knowledge_base": "База знаний", "input.knowledge_base": "База знаний",
@ -454,6 +454,12 @@
"display.custom.css": "Пользовательский CSS", "display.custom.css": "Пользовательский CSS",
"display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */", "display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */",
"input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов", "input.auto_translate_with_space": "Быстрый перевод с помощью 3-х пробелов",
"input.target_language": "Целевой язык",
"input.target_language.chinese": "Китайский упрощенный",
"input.target_language.chinese-traditional": "Китайский традиционный",
"input.target_language.english": "Английский",
"input.target_language.japanese": "Японский",
"input.target_language.russianinput.translate": "Русский",
"messages.divider": "Показывать разделитель между сообщениями", "messages.divider": "Показывать разделитель между сообщениями",
"messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл", "messages.input.paste_long_text_as_file": "Вставлять длинный текст как файл",
"messages.input.send_shortcuts": "Горячие клавиши для отправки", "messages.input.send_shortcuts": "Горячие клавиши для отправки",

View File

@ -86,7 +86,7 @@
"input.send": "发送", "input.send": "发送",
"input.settings": "设置", "input.settings": "设置",
"input.topics": " 话题 ", "input.topics": " 话题 ",
"input.translate": "翻译成英文", "input.translate": "翻译成{{target_language}}",
"input.upload": "上传图片或文档", "input.upload": "上传图片或文档",
"input.web_search": "开启网络搜索", "input.web_search": "开启网络搜索",
"input.knowledge_base": "知识库", "input.knowledge_base": "知识库",
@ -460,6 +460,12 @@
"display.custom.css": "自定义 CSS", "display.custom.css": "自定义 CSS",
"display.custom.css.placeholder": "/* 这里写自定义CSS */", "display.custom.css.placeholder": "/* 这里写自定义CSS */",
"input.auto_translate_with_space": "快速敲击3次空格翻译", "input.auto_translate_with_space": "快速敲击3次空格翻译",
"input.target_language": "目标语言",
"input.target_language.chinese": "简体中文",
"input.target_language.chinese-traditional": "繁体中文",
"input.target_language.english": "英文",
"input.target_language.japanese": "日文",
"input.target_language.russian": "俄文",
"messages.divider": "消息分割线", "messages.divider": "消息分割线",
"messages.input.paste_long_text_as_file": "长文本粘贴为文件", "messages.input.paste_long_text_as_file": "长文本粘贴为文件",
"messages.input.send_shortcuts": "发送快捷键", "messages.input.send_shortcuts": "发送快捷键",

View File

@ -86,7 +86,7 @@
"input.send": "發送", "input.send": "發送",
"input.settings": "設定", "input.settings": "設定",
"input.topics": " 話題 ", "input.topics": " 話題 ",
"input.translate": "翻譯成英文", "input.translate": "翻譯成{{target_language}}",
"input.upload": "上傳圖片或文檔", "input.upload": "上傳圖片或文檔",
"input.web_search": "開啟網路搜索", "input.web_search": "開啟網路搜索",
"input.knowledge_base": "知識庫", "input.knowledge_base": "知識庫",
@ -459,6 +459,12 @@
"display.custom.css": "自定義 CSS", "display.custom.css": "自定義 CSS",
"display.custom.css.placeholder": "/* 這裡寫自定義 CSS */", "display.custom.css.placeholder": "/* 這裡寫自定義 CSS */",
"input.auto_translate_with_space": "快速敲擊3次空格翻譯", "input.auto_translate_with_space": "快速敲擊3次空格翻譯",
"input.target_language": "目標語言",
"input.target_language.chinese": "簡體中文",
"input.target_language.chinese-traditional": "繁體中文",
"input.target_language.english": "英文",
"input.target_language.japanese": "日文",
"input.target_language.russian": "俄文",
"messages.divider": "訊息間顯示分隔線", "messages.divider": "訊息間顯示分隔線",
"messages.input.paste_long_text_as_file": "將長文本貼上為檔案", "messages.input.paste_long_text_as_file": "將長文本貼上為檔案",
"messages.input.send_shortcuts": "發送快捷鍵", "messages.input.send_shortcuts": "發送快捷鍵",

View File

@ -58,13 +58,13 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const [inputFocus, setInputFocus] = useState(false) const [inputFocus, setInputFocus] = useState(false)
const { assistant, addTopic, model, setModel, updateAssistant } = useAssistant(_assistant.id) const { assistant, addTopic, model, setModel, updateAssistant } = useAssistant(_assistant.id)
const { const {
targetLanguage,
sendMessageShortcut, sendMessageShortcut,
fontSize, fontSize,
pasteLongTextAsFile, pasteLongTextAsFile,
pasteLongTextThreshold, pasteLongTextThreshold,
showInputEstimatedTokens, showInputEstimatedTokens,
clickAssistantToShowTopic, clickAssistantToShowTopic,
language,
autoTranslateWithSpace, autoTranslateWithSpace,
sidebarIcons sidebarIcons
} = useSettings() } = useSettings()
@ -152,7 +152,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
try { try {
setIsTranslating(true) setIsTranslating(true)
const translatedText = await translateText(text, 'english') const translatedText = await translateText(text, targetLanguage)
translatedText && setText(translatedText) translatedText && setText(translatedText)
setTimeout(() => resizeTextArea(), 0) setTimeout(() => resizeTextArea(), 0)
} catch (error) { } catch (error) {
@ -539,9 +539,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
/> />
</ToolbarMenu> </ToolbarMenu>
<ToolbarMenu> <ToolbarMenu>
{!language.startsWith('en') && ( <TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
<TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
)}
{generating && ( {generating && (
<Tooltip placement="top" title={t('chat.input.pause')} arrow> <Tooltip placement="top" title={t('chat.input.pause')} arrow>
<ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}> <ToolbarButton type="text" onClick={onPause} style={{ marginRight: -2, marginTop: 1 }}>

View File

@ -29,7 +29,7 @@ import {
setShowInputEstimatedTokens, setShowInputEstimatedTokens,
setShowMessageDivider setShowMessageDivider
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { Assistant, AssistantSettings, ThemeMode } from '@renderer/types' import { Assistant, AssistantSettings, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd' import { Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -59,6 +59,8 @@ const SettingsTab: FC<Props> = (props) => {
showInputEstimatedTokens, showInputEstimatedTokens,
sendMessageShortcut, sendMessageShortcut,
setSendMessageShortcut, setSendMessageShortcut,
targetLanguage,
setTargetLanguage,
pasteLongTextAsFile, pasteLongTextAsFile,
renderInputMessageAsMarkdown, renderInputMessageAsMarkdown,
codeShowLineNumbers, codeShowLineNumbers,
@ -378,6 +380,25 @@ const SettingsTab: FC<Props> = (props) => {
<SettingDivider /> <SettingDivider />
</> </>
)} )}
<SettingRow>
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
<Select
defaultValue={'english' as TranslateLanguageVarious}
size="small"
value={targetLanguage}
menuItemSelectedIcon={<CheckOutlined />}
options={[
{ value: 'chinese', label: t('settings.input.target_language.chinese') },
{ value: 'chinese-traditional', label: t('settings.input.target_language.chinese-traditional') },
{ value: 'english', label: t('settings.input.target_language.english') },
{ value: 'japanese', label: t('settings.input.target_language.japanese') },
{ value: 'russian', label: t('settings.input.target_language.russian') }
]}
onChange={(value) => setTargetLanguage(value)}
style={{ width: 135 }}
/>
</SettingRow>
<SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitleSmall>{t('settings.messages.input.send_shortcuts')}</SettingRowTitleSmall> <SettingRowTitleSmall>{t('settings.messages.input.send_shortcuts')}</SettingRowTitleSmall>
<Select <Select

View File

@ -921,6 +921,10 @@ const migrateConfig = {
enabled: false enabled: false
}) })
return state return state
},
'63': (state: RootState) => {
state.settings.targetLanguage = 'english'
return state
} }
} }

View File

@ -1,6 +1,6 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
import { CodeStyleVarious, LanguageVarious, ThemeMode } from '@renderer/types' import { CodeStyleVarious, LanguageVarious, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter' export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter'
@ -21,6 +21,7 @@ export interface SettingsState {
showTopics: boolean showTopics: boolean
sendMessageShortcut: SendMessageShortcut sendMessageShortcut: SendMessageShortcut
language: LanguageVarious language: LanguageVarious
targetLanguage: TranslateLanguageVarious
proxyMode: 'system' | 'custom' | 'none' proxyMode: 'system' | 'custom' | 'none'
proxyUrl?: string proxyUrl?: string
userName: string userName: string
@ -73,6 +74,7 @@ const initialState: SettingsState = {
showTopics: true, showTopics: true,
sendMessageShortcut: 'Enter', sendMessageShortcut: 'Enter',
language: navigator.language as LanguageVarious, language: navigator.language as LanguageVarious,
targetLanguage: 'english' as TranslateLanguageVarious,
proxyMode: 'system', proxyMode: 'system',
proxyUrl: undefined, proxyUrl: undefined,
userName: '', userName: '',
@ -139,6 +141,9 @@ const settingsSlice = createSlice({
state.language = action.payload state.language = action.payload
window.electron.ipcRenderer.send('miniwindow-reload') window.electron.ipcRenderer.send('miniwindow-reload')
}, },
setTargetLanguage: (state, action: PayloadAction<TranslateLanguageVarious>) => {
state.targetLanguage = action.payload
},
setProxyMode: (state, action: PayloadAction<'system' | 'custom' | 'none'>) => { setProxyMode: (state, action: PayloadAction<'system' | 'custom' | 'none'>) => {
state.proxyMode = action.payload state.proxyMode = action.payload
}, },
@ -269,6 +274,7 @@ export const {
toggleShowTopics, toggleShowTopics,
setSendMessageShortcut, setSendMessageShortcut,
setLanguage, setLanguage,
setTargetLanguage,
setProxyMode, setProxyMode,
setProxyUrl, setProxyUrl,
setUserName, setUserName,

View File

@ -180,6 +180,8 @@ export enum ThemeMode {
export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'ja-JP' export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'en-US' | 'ru-RU' | 'ja-JP'
export type TranslateLanguageVarious = 'chinese' | 'chinese-traditional' | 'english' | 'japanese' | 'russian'
export type CodeStyleVarious = BuiltinTheme | 'auto' export type CodeStyleVarious = BuiltinTheme | 'auto'
export type WebDavConfig = { export type WebDavConfig = {