mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 22:52:08 +08:00
refactor(translate): Language Type (#7727)
* refactor(translate): 重构翻译功能使用语言枚举类型 统一翻译功能中的语言表示方式,使用枚举类型替代字符串 更新相关组件和服务以适配新的语言类型定义 添加数据库迁移脚本处理语言类型变更 添加store迁移处理语言类型变更 * refactor(translate): 移除调试用的console.log语句 * refactor(translate): 移除冗余的类型检查逻辑 * fix(db): 添加对TranslateHistory的db迁移 * fix(databases): 捕获数据库升级时的语言映射错误 添加错误处理以防止语言映射失败时中断升级过程 * fix(翻译组件): 修复语言比较和选择逻辑错误 修复语言比较时直接比较对象而非langCode的问题 更新Select组件使用langCode作为值并正确处理语言切换 * refactor(translate): 将saveTranslateHistory参数类型从Language改为LanguageCode * refactor(hooks): 更新useMessageOperations中的语言代码类型 将targetLanguage和sourceLanguage参数类型从string更新为LanguageCode,提高类型安全性 * docs(translate): 更新JSDoc注释以使用TypeScript类型语法 * feat(备份服务): 升级数据库版本至v8并添加迁移逻辑 添加从v7到v8的数据库迁移支持 更新翻译历史记录中的语言代码映射 优化迁移过程中的日志记录和错误处理 * fix(store): 修复目标语言迁移时的默认值处理 确保在迁移配置时将旧版语言代码正确映射到新版格式,无法映射时使用默认英语 * refactor(translate): 将语言标签从字符串改为函数以支持动态翻译 * refactor(translate): 优化翻译窗口语言选择逻辑 重构翻译窗口的目标语言选择逻辑,使用语言代码获取完整语言信息 移除冗余的Space组件,简化Select选项渲染方式 * docs(技术文档): 新增数据库设置字段文档 添加数据库设置字段的说明文档,包含翻译相关字段的类型和用途 * refactor(translate): 修改db中biDirectionLangPair存储类型 将语言代码处理统一改为存储langCode而非Language对象 修改相关代码以使用getLanguageByLangcode进行转换 更新数据库升级逻辑以兼容新格式 * docs(translate): 为getLanguageByLangcode函数添加注释说明 * fix(数据库升级): 修复升级到V8时可能出现的空值访问问题 * refactor(databases): 优化语言映射错误处理逻辑 将不必要的try-catch块替换为if条件判断 * docs(technical): 修正数据库设置文档中的类型描述 * refactor: 优化语言代码处理和变量命名 * fix(ActionTranslate): 使用langCode存储双向翻译语言对 * fix(migrate): 修复错误的迁移过程 * refactor(translate): 重构语言选项从硬编码改为动态生成 将translateLanguageOptions从硬编码的数组改为通过LanguagesEnum动态生成,提高可维护性 * fix(store): 更新持久化存储版本并修复语言映射迁移问题 将持久化存储版本从119升级到120,并修复语言代码映射迁移问题。迁移过程中将旧的语言标识转换为新的标准语言代码格式。
This commit is contained in:
parent
278fd931fb
commit
a314a43f0f
11
docs/technical/db.settings.md
Normal file
11
docs/technical/db.settings.md
Normal file
@ -0,0 +1,11 @@
|
||||
# 数据库设置字段
|
||||
|
||||
此文档包含部分字段的数据类型说明。
|
||||
|
||||
## 字段
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------------------------ | ------------------------------ | ------------ |
|
||||
| `translate:target:language` | `LanguageCode` | 翻译目标语言 |
|
||||
| `translate:source:language` | `LanguageCode` | 翻译源语言 |
|
||||
| `translate:bidirectional:pair` | `[LanguageCode, LanguageCode]` | 双向翻译对 |
|
||||
@ -3,6 +3,7 @@ import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||
import { Modal, ModalProps } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { TextAreaProps } from 'antd/lib/input'
|
||||
@ -111,7 +112,7 @@ const PopupContainer: React.FC<Props> = ({
|
||||
}
|
||||
|
||||
try {
|
||||
const assistant = getDefaultTranslateAssistant(targetLanguage, textValue)
|
||||
const assistant = getDefaultTranslateAssistant(getLanguageByLangcode(targetLanguage), textValue)
|
||||
const translatedText = await fetchTranslate({ content: textValue, assistant })
|
||||
if (isMounted.current) {
|
||||
setTextValue(translatedText)
|
||||
|
||||
@ -3,6 +3,7 @@ import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { Languages } from 'lucide-react'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
@ -54,7 +55,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
||||
|
||||
setIsTranslating(true)
|
||||
try {
|
||||
const assistant = getDefaultTranslateAssistant(targetLanguage, text)
|
||||
const assistant = getDefaultTranslateAssistant(getLanguageByLangcode(targetLanguage), text)
|
||||
const translatedText = await fetchTranslate({ content: text, assistant })
|
||||
onTranslated(translatedText)
|
||||
} catch (error) {
|
||||
@ -75,7 +76,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
||||
return (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={t('chat.input.translate', { target_language: t(`languages.${targetLanguage.toString()}`) })}
|
||||
title={t('chat.input.translate', { target_language: getLanguageByLangcode(targetLanguage).label() })}
|
||||
arrow>
|
||||
<ToolbarButton onClick={handleTranslate} disabled={disabled || isTranslating} style={style} type="text">
|
||||
{isTranslating ? <LoadingOutlined spin /> : <Languages size={18} />}
|
||||
|
||||
@ -1,136 +1,159 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import { Language } from '@renderer/types'
|
||||
|
||||
export interface TranslateLanguageOption {
|
||||
value: string
|
||||
langCode?: string
|
||||
label: string
|
||||
emoji: string
|
||||
}
|
||||
|
||||
export const TranslateLanguageOptions: TranslateLanguageOption[] = [
|
||||
{
|
||||
export const ENGLISH: Language = {
|
||||
value: 'English',
|
||||
langCode: 'en-us',
|
||||
label: i18n.t('languages.english'),
|
||||
label: () => i18n.t('languages.english'),
|
||||
emoji: '🇬🇧'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const CHINESE_SIMPLIFIED: Language = {
|
||||
value: 'Chinese (Simplified)',
|
||||
langCode: 'zh-cn',
|
||||
label: i18n.t('languages.chinese'),
|
||||
label: () => i18n.t('languages.chinese'),
|
||||
emoji: '🇨🇳'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const CHINESE_TRADITIONAL: Language = {
|
||||
value: 'Chinese (Traditional)',
|
||||
langCode: 'zh-tw',
|
||||
label: i18n.t('languages.chinese-traditional'),
|
||||
label: () => i18n.t('languages.chinese-traditional'),
|
||||
emoji: '🇭🇰'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const JAPANESE: Language = {
|
||||
value: 'Japanese',
|
||||
langCode: 'ja-jp',
|
||||
label: i18n.t('languages.japanese'),
|
||||
label: () => i18n.t('languages.japanese'),
|
||||
emoji: '🇯🇵'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const KOREAN: Language = {
|
||||
value: 'Korean',
|
||||
langCode: 'ko-kr',
|
||||
label: i18n.t('languages.korean'),
|
||||
label: () => i18n.t('languages.korean'),
|
||||
emoji: '🇰🇷'
|
||||
},
|
||||
}
|
||||
|
||||
{
|
||||
export const FRENCH: Language = {
|
||||
value: 'French',
|
||||
langCode: 'fr-fr',
|
||||
label: i18n.t('languages.french'),
|
||||
label: () => i18n.t('languages.french'),
|
||||
emoji: '🇫🇷'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const GERMAN: Language = {
|
||||
value: 'German',
|
||||
langCode: 'de-de',
|
||||
label: i18n.t('languages.german'),
|
||||
label: () => i18n.t('languages.german'),
|
||||
emoji: '🇩🇪'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const ITALIAN: Language = {
|
||||
value: 'Italian',
|
||||
langCode: 'it-it',
|
||||
label: i18n.t('languages.italian'),
|
||||
label: () => i18n.t('languages.italian'),
|
||||
emoji: '🇮🇹'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const SPANISH: Language = {
|
||||
value: 'Spanish',
|
||||
langCode: 'es-es',
|
||||
label: i18n.t('languages.spanish'),
|
||||
label: () => i18n.t('languages.spanish'),
|
||||
emoji: '🇪🇸'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const PORTUGUESE: Language = {
|
||||
value: 'Portuguese',
|
||||
langCode: 'pt-pt',
|
||||
label: i18n.t('languages.portuguese'),
|
||||
label: () => i18n.t('languages.portuguese'),
|
||||
emoji: '🇵🇹'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const RUSSIAN: Language = {
|
||||
value: 'Russian',
|
||||
langCode: 'ru-ru',
|
||||
label: i18n.t('languages.russian'),
|
||||
label: () => i18n.t('languages.russian'),
|
||||
emoji: '🇷🇺'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const POLISH: Language = {
|
||||
value: 'Polish',
|
||||
langCode: 'pl-pl',
|
||||
label: i18n.t('languages.polish'),
|
||||
label: () => i18n.t('languages.polish'),
|
||||
emoji: '🇵🇱'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const ARABIC: Language = {
|
||||
value: 'Arabic',
|
||||
langCode: 'ar-ar',
|
||||
label: i18n.t('languages.arabic'),
|
||||
label: () => i18n.t('languages.arabic'),
|
||||
emoji: '🇸🇦'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const TURKISH: Language = {
|
||||
value: 'Turkish',
|
||||
langCode: 'tr-tr',
|
||||
label: i18n.t('languages.turkish'),
|
||||
label: () => i18n.t('languages.turkish'),
|
||||
emoji: '🇹🇷'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const THAI: Language = {
|
||||
value: 'Thai',
|
||||
langCode: 'th-th',
|
||||
label: i18n.t('languages.thai'),
|
||||
label: () => i18n.t('languages.thai'),
|
||||
emoji: '🇹🇭'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const VIETNAMESE: Language = {
|
||||
value: 'Vietnamese',
|
||||
langCode: 'vi-vn',
|
||||
label: i18n.t('languages.vietnamese'),
|
||||
label: () => i18n.t('languages.vietnamese'),
|
||||
emoji: '🇻🇳'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const INDONESIAN: Language = {
|
||||
value: 'Indonesian',
|
||||
langCode: 'id-id',
|
||||
label: i18n.t('languages.indonesian'),
|
||||
label: () => i18n.t('languages.indonesian'),
|
||||
emoji: '🇮🇩'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const URDU: Language = {
|
||||
value: 'Urdu',
|
||||
langCode: 'ur-pk',
|
||||
label: i18n.t('languages.urdu'),
|
||||
label: () => i18n.t('languages.urdu'),
|
||||
emoji: '🇵🇰'
|
||||
},
|
||||
{
|
||||
}
|
||||
|
||||
export const MALAY: Language = {
|
||||
value: 'Malay',
|
||||
langCode: 'ms-my',
|
||||
label: i18n.t('languages.malay'),
|
||||
label: () => i18n.t('languages.malay'),
|
||||
emoji: '🇲🇾'
|
||||
}
|
||||
]
|
||||
|
||||
export const translateLanguageOptions = (): typeof TranslateLanguageOptions => {
|
||||
return TranslateLanguageOptions.map((option) => {
|
||||
return {
|
||||
value: option.value,
|
||||
label: option.label,
|
||||
emoji: option.emoji
|
||||
}
|
||||
})
|
||||
}
|
||||
export const LanguagesEnum = {
|
||||
enUS: ENGLISH,
|
||||
zhCN: CHINESE_SIMPLIFIED,
|
||||
zhTW: CHINESE_TRADITIONAL,
|
||||
jaJP: JAPANESE,
|
||||
koKR: KOREAN,
|
||||
frFR: FRENCH,
|
||||
deDE: GERMAN,
|
||||
itIT: ITALIAN,
|
||||
esES: SPANISH,
|
||||
ptPT: PORTUGUESE,
|
||||
ruRU: RUSSIAN,
|
||||
plPL: POLISH,
|
||||
arAR: ARABIC,
|
||||
trTR: TURKISH,
|
||||
thTH: THAI,
|
||||
viVN: VIETNAMESE,
|
||||
idID: INDONESIAN,
|
||||
urPK: URDU,
|
||||
msMY: MALAY
|
||||
} as const
|
||||
|
||||
export const translateLanguageOptions: Language[] = Object.values(LanguagesEnum)
|
||||
|
||||
@ -3,7 +3,7 @@ import { FileMetadata, KnowledgeItem, QuickPhrase, TranslateHistory } from '@ren
|
||||
import type { Message as NewMessage, MessageBlock } from '@renderer/types/newMessage'
|
||||
import { Dexie, type EntityTable } from 'dexie'
|
||||
|
||||
import { upgradeToV5, upgradeToV7 } from './upgrades'
|
||||
import { upgradeToV5, upgradeToV7, upgradeToV8 } from './upgrades'
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
export const db = new Dexie('CherryStudio') as Dexie & {
|
||||
@ -74,4 +74,17 @@ db.version(7)
|
||||
})
|
||||
.upgrade((tx) => upgradeToV7(tx))
|
||||
|
||||
db.version(8)
|
||||
.stores({
|
||||
// Re-declare all tables for the new version
|
||||
files: 'id, name, origin_name, path, size, ext, type, created_at, count',
|
||||
topics: '&id', // Correct index for topics
|
||||
settings: '&id, value',
|
||||
knowledge_notes: '&id, baseId, type, content, created_at, updated_at',
|
||||
translate_history: '&id, sourceText, targetText, sourceLanguage, targetLanguage, createdAt',
|
||||
quick_phrases: 'id',
|
||||
message_blocks: 'id, messageId, file.id' // Correct syntax with comma separator
|
||||
})
|
||||
.upgrade((tx) => upgradeToV8(tx))
|
||||
|
||||
export default db
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Logger from '@renderer/config/logger'
|
||||
import type { LegacyMessage as OldMessage, Topic } from '@renderer/types'
|
||||
import { FileTypes } from '@renderer/types' // Import FileTypes enum
|
||||
import { WebSearchSource } from '@renderer/types'
|
||||
import { LanguagesEnum } from '@renderer/config/translate'
|
||||
import type { LanguageCode, LegacyMessage as OldMessage, Topic } from '@renderer/types'
|
||||
import { FileTypes, WebSearchSource } from '@renderer/types' // Import FileTypes enum
|
||||
import type {
|
||||
BaseMessageBlock,
|
||||
CitationMessageBlock,
|
||||
@ -308,3 +308,78 @@ export async function upgradeToV7(tx: Transaction): Promise<void> {
|
||||
|
||||
Logger.log('DB migration to version 7 finished successfully.')
|
||||
}
|
||||
|
||||
export async function upgradeToV8(tx: Transaction): Promise<void> {
|
||||
Logger.log('DB migration to version 8 started')
|
||||
|
||||
const langMap: Record<string, LanguageCode> = {
|
||||
english: 'en-us',
|
||||
chinese: 'zh-cn',
|
||||
'chinese-traditional': 'zh-tw',
|
||||
japanese: 'ja-jp',
|
||||
korean: 'ko-kr',
|
||||
french: 'fr-fr',
|
||||
german: 'de-de',
|
||||
italian: 'it-it',
|
||||
spanish: 'es-es',
|
||||
portuguese: 'pt-pt',
|
||||
russian: 'ru-ru',
|
||||
polish: 'pl-pl',
|
||||
arabic: 'ar-ar',
|
||||
turkish: 'tr-tr',
|
||||
thai: 'th-th',
|
||||
vietnamese: 'vi-vn',
|
||||
indonesian: 'id-id',
|
||||
urdu: 'ur-pk',
|
||||
malay: 'ms-my'
|
||||
}
|
||||
|
||||
const settingsTable = tx.table('settings')
|
||||
const defaultPair: [LanguageCode, LanguageCode] = [LanguagesEnum.enUS.langCode, LanguagesEnum.zhCN.langCode]
|
||||
const originSource = (await settingsTable.get('translate:source:language'))?.value
|
||||
const originTarget = (await settingsTable.get('translate:target:language'))?.value
|
||||
const originPair = (await settingsTable.get('translate:bidirectional:pair'))?.value
|
||||
let newSource, newTarget, newPair
|
||||
Logger.log('originSource: %o', originSource)
|
||||
if (originSource === 'auto') {
|
||||
newSource = 'auto'
|
||||
} else {
|
||||
newSource = langMap[originSource]
|
||||
if (!newSource) {
|
||||
newSource = LanguagesEnum.enUS.langCode
|
||||
}
|
||||
}
|
||||
|
||||
Logger.log('originTarget: %o', originTarget)
|
||||
newTarget = langMap[originTarget]
|
||||
if (!newTarget) {
|
||||
newTarget = LanguagesEnum.zhCN.langCode
|
||||
}
|
||||
|
||||
Logger.log('originPair: %o', originPair)
|
||||
newPair = [langMap[originPair[0]], langMap[originPair[1]]]
|
||||
if (!newPair[0] || !newPair[1]) {
|
||||
newPair = defaultPair
|
||||
}
|
||||
|
||||
Logger.log('DB migration to version 8: %o', { newSource, newTarget, newPair })
|
||||
|
||||
await settingsTable.put({ id: 'translate:bidirectional:pair', value: newPair })
|
||||
await settingsTable.put({ id: 'translate:source:language', value: newSource })
|
||||
await settingsTable.put({ id: 'translate:target:language', value: newTarget })
|
||||
|
||||
const histories = tx.table('translate_history')
|
||||
|
||||
for (const history of await histories.toArray()) {
|
||||
try {
|
||||
await tx.table('translate_history').put({
|
||||
...history,
|
||||
sourceLanguage: langMap[history.sourceLanguage],
|
||||
targetLanguage: langMap[history.targetLanguage]
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error upgrading history:', error)
|
||||
}
|
||||
}
|
||||
Logger.log('DB migration to version 8 finished.')
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
updateMessageAndBlocksThunk,
|
||||
updateTranslationBlockThunk
|
||||
} from '@renderer/store/thunk/messageThunk'
|
||||
import type { Assistant, Model, Topic } from '@renderer/types'
|
||||
import type { Assistant, LanguageCode, Model, Topic } from '@renderer/types'
|
||||
import type { Message, MessageBlock } from '@renderer/types/newMessage'
|
||||
import { MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { abortCompletion } from '@renderer/utils/abortController'
|
||||
@ -195,9 +195,9 @@ export function useMessageOperations(topic: Topic) {
|
||||
const getTranslationUpdater = useCallback(
|
||||
async (
|
||||
messageId: string,
|
||||
targetLanguage: string,
|
||||
targetLanguage: LanguageCode,
|
||||
sourceBlockId?: string,
|
||||
sourceLanguage?: string
|
||||
sourceLanguage?: LanguageCode
|
||||
): Promise<((accumulatedText: string, isComplete?: boolean) => void) | null> => {
|
||||
if (!topic.id) return null
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ import type { MessageInputBaseParams } from '@renderer/types/newMessage'
|
||||
import { classNames, delay, formatFileSize, getFileExtension } from '@renderer/utils'
|
||||
import { formatQuotedText } from '@renderer/utils/formats'
|
||||
import { getFilesFromDropEvent, getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
||||
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
@ -253,7 +254,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
|
||||
try {
|
||||
setIsTranslating(true)
|
||||
const translatedText = await translateText(text, targetLanguage)
|
||||
const translatedText = await translateText(text, getLanguageByLangcode(targetLanguage))
|
||||
translatedText && setText(translatedText)
|
||||
setTimeout(() => resizeTextArea(), 0)
|
||||
} catch (error) {
|
||||
|
||||
@ -2,7 +2,7 @@ import { CheckOutlined, EditOutlined, QuestionCircleOutlined, SyncOutlined } fro
|
||||
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import { isVisionModel } from '@renderer/config/models'
|
||||
import { TranslateLanguageOptions } from '@renderer/config/translate'
|
||||
import { translateLanguageOptions } from '@renderer/config/translate'
|
||||
import { useMessageEditing } from '@renderer/context/MessageEditingContext'
|
||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
||||
@ -13,7 +13,7 @@ import { translateText } from '@renderer/services/TranslateService'
|
||||
import store, { RootState } from '@renderer/store'
|
||||
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
||||
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||
import type { Assistant, Model, Topic } from '@renderer/types'
|
||||
import type { Assistant, Language, Model, Topic } from '@renderer/types'
|
||||
import { type Message, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, classNames } from '@renderer/utils'
|
||||
import { copyMessageAsPlainText } from '@renderer/utils/copy'
|
||||
@ -153,12 +153,12 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
}, [message.id, startEditing])
|
||||
|
||||
const handleTranslate = useCallback(
|
||||
async (language: string) => {
|
||||
async (language: Language) => {
|
||||
if (isTranslating) return
|
||||
|
||||
setIsTranslating(true)
|
||||
const messageId = message.id
|
||||
const translationUpdater = await getTranslationUpdater(messageId, language)
|
||||
const translationUpdater = await getTranslationUpdater(messageId, language.langCode)
|
||||
if (!translationUpdater) return
|
||||
try {
|
||||
await translateText(mainTextContent, language, translationUpdater)
|
||||
@ -457,10 +457,10 @@ const MessageMenubar: FC<Props> = (props) => {
|
||||
backgroundClip: 'border-box'
|
||||
},
|
||||
items: [
|
||||
...TranslateLanguageOptions.map((item) => ({
|
||||
label: item.emoji + ' ' + item.label,
|
||||
key: item.value,
|
||||
onClick: () => handleTranslate(item.value)
|
||||
...translateLanguageOptions.map((item) => ({
|
||||
label: item.emoji + ' ' + item.label(),
|
||||
key: item.langCode,
|
||||
onClick: () => handleTranslate(item)
|
||||
})),
|
||||
...(hasTranslationBlocks
|
||||
? [
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
isSupportedFlexServiceTier,
|
||||
isSupportedReasoningEffortOpenAIModel
|
||||
} from '@renderer/config/models'
|
||||
import { translateLanguageOptions } from '@renderer/config/translate'
|
||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
@ -44,14 +45,7 @@ import {
|
||||
setShowTranslateConfirm,
|
||||
setThoughtAutoCollapse
|
||||
} from '@renderer/store/settings'
|
||||
import {
|
||||
Assistant,
|
||||
AssistantSettings,
|
||||
CodeStyleVarious,
|
||||
MathEngine,
|
||||
ThemeMode,
|
||||
TranslateLanguageVarious
|
||||
} from '@renderer/types'
|
||||
import { Assistant, AssistantSettings, CodeStyleVarious, MathEngine, ThemeMode } from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
||||
import { Button, Col, InputNumber, Row, Slider, Switch, Tooltip } from 'antd'
|
||||
@ -625,14 +619,10 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
|
||||
<Selector
|
||||
value={targetLanguage}
|
||||
onChange={(value) => setTargetLanguage(value as TranslateLanguageVarious)}
|
||||
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)}
|
||||
options={translateLanguageOptions.map((item) => {
|
||||
return { value: item.langCode, label: item.emoji + ' ' + item.label() }
|
||||
})}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
|
||||
@ -7,6 +7,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { getProviderLogo } from '@renderer/config/providers'
|
||||
import { LanguagesEnum } from '@renderer/config/translate'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||
@ -543,7 +544,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
try {
|
||||
setIsTranslating(true)
|
||||
const translatedText = await translateText(painting.prompt, 'english')
|
||||
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||
updatePaintingState({ prompt: translatedText })
|
||||
} catch (error) {
|
||||
console.error('Translation failed:', error)
|
||||
|
||||
@ -12,6 +12,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { TEXT_TO_IMAGES_MODELS } from '@renderer/config/models'
|
||||
import { LanguagesEnum } from '@renderer/config/translate'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||
@ -302,7 +303,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
try {
|
||||
setIsTranslating(true)
|
||||
const translatedText = await translateText(painting.prompt, 'english')
|
||||
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||
updatePaintingState({ prompt: translatedText })
|
||||
} catch (error) {
|
||||
console.error('Translation failed:', error)
|
||||
|
||||
@ -4,6 +4,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import TranslateButton from '@renderer/components/TranslateButton'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { getProviderLogo } from '@renderer/config/providers'
|
||||
import { LanguagesEnum } from '@renderer/config/translate'
|
||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
@ -255,7 +256,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
|
||||
try {
|
||||
setIsTranslating(true)
|
||||
const translatedText = await translateText(painting.prompt, 'english')
|
||||
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||
updatePaintingState({ prompt: translatedText })
|
||||
} catch (error) {
|
||||
console.error('Translation failed:', error)
|
||||
|
||||
@ -4,7 +4,7 @@ import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import { isEmbeddingModel } from '@renderer/config/models'
|
||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||
import { translateLanguageOptions } from '@renderer/config/translate'
|
||||
import { LanguagesEnum, translateLanguageOptions } from '@renderer/config/translate'
|
||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||
import db from '@renderer/databases'
|
||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
@ -15,13 +15,14 @@ import { getDefaultTranslateAssistant } from '@renderer/services/AssistantServic
|
||||
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setTranslateModelPrompt } from '@renderer/store/settings'
|
||||
import type { Model, TranslateHistory } from '@renderer/types'
|
||||
import type { Language, LanguageCode, Model, TranslateHistory } from '@renderer/types'
|
||||
import { runAsyncFunction, uuid } from '@renderer/utils'
|
||||
import {
|
||||
createInputScrollHandler,
|
||||
createOutputScrollHandler,
|
||||
detectLanguage,
|
||||
determineTargetLanguage
|
||||
determineTargetLanguage,
|
||||
getLanguageByLangcode
|
||||
} from '@renderer/utils/translate'
|
||||
import { Button, Dropdown, Empty, Flex, Modal, Popconfirm, Select, Space, Switch, Tooltip } from 'antd'
|
||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||
@ -35,7 +36,7 @@ import styled from 'styled-components'
|
||||
|
||||
let _text = ''
|
||||
let _result = ''
|
||||
let _targetLanguage = 'english'
|
||||
let _targetLanguage = LanguagesEnum.enUS
|
||||
|
||||
const TranslateSettings: FC<{
|
||||
visible: boolean
|
||||
@ -46,8 +47,8 @@ const TranslateSettings: FC<{
|
||||
setIsBidirectional: (value: boolean) => void
|
||||
enableMarkdown: boolean
|
||||
setEnableMarkdown: (value: boolean) => void
|
||||
bidirectionalPair: [string, string]
|
||||
setBidirectionalPair: (value: [string, string]) => void
|
||||
bidirectionalPair: [Language, Language]
|
||||
setBidirectionalPair: (value: [Language, Language]) => void
|
||||
translateModel: Model | undefined
|
||||
onModelChange: (model: Model) => void
|
||||
allModels: Model[]
|
||||
@ -71,7 +72,7 @@ const TranslateSettings: FC<{
|
||||
const { t } = useTranslation()
|
||||
const { translateModelPrompt } = useSettings()
|
||||
const dispatch = useAppDispatch()
|
||||
const [localPair, setLocalPair] = useState<[string, string]>(bidirectionalPair)
|
||||
const [localPair, setLocalPair] = useState<[Language, Language]>(bidirectionalPair)
|
||||
const [showPrompt, setShowPrompt] = useState(false)
|
||||
const [localPrompt, setLocalPrompt] = useState(translateModelPrompt)
|
||||
|
||||
@ -94,7 +95,7 @@ const TranslateSettings: FC<{
|
||||
return
|
||||
}
|
||||
setBidirectionalPair(localPair)
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: localPair })
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [localPair[0].langCode, localPair[1].langCode] })
|
||||
db.settings.put({ id: 'translate:scroll:sync', value: isScrollSyncEnabled })
|
||||
db.settings.put({ id: 'translate:markdown:enabled', value: enableMarkdown })
|
||||
db.settings.put({ id: 'translate:model:prompt', value: localPrompt })
|
||||
@ -189,16 +190,16 @@ const TranslateSettings: FC<{
|
||||
<Flex align="center" justify="space-between" gap={10}>
|
||||
<Select
|
||||
style={{ flex: 1 }}
|
||||
value={localPair[0]}
|
||||
onChange={(value) => setLocalPair([value, localPair[1]])}
|
||||
options={translateLanguageOptions().map((lang) => ({
|
||||
value: lang.value,
|
||||
value={localPair[0].langCode}
|
||||
onChange={(value) => setLocalPair([getLanguageByLangcode(value), localPair[1]])}
|
||||
options={translateLanguageOptions.map((lang) => ({
|
||||
value: lang.langCode,
|
||||
label: (
|
||||
<Space.Compact direction="horizontal" block>
|
||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||
{lang.emoji}
|
||||
</span>
|
||||
<Space.Compact block>{lang.label}</Space.Compact>
|
||||
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||
</Space.Compact>
|
||||
)
|
||||
}))}
|
||||
@ -206,16 +207,16 @@ const TranslateSettings: FC<{
|
||||
<span>⇆</span>
|
||||
<Select
|
||||
style={{ flex: 1 }}
|
||||
value={localPair[1]}
|
||||
onChange={(value) => setLocalPair([localPair[0], value])}
|
||||
options={translateLanguageOptions().map((lang) => ({
|
||||
value: lang.value,
|
||||
value={localPair[1].langCode}
|
||||
onChange={(value) => setLocalPair([localPair[0], getLanguageByLangcode(value)])}
|
||||
options={translateLanguageOptions.map((lang) => ({
|
||||
value: lang.langCode,
|
||||
label: (
|
||||
<Space.Compact direction="horizontal" block>
|
||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||
{lang.emoji}
|
||||
</span>
|
||||
<div style={{ textAlign: 'left', flex: 1 }}>{lang.label}</div>
|
||||
<div style={{ textAlign: 'left', flex: 1 }}>{lang.label()}</div>
|
||||
</Space.Compact>
|
||||
)
|
||||
}))}
|
||||
@ -275,7 +276,6 @@ const TranslateSettings: FC<{
|
||||
const TranslatePage: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { shikiMarkdownIt } = useCodeStyle()
|
||||
const [targetLanguage, setTargetLanguage] = useState(_targetLanguage)
|
||||
const [text, setText] = useState(_text)
|
||||
const [result, setResult] = useState(_result)
|
||||
const [renderedMarkdown, setRenderedMarkdown] = useState<string>('')
|
||||
@ -286,10 +286,14 @@ const TranslatePage: FC = () => {
|
||||
const [isScrollSyncEnabled, setIsScrollSyncEnabled] = useState(false)
|
||||
const [isBidirectional, setIsBidirectional] = useState(false)
|
||||
const [enableMarkdown, setEnableMarkdown] = useState(false)
|
||||
const [bidirectionalPair, setBidirectionalPair] = useState<[string, string]>(['english', 'chinese'])
|
||||
const [bidirectionalPair, setBidirectionalPair] = useState<[Language, Language]>([
|
||||
LanguagesEnum.enUS,
|
||||
LanguagesEnum.zhCN
|
||||
])
|
||||
const [settingsVisible, setSettingsVisible] = useState(false)
|
||||
const [detectedLanguage, setDetectedLanguage] = useState<string | null>(null)
|
||||
const [sourceLanguage, setSourceLanguage] = useState<string>('auto')
|
||||
const [detectedLanguage, setDetectedLanguage] = useState<Language | null>(null)
|
||||
const [sourceLanguage, setSourceLanguage] = useState<Language | 'auto'>('auto')
|
||||
const [targetLanguage, setTargetLanguage] = useState<Language>(_targetLanguage)
|
||||
const contentContainerRef = useRef<HTMLDivElement>(null)
|
||||
const textAreaRef = useRef<TextAreaRef>(null)
|
||||
const outputTextRef = useRef<HTMLDivElement>(null)
|
||||
@ -329,8 +333,8 @@ const TranslatePage: FC = () => {
|
||||
const saveTranslateHistory = async (
|
||||
sourceText: string,
|
||||
targetText: string,
|
||||
sourceLanguage: string,
|
||||
targetLanguage: string
|
||||
sourceLanguage: LanguageCode,
|
||||
targetLanguage: LanguageCode
|
||||
) => {
|
||||
const history: TranslateHistory = {
|
||||
id: uuid(),
|
||||
@ -364,7 +368,7 @@ const TranslatePage: FC = () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
// 确定源语言:如果用户选择了特定语言,使用用户选择的;如果选择'auto',则自动检测
|
||||
let actualSourceLanguage: string
|
||||
let actualSourceLanguage: Language
|
||||
if (sourceLanguage === 'auto') {
|
||||
actualSourceLanguage = await detectLanguage(text)
|
||||
setDetectedLanguage(actualSourceLanguage)
|
||||
@ -389,7 +393,7 @@ const TranslatePage: FC = () => {
|
||||
return
|
||||
}
|
||||
|
||||
const actualTargetLanguage = result.language as string
|
||||
const actualTargetLanguage = result.language as Language
|
||||
if (isBidirectional) {
|
||||
setTargetLanguage(actualTargetLanguage)
|
||||
}
|
||||
@ -405,7 +409,7 @@ const TranslatePage: FC = () => {
|
||||
}
|
||||
})
|
||||
|
||||
await saveTranslateHistory(text, translatedText, actualSourceLanguage, actualTargetLanguage)
|
||||
await saveTranslateHistory(text, translatedText, actualSourceLanguage.langCode, actualTargetLanguage.langCode)
|
||||
setLoading(false)
|
||||
} catch (error) {
|
||||
console.error('Translation error:', error)
|
||||
@ -432,7 +436,7 @@ const TranslatePage: FC = () => {
|
||||
const onHistoryItemClick = (history: TranslateHistory) => {
|
||||
setText(history.sourceText)
|
||||
setResult(history.targetText)
|
||||
setTargetLanguage(history.targetLanguage)
|
||||
setTargetLanguage(getLanguageByLangcode(history.targetLanguage))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -460,20 +464,32 @@ const TranslatePage: FC = () => {
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
const targetLang = await db.settings.get({ id: 'translate:target:language' })
|
||||
targetLang && setTargetLanguage(targetLang.value)
|
||||
targetLang && setTargetLanguage(getLanguageByLangcode(targetLang.value))
|
||||
|
||||
const sourceLang = await db.settings.get({ id: 'translate:source:language' })
|
||||
sourceLang && setSourceLanguage(sourceLang.value)
|
||||
sourceLang &&
|
||||
setSourceLanguage(sourceLang.value === 'auto' ? sourceLang.value : getLanguageByLangcode(sourceLang.value))
|
||||
|
||||
const bidirectionalPairSetting = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||
if (bidirectionalPairSetting) {
|
||||
const langPair = bidirectionalPairSetting.value
|
||||
let source: undefined | Language
|
||||
let target: undefined | Language
|
||||
|
||||
if (Array.isArray(langPair) && langPair.length === 2 && langPair[0] !== langPair[1]) {
|
||||
setBidirectionalPair(langPair as [string, string])
|
||||
source = getLanguageByLangcode(langPair[0])
|
||||
target = getLanguageByLangcode(langPair[1])
|
||||
}
|
||||
|
||||
if (source && target) {
|
||||
setBidirectionalPair([source, target])
|
||||
} else {
|
||||
const defaultPair: [string, string] = ['english', 'chinese']
|
||||
const defaultPair: [Language, Language] = [LanguagesEnum.enUS, LanguagesEnum.zhCN]
|
||||
setBidirectionalPair(defaultPair)
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: defaultPair })
|
||||
db.settings.put({
|
||||
id: 'translate:bidirectional:pair',
|
||||
value: [defaultPair[0].langCode, defaultPair[1].langCode]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,7 +505,7 @@ const TranslatePage: FC = () => {
|
||||
}, [])
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const isEnterPressed = e.keyCode == 13
|
||||
const isEnterPressed = e.key === 'Enter'
|
||||
if (isEnterPressed && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||
e.preventDefault()
|
||||
onTranslate()
|
||||
@ -501,32 +517,37 @@ const TranslatePage: FC = () => {
|
||||
|
||||
// 获取当前语言状态显示
|
||||
const getLanguageDisplay = () => {
|
||||
try {
|
||||
if (isBidirectional) {
|
||||
return (
|
||||
<Flex align="center" style={{ width: 160 }}>
|
||||
<BidirectionalLanguageDisplay>
|
||||
{`${t(`languages.${bidirectionalPair[0]}`)} ⇆ ${t(`languages.${bidirectionalPair[1]}`)}`}
|
||||
{`${bidirectionalPair[0].label()} ⇆ ${bidirectionalPair[1].label()}`}
|
||||
</BidirectionalLanguageDisplay>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting language display:', error)
|
||||
setBidirectionalPair([LanguagesEnum.enUS, LanguagesEnum.zhCN])
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
style={{ width: 160 }}
|
||||
value={targetLanguage}
|
||||
value={targetLanguage.langCode}
|
||||
onChange={(value) => {
|
||||
setTargetLanguage(value)
|
||||
setTargetLanguage(getLanguageByLangcode(value))
|
||||
db.settings.put({ id: 'translate:target:language', value })
|
||||
}}
|
||||
options={translateLanguageOptions().map((lang) => ({
|
||||
value: lang.value,
|
||||
options={translateLanguageOptions.map((lang) => ({
|
||||
value: lang.langCode,
|
||||
label: (
|
||||
<Space.Compact direction="horizontal" block>
|
||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||
{lang.emoji}
|
||||
</span>
|
||||
<Space.Compact block>{lang.label}</Space.Compact>
|
||||
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||
</Space.Compact>
|
||||
)
|
||||
}))}
|
||||
@ -603,28 +624,29 @@ const TranslatePage: FC = () => {
|
||||
<Flex align="center" gap={20}>
|
||||
<Select
|
||||
showSearch
|
||||
value={sourceLanguage}
|
||||
value={sourceLanguage !== 'auto' ? sourceLanguage.langCode : 'auto'}
|
||||
style={{ width: 180 }}
|
||||
optionFilterProp="label"
|
||||
onChange={(value) => {
|
||||
setSourceLanguage(value)
|
||||
onChange={(value: LanguageCode | 'auto') => {
|
||||
if (value !== 'auto') setSourceLanguage(getLanguageByLangcode(value))
|
||||
else setSourceLanguage('auto')
|
||||
db.settings.put({ id: 'translate:source:language', value })
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
value: 'auto',
|
||||
label: detectedLanguage
|
||||
? `${t('translate.detected.language')} (${t(`languages.${detectedLanguage.toLowerCase()}`)})`
|
||||
? `${t('translate.detected.language')} (${detectedLanguage.label()})`
|
||||
: t('translate.detected.language')
|
||||
},
|
||||
...translateLanguageOptions().map((lang) => ({
|
||||
value: lang.value,
|
||||
...translateLanguageOptions.map((lang) => ({
|
||||
value: lang.langCode,
|
||||
label: (
|
||||
<Space.Compact direction="horizontal" block>
|
||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||
{lang.emoji}
|
||||
</span>
|
||||
<Space.Compact block>{lang.label}</Space.Compact>
|
||||
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||
</Space.Compact>
|
||||
)
|
||||
}))
|
||||
|
||||
@ -2,7 +2,7 @@ import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import { addAssistant } from '@renderer/store/assistants'
|
||||
import type { Agent, Assistant, AssistantSettings, Model, Provider, Topic } from '@renderer/types'
|
||||
import type { Agent, Assistant, AssistantSettings, Language, Model, Provider, Topic } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
|
||||
export function getDefaultAssistant(): Assistant {
|
||||
@ -28,7 +28,7 @@ export function getDefaultAssistant(): Assistant {
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultTranslateAssistant(targetLanguage: string, text: string): Assistant {
|
||||
export function getDefaultTranslateAssistant(targetLanguage: Language, text: string): Assistant {
|
||||
const translateModel = getTranslateModel()
|
||||
const assistant: Assistant = getDefaultAssistant()
|
||||
assistant.model = translateModel
|
||||
@ -39,7 +39,7 @@ export function getDefaultTranslateAssistant(targetLanguage: string, text: strin
|
||||
|
||||
assistant.prompt = store
|
||||
.getState()
|
||||
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage)
|
||||
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value)
|
||||
.replaceAll('{{text}}', text)
|
||||
return assistant
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import Logger from '@renderer/config/logger'
|
||||
import db from '@renderer/databases'
|
||||
import { upgradeToV7 } from '@renderer/databases/upgrades'
|
||||
import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades'
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import { setWebDAVSyncState } from '@renderer/store/backup'
|
||||
@ -637,7 +637,7 @@ export function stopAutoSync() {
|
||||
export async function getBackupData() {
|
||||
return JSON.stringify({
|
||||
time: new Date().getTime(),
|
||||
version: 4,
|
||||
version: 5,
|
||||
localStorage,
|
||||
indexedDB: await backupDatabase()
|
||||
})
|
||||
@ -674,6 +674,12 @@ export async function handleData(data: Record<string, any>) {
|
||||
})
|
||||
}
|
||||
|
||||
if (data.version === 4) {
|
||||
await db.transaction('rw', db.tables, async (tx) => {
|
||||
await upgradeToV8(tx)
|
||||
})
|
||||
}
|
||||
|
||||
window.message.success({ content: i18n.t('message.restore.success'), key: 'restore' })
|
||||
setTimeout(() => window.api.reload(), 1000)
|
||||
return
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import { Language } from '@renderer/types'
|
||||
|
||||
import { fetchTranslate } from './ApiService'
|
||||
import { getDefaultTranslateAssistant } from './AssistantService'
|
||||
|
||||
export const translateText = async (
|
||||
text: string,
|
||||
targetLanguage: string,
|
||||
targetLanguage: Language,
|
||||
onResponse?: (text: string, isComplete: boolean) => void
|
||||
) => {
|
||||
const translateModel = store.getState().llm.translateModel
|
||||
|
||||
@ -54,7 +54,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 119,
|
||||
version: 120,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@ import { SYSTEM_MODELS } from '@renderer/config/models'
|
||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||
import db from '@renderer/databases'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { Assistant, Provider, WebSearchProvider } from '@renderer/types'
|
||||
import { Assistant, LanguageCode, Provider, WebSearchProvider } from '@renderer/types'
|
||||
import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils'
|
||||
import { UpgradeChannel } from '@shared/config/constant'
|
||||
import { isEmpty } from 'lodash'
|
||||
@ -897,6 +897,7 @@ const migrateConfig = {
|
||||
},
|
||||
'65': (state: RootState) => {
|
||||
try {
|
||||
// @ts-ignore expect error
|
||||
state.settings.targetLanguage = 'english'
|
||||
return state
|
||||
} catch (error) {
|
||||
@ -1736,6 +1737,25 @@ const migrateConfig = {
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
},
|
||||
'120': (state: RootState) => {
|
||||
try {
|
||||
const langMap: Record<string, LanguageCode> = {
|
||||
english: 'en-us',
|
||||
chinese: 'zh-cn',
|
||||
'chinese-traditional': 'zh-tw',
|
||||
japanese: 'ja-jp',
|
||||
russian: 'ru-ru'
|
||||
}
|
||||
|
||||
const origin = state.settings.targetLanguage
|
||||
const newLang = langMap[origin]
|
||||
if (newLang) state.settings.targetLanguage = newLang
|
||||
else state.settings.targetLanguage = 'en-us'
|
||||
return state
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -201,7 +201,7 @@ export const initialState: SettingsState = {
|
||||
assistantsTabSortType: 'list',
|
||||
sendMessageShortcut: 'Enter',
|
||||
language: navigator.language as LanguageVarious,
|
||||
targetLanguage: 'english' as TranslateLanguageVarious,
|
||||
targetLanguage: 'en-us',
|
||||
proxyMode: 'system',
|
||||
proxyUrl: undefined,
|
||||
userName: '',
|
||||
|
||||
@ -340,16 +340,7 @@ export enum ThemeMode {
|
||||
|
||||
export type LanguageVarious = 'zh-CN' | 'zh-TW' | 'el-GR' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'pt-PT' | 'ru-RU'
|
||||
|
||||
export type TranslateLanguageVarious =
|
||||
| 'chinese'
|
||||
| 'chinese-traditional'
|
||||
| 'greek'
|
||||
| 'english'
|
||||
| 'spanish'
|
||||
| 'french'
|
||||
| 'japanese'
|
||||
| 'portuguese'
|
||||
| 'russian'
|
||||
export type TranslateLanguageVarious = LanguageCode
|
||||
|
||||
export type CodeStyleVarious = 'auto' | string
|
||||
|
||||
@ -489,12 +480,41 @@ export type GenerateImageResponse = {
|
||||
images: string[]
|
||||
}
|
||||
|
||||
export type LanguageCode =
|
||||
| 'en-us'
|
||||
| 'zh-cn'
|
||||
| 'zh-tw'
|
||||
| 'ja-jp'
|
||||
| 'ko-kr'
|
||||
| 'fr-fr'
|
||||
| 'de-de'
|
||||
| 'it-it'
|
||||
| 'es-es'
|
||||
| 'pt-pt'
|
||||
| 'ru-ru'
|
||||
| 'pl-pl'
|
||||
| 'ar-ar'
|
||||
| 'tr-tr'
|
||||
| 'th-th'
|
||||
| 'vi-vn'
|
||||
| 'id-id'
|
||||
| 'ur-pk'
|
||||
| 'ms-my'
|
||||
|
||||
// langCode应当能够唯一确认一种语言
|
||||
export type Language = {
|
||||
value: string
|
||||
langCode: LanguageCode
|
||||
label: () => string
|
||||
emoji: string
|
||||
}
|
||||
|
||||
export interface TranslateHistory {
|
||||
id: string
|
||||
sourceText: string
|
||||
targetText: string
|
||||
sourceLanguage: string
|
||||
targetLanguage: string
|
||||
sourceLanguage: LanguageCode
|
||||
targetLanguage: LanguageCode
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { LanguagesEnum } from '@renderer/config/translate'
|
||||
import { Language, LanguageCode } from '@renderer/types'
|
||||
import { franc } from 'franc-min'
|
||||
import React, { MutableRefObject } from 'react'
|
||||
|
||||
/**
|
||||
* 使用Unicode字符范围检测语言
|
||||
* 适用于较短文本的语言检测
|
||||
* @param {string} text 需要检测语言的文本
|
||||
* @returns {string} 检测到的语言代码
|
||||
* @param text 需要检测语言的文本
|
||||
* @returns 检测到的语言
|
||||
*/
|
||||
export const detectLanguageByUnicode = (text: string): string => {
|
||||
export const detectLanguageByUnicode = (text: string): Language => {
|
||||
const counts = {
|
||||
zh: 0,
|
||||
ja: 0,
|
||||
@ -40,8 +42,8 @@ export const detectLanguageByUnicode = (text: string): string => {
|
||||
}
|
||||
}
|
||||
|
||||
if (totalChars === 0) return 'en'
|
||||
let maxLang = 'en'
|
||||
if (totalChars === 0) return LanguagesEnum.enUS
|
||||
let maxLang = ''
|
||||
let maxCount = 0
|
||||
|
||||
for (const [lang, count] of Object.entries(counts)) {
|
||||
@ -52,73 +54,68 @@ export const detectLanguageByUnicode = (text: string): string => {
|
||||
}
|
||||
|
||||
if (maxCount / totalChars < 0.3) {
|
||||
return 'en'
|
||||
return LanguagesEnum.enUS
|
||||
}
|
||||
|
||||
switch (maxLang) {
|
||||
case 'zh':
|
||||
return LanguagesEnum.zhCN
|
||||
case 'ja':
|
||||
return LanguagesEnum.jaJP
|
||||
case 'ko':
|
||||
return LanguagesEnum.koKR
|
||||
case 'ru':
|
||||
return LanguagesEnum.ruRU
|
||||
case 'ar':
|
||||
return LanguagesEnum.arAR
|
||||
case 'en':
|
||||
return LanguagesEnum.enUS
|
||||
default:
|
||||
console.error(`Unknown language: ${maxLang}`)
|
||||
return LanguagesEnum.enUS
|
||||
}
|
||||
return maxLang
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测输入文本的语言
|
||||
* @param {string} inputText 需要检测语言的文本
|
||||
* @returns {Promise<string>} 检测到的语言代码
|
||||
* @param inputText 需要检测语言的文本
|
||||
* @returns 检测到的语言
|
||||
*/
|
||||
export const detectLanguage = async (inputText: string): Promise<string> => {
|
||||
export const detectLanguage = async (inputText: string): Promise<Language> => {
|
||||
const text = inputText.trim()
|
||||
if (!text) return 'any'
|
||||
let code: string
|
||||
if (!text) return LanguagesEnum.zhCN
|
||||
let lang: Language
|
||||
|
||||
// 如果文本长度小于20个字符,使用Unicode范围检测
|
||||
if (text.length < 20) {
|
||||
code = detectLanguageByUnicode(text)
|
||||
lang = detectLanguageByUnicode(text)
|
||||
} else {
|
||||
// franc 返回 ISO 639-3 代码
|
||||
const iso3 = franc(text)
|
||||
const isoMap: Record<string, string> = {
|
||||
cmn: 'zh',
|
||||
jpn: 'ja',
|
||||
kor: 'ko',
|
||||
rus: 'ru',
|
||||
ara: 'ar',
|
||||
spa: 'es',
|
||||
fra: 'fr',
|
||||
deu: 'de',
|
||||
ita: 'it',
|
||||
por: 'pt',
|
||||
eng: 'en',
|
||||
pol: 'pl',
|
||||
tur: 'tr',
|
||||
tha: 'th',
|
||||
vie: 'vi',
|
||||
ind: 'id',
|
||||
urd: 'ur',
|
||||
zsm: 'ms'
|
||||
const isoMap: Record<string, Language> = {
|
||||
cmn: LanguagesEnum.zhCN,
|
||||
jpn: LanguagesEnum.jaJP,
|
||||
kor: LanguagesEnum.koKR,
|
||||
rus: LanguagesEnum.ruRU,
|
||||
ara: LanguagesEnum.arAR,
|
||||
spa: LanguagesEnum.esES,
|
||||
fra: LanguagesEnum.frFR,
|
||||
deu: LanguagesEnum.deDE,
|
||||
ita: LanguagesEnum.itIT,
|
||||
por: LanguagesEnum.ptPT,
|
||||
eng: LanguagesEnum.enUS,
|
||||
pol: LanguagesEnum.plPL,
|
||||
tur: LanguagesEnum.trTR,
|
||||
tha: LanguagesEnum.thTH,
|
||||
vie: LanguagesEnum.viVN,
|
||||
ind: LanguagesEnum.idID,
|
||||
urd: LanguagesEnum.urPK,
|
||||
zsm: LanguagesEnum.msMY
|
||||
}
|
||||
code = isoMap[iso3] || 'en'
|
||||
lang = isoMap[iso3] || LanguagesEnum.enUS
|
||||
}
|
||||
|
||||
// 映射到应用使用的语言键
|
||||
const languageMap: Record<string, string> = {
|
||||
zh: 'chinese',
|
||||
ja: 'japanese',
|
||||
ko: 'korean',
|
||||
ru: 'russian',
|
||||
es: 'spanish',
|
||||
fr: 'french',
|
||||
de: 'german',
|
||||
it: 'italian',
|
||||
pt: 'portuguese',
|
||||
ar: 'arabic',
|
||||
en: 'english',
|
||||
pl: 'polish',
|
||||
tr: 'turkish',
|
||||
th: 'thai',
|
||||
vi: 'vietnamese',
|
||||
id: 'indonesian',
|
||||
ur: 'urdu',
|
||||
ms: 'malay'
|
||||
}
|
||||
|
||||
return languageMap[code] || 'english'
|
||||
return lang
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,10 +124,13 @@ export const detectLanguage = async (inputText: string): Promise<string> => {
|
||||
* @param languagePair 配置的语言对
|
||||
* @returns 目标语言
|
||||
*/
|
||||
export const getTargetLanguageForBidirectional = (sourceLanguage: string, languagePair: [string, string]): string => {
|
||||
if (sourceLanguage === languagePair[0]) {
|
||||
export const getTargetLanguageForBidirectional = (
|
||||
sourceLanguage: Language,
|
||||
languagePair: [Language, Language]
|
||||
): Language => {
|
||||
if (sourceLanguage.langCode === languagePair[0].langCode) {
|
||||
return languagePair[1]
|
||||
} else if (sourceLanguage === languagePair[1]) {
|
||||
} else if (sourceLanguage.langCode === languagePair[1].langCode) {
|
||||
return languagePair[0]
|
||||
}
|
||||
return languagePair[0] !== sourceLanguage ? languagePair[0] : languagePair[1]
|
||||
@ -142,8 +142,8 @@ export const getTargetLanguageForBidirectional = (sourceLanguage: string, langua
|
||||
* @param languagePair 配置的语言对
|
||||
* @returns 是否在语言对中
|
||||
*/
|
||||
export const isLanguageInPair = (sourceLanguage: string, languagePair: [string, string]): boolean => {
|
||||
return [languagePair[0], languagePair[1]].includes(sourceLanguage)
|
||||
export const isLanguageInPair = (sourceLanguage: Language, languagePair: [Language, Language]): boolean => {
|
||||
return [languagePair[0].langCode, languagePair[1].langCode].includes(sourceLanguage.langCode)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,11 +155,11 @@ export const isLanguageInPair = (sourceLanguage: string, languagePair: [string,
|
||||
* @returns 处理结果对象
|
||||
*/
|
||||
export const determineTargetLanguage = (
|
||||
sourceLanguage: string,
|
||||
targetLanguage: string,
|
||||
sourceLanguage: Language,
|
||||
targetLanguage: Language,
|
||||
isBidirectional: boolean,
|
||||
bidirectionalPair: [string, string]
|
||||
): { success: boolean; language?: string; errorType?: 'same_language' | 'not_in_pair' } => {
|
||||
bidirectionalPair: [Language, Language]
|
||||
): { success: boolean; language?: Language; errorType?: 'same_language' | 'not_in_pair' } => {
|
||||
if (isBidirectional) {
|
||||
if (!isLanguageInPair(sourceLanguage, bidirectionalPair)) {
|
||||
return { success: false, errorType: 'not_in_pair' }
|
||||
@ -169,7 +169,7 @@ export const determineTargetLanguage = (
|
||||
language: getTargetLanguageForBidirectional(sourceLanguage, bidirectionalPair)
|
||||
}
|
||||
} else {
|
||||
if (sourceLanguage === targetLanguage) {
|
||||
if (sourceLanguage.langCode === targetLanguage.langCode) {
|
||||
return { success: false, errorType: 'same_language' }
|
||||
}
|
||||
return { success: true, language: targetLanguage }
|
||||
@ -228,3 +228,21 @@ export const createOutputScrollHandler = (
|
||||
handleScrollSync(e.currentTarget, inputEl, isProgrammaticScrollRef)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据语言代码获取对应的语言对象
|
||||
* @param langcode - 语言代码
|
||||
* @returns 返回对应的语言对象,如果找不到则返回英语(enUS)
|
||||
* @example
|
||||
* ```typescript
|
||||
* const language = getLanguageByLangcode('zh-cn') // 返回中文语言对象
|
||||
* ```
|
||||
*/
|
||||
export const getLanguageByLangcode = (langcode: LanguageCode): Language => {
|
||||
const result = Object.values(LanguagesEnum).find((item) => item.langCode === langcode)
|
||||
if (!result) {
|
||||
console.error(`Language not found for langcode: ${langcode}`)
|
||||
return LanguagesEnum.enUS
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { SwapOutlined } from '@ant-design/icons'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { TranslateLanguageOptions } from '@renderer/config/translate'
|
||||
import { LanguagesEnum, translateLanguageOptions } from '@renderer/config/translate'
|
||||
import db from '@renderer/databases'
|
||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Assistant, Language } from '@renderer/types'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { Select, Space } from 'antd'
|
||||
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||
import { Select } from 'antd'
|
||||
import { isEmpty } from 'lodash'
|
||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
@ -18,11 +19,11 @@ interface Props {
|
||||
text: string
|
||||
}
|
||||
|
||||
let _targetLanguage = 'chinese'
|
||||
let _targetLanguage = (await db.settings.get({ id: 'translate:target:language' }))?.value || LanguagesEnum.zhCN
|
||||
|
||||
const Translate: FC<Props> = ({ text }) => {
|
||||
const [result, setResult] = useState('')
|
||||
const [targetLanguage, setTargetLanguage] = useState(_targetLanguage)
|
||||
const [targetLanguage, setTargetLanguage] = useState<Language>(_targetLanguage)
|
||||
const { translateModel } = useDefaultModel()
|
||||
const { t } = useTranslation()
|
||||
const translatingRef = useRef(false)
|
||||
@ -37,8 +38,7 @@ const Translate: FC<Props> = ({ text }) => {
|
||||
try {
|
||||
translatingRef.current = true
|
||||
|
||||
const targetLang = await db.settings.get({ id: 'translate:target:language' })
|
||||
const assistant: Assistant = getDefaultTranslateAssistant(targetLang?.value || targetLanguage, text)
|
||||
const assistant: Assistant = getDefaultTranslateAssistant(targetLanguage, text)
|
||||
// const message: Message = {
|
||||
// id: uuid(),
|
||||
// role: 'user',
|
||||
@ -64,7 +64,7 @@ const Translate: FC<Props> = ({ text }) => {
|
||||
useEffect(() => {
|
||||
runAsyncFunction(async () => {
|
||||
const targetLang = await db.settings.get({ id: 'translate:target:language' })
|
||||
targetLang && setTargetLanguage(targetLang.value)
|
||||
targetLang && setTargetLanguage(getLanguageByLangcode(targetLang.value))
|
||||
})
|
||||
}, [])
|
||||
|
||||
@ -91,22 +91,17 @@ const Translate: FC<Props> = ({ text }) => {
|
||||
<SwapOutlined />
|
||||
<Select
|
||||
showSearch
|
||||
value={targetLanguage}
|
||||
value={targetLanguage.langCode}
|
||||
style={{ maxWidth: 200, minWidth: 130, flex: 1 }}
|
||||
optionFilterProp="label"
|
||||
options={TranslateLanguageOptions}
|
||||
options={translateLanguageOptions.map((option) => ({
|
||||
value: option.langCode,
|
||||
label: option.emoji + ' ' + option.label()
|
||||
}))}
|
||||
onChange={async (value) => {
|
||||
await db.settings.put({ id: 'translate:target:language', value })
|
||||
setTargetLanguage(value)
|
||||
setTargetLanguage(getLanguageByLangcode(value))
|
||||
}}
|
||||
optionRender={(option) => (
|
||||
<Space>
|
||||
<span role="img" aria-label={option.data.label}>
|
||||
{option.data.emoji}
|
||||
</span>
|
||||
{option.label}
|
||||
</Space>
|
||||
)}
|
||||
/>
|
||||
</MenuContainer>
|
||||
<Main>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import CopyButton from '@renderer/components/CopyButton'
|
||||
import { TranslateLanguageOptions, translateLanguageOptions } from '@renderer/config/translate'
|
||||
import { LanguagesEnum, translateLanguageOptions } from '@renderer/config/translate'
|
||||
import db from '@renderer/databases'
|
||||
import { useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
@ -11,11 +11,11 @@ import {
|
||||
getDefaultTopic,
|
||||
getTranslateModel
|
||||
} from '@renderer/services/AssistantService'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Assistant, Language, Topic } from '@renderer/types'
|
||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { abortCompletion } from '@renderer/utils/abortController'
|
||||
import { detectLanguage } from '@renderer/utils/translate'
|
||||
import { detectLanguage, getLanguageByLangcode } from '@renderer/utils/translate'
|
||||
import { Select, Space, Tooltip } from 'antd'
|
||||
import { ArrowRightFromLine, ArrowRightToLine, ChevronDown, CircleHelp, Globe } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@ -33,8 +33,8 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
const { t } = useTranslation()
|
||||
const { translateModelPrompt, language } = useSettings()
|
||||
|
||||
const [targetLanguage, setTargetLanguage] = useState('')
|
||||
const [alterLanguage, setAlterLanguage] = useState('')
|
||||
const [targetLanguage, setTargetLanguage] = useState<Language>(LanguagesEnum.enUS)
|
||||
const [alterLanguage, setAlterLanguage] = useState<Language>(LanguagesEnum.zhCN)
|
||||
|
||||
const [error, setError] = useState('')
|
||||
const [showOriginal, setShowOriginal] = useState(false)
|
||||
@ -52,24 +52,24 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
runAsyncFunction(async () => {
|
||||
const biDirectionLangPair = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||
|
||||
let targetLang = ''
|
||||
let alterLang = ''
|
||||
let targetLang: Language
|
||||
let alterLang: Language
|
||||
|
||||
if (!biDirectionLangPair || !biDirectionLangPair.value[0]) {
|
||||
const lang = TranslateLanguageOptions.find((lang) => lang.langCode?.toLowerCase() === language.toLowerCase())
|
||||
const lang = translateLanguageOptions.find((lang) => lang.langCode?.toLowerCase() === language.toLowerCase())
|
||||
if (lang) {
|
||||
targetLang = lang.value
|
||||
targetLang = lang
|
||||
} else {
|
||||
targetLang = 'chinese'
|
||||
targetLang = LanguagesEnum.zhCN
|
||||
}
|
||||
} else {
|
||||
targetLang = biDirectionLangPair.value[0]
|
||||
targetLang = getLanguageByLangcode(biDirectionLangPair.value[0])
|
||||
}
|
||||
|
||||
if (!biDirectionLangPair || !biDirectionLangPair.value[1]) {
|
||||
alterLang = 'english'
|
||||
alterLang = LanguagesEnum.enUS
|
||||
} else {
|
||||
alterLang = biDirectionLangPair.value[1]
|
||||
alterLang = getLanguageByLangcode(biDirectionLangPair.value[1])
|
||||
}
|
||||
|
||||
setTargetLanguage(targetLang)
|
||||
@ -120,8 +120,8 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
|
||||
const sourceLanguage = await detectLanguage(action.selectedText)
|
||||
|
||||
let translateLang = ''
|
||||
if (sourceLanguage === targetLanguage) {
|
||||
let translateLang: Language
|
||||
if (sourceLanguage.langCode === targetLanguage.langCode) {
|
||||
translateLang = alterLanguage
|
||||
} else {
|
||||
translateLang = targetLanguage
|
||||
@ -129,7 +129,7 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
|
||||
// Initialize prompt content
|
||||
const userContent = translateModelPrompt
|
||||
.replaceAll('{{target_language}}', translateLang)
|
||||
.replaceAll('{{target_language}}', translateLang.value)
|
||||
.replaceAll('{{text}}', action.selectedText)
|
||||
|
||||
processMessages(assistantRef.current, topicRef.current, userContent, setAskId, onStream, onFinish, onError)
|
||||
@ -147,11 +147,11 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
return lastAssistantMessage ? <MessageContent key={lastAssistantMessage.id} message={lastAssistantMessage} /> : null
|
||||
}, [allMessages])
|
||||
|
||||
const handleChangeLanguage = (targetLanguage: string, alterLanguage: string) => {
|
||||
const handleChangeLanguage = (targetLanguage: Language, alterLanguage: Language) => {
|
||||
setTargetLanguage(targetLanguage)
|
||||
setAlterLanguage(alterLanguage)
|
||||
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [targetLanguage, alterLanguage] })
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [targetLanguage.langCode, alterLanguage.langCode] })
|
||||
}
|
||||
|
||||
const handlePause = () => {
|
||||
@ -177,46 +177,46 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
<ArrowRightToLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
||||
<Tooltip placement="bottom" title={t('translate.target_language')} arrow>
|
||||
<Select
|
||||
value={targetLanguage}
|
||||
value={targetLanguage.langCode}
|
||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||
listHeight={160}
|
||||
title={t('translate.target_language')}
|
||||
optionFilterProp="label"
|
||||
options={translateLanguageOptions().map((lang) => ({
|
||||
value: lang.value,
|
||||
options={translateLanguageOptions.map((lang) => ({
|
||||
value: lang.langCode,
|
||||
label: (
|
||||
<Space.Compact direction="horizontal" block>
|
||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||
{lang.emoji}
|
||||
</span>
|
||||
<Space.Compact block>{lang.label}</Space.Compact>
|
||||
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||
</Space.Compact>
|
||||
)
|
||||
}))}
|
||||
onChange={(value) => handleChangeLanguage(value, alterLanguage)}
|
||||
onChange={(value) => handleChangeLanguage(getLanguageByLangcode(value), alterLanguage)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ArrowRightFromLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
||||
<Tooltip placement="bottom" title={t('translate.alter_language')} arrow>
|
||||
<Select
|
||||
value={alterLanguage}
|
||||
value={alterLanguage.langCode}
|
||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||
listHeight={160}
|
||||
title={t('translate.alter_language')}
|
||||
optionFilterProp="label"
|
||||
options={translateLanguageOptions().map((lang) => ({
|
||||
value: lang.value,
|
||||
options={translateLanguageOptions.map((lang) => ({
|
||||
value: lang.langCode,
|
||||
label: (
|
||||
<Space.Compact direction="horizontal" block>
|
||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||
{lang.emoji}
|
||||
</span>
|
||||
<Space.Compact block>{lang.label}</Space.Compact>
|
||||
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||
</Space.Compact>
|
||||
)
|
||||
}))}
|
||||
onChange={(value) => handleChangeLanguage(targetLanguage, value)}
|
||||
onChange={(value) => handleChangeLanguage(targetLanguage, getLanguageByLangcode(value))}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user