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 { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||||
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||||
|
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||||
import { Modal, ModalProps } from 'antd'
|
import { Modal, ModalProps } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { TextAreaProps } from 'antd/lib/input'
|
import { TextAreaProps } from 'antd/lib/input'
|
||||||
@ -111,7 +112,7 @@ const PopupContainer: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const assistant = getDefaultTranslateAssistant(targetLanguage, textValue)
|
const assistant = getDefaultTranslateAssistant(getLanguageByLangcode(targetLanguage), textValue)
|
||||||
const translatedText = await fetchTranslate({ content: textValue, assistant })
|
const translatedText = await fetchTranslate({ content: textValue, assistant })
|
||||||
if (isMounted.current) {
|
if (isMounted.current) {
|
||||||
setTextValue(translatedText)
|
setTextValue(translatedText)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
|||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||||
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||||
|
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||||
import { Button, Tooltip } from 'antd'
|
import { Button, Tooltip } from 'antd'
|
||||||
import { Languages } from 'lucide-react'
|
import { Languages } from 'lucide-react'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
@ -54,7 +55,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
|||||||
|
|
||||||
setIsTranslating(true)
|
setIsTranslating(true)
|
||||||
try {
|
try {
|
||||||
const assistant = getDefaultTranslateAssistant(targetLanguage, text)
|
const assistant = getDefaultTranslateAssistant(getLanguageByLangcode(targetLanguage), text)
|
||||||
const translatedText = await fetchTranslate({ content: text, assistant })
|
const translatedText = await fetchTranslate({ content: text, assistant })
|
||||||
onTranslated(translatedText)
|
onTranslated(translatedText)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -75,7 +76,7 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
|
|||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
placement="top"
|
placement="top"
|
||||||
title={t('chat.input.translate', { target_language: t(`languages.${targetLanguage.toString()}`) })}
|
title={t('chat.input.translate', { target_language: getLanguageByLangcode(targetLanguage).label() })}
|
||||||
arrow>
|
arrow>
|
||||||
<ToolbarButton onClick={handleTranslate} disabled={disabled || isTranslating} style={style} type="text">
|
<ToolbarButton onClick={handleTranslate} disabled={disabled || isTranslating} style={style} type="text">
|
||||||
{isTranslating ? <LoadingOutlined spin /> : <Languages size={18} />}
|
{isTranslating ? <LoadingOutlined spin /> : <Languages size={18} />}
|
||||||
|
|||||||
@ -1,136 +1,159 @@
|
|||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
|
import { Language } from '@renderer/types'
|
||||||
|
|
||||||
export interface TranslateLanguageOption {
|
export const ENGLISH: Language = {
|
||||||
value: string
|
|
||||||
langCode?: string
|
|
||||||
label: string
|
|
||||||
emoji: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TranslateLanguageOptions: TranslateLanguageOption[] = [
|
|
||||||
{
|
|
||||||
value: 'English',
|
value: 'English',
|
||||||
langCode: 'en-us',
|
langCode: 'en-us',
|
||||||
label: i18n.t('languages.english'),
|
label: () => i18n.t('languages.english'),
|
||||||
emoji: '🇬🇧'
|
emoji: '🇬🇧'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const CHINESE_SIMPLIFIED: Language = {
|
||||||
value: 'Chinese (Simplified)',
|
value: 'Chinese (Simplified)',
|
||||||
langCode: 'zh-cn',
|
langCode: 'zh-cn',
|
||||||
label: i18n.t('languages.chinese'),
|
label: () => i18n.t('languages.chinese'),
|
||||||
emoji: '🇨🇳'
|
emoji: '🇨🇳'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const CHINESE_TRADITIONAL: Language = {
|
||||||
value: 'Chinese (Traditional)',
|
value: 'Chinese (Traditional)',
|
||||||
langCode: 'zh-tw',
|
langCode: 'zh-tw',
|
||||||
label: i18n.t('languages.chinese-traditional'),
|
label: () => i18n.t('languages.chinese-traditional'),
|
||||||
emoji: '🇭🇰'
|
emoji: '🇭🇰'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const JAPANESE: Language = {
|
||||||
value: 'Japanese',
|
value: 'Japanese',
|
||||||
langCode: 'ja-jp',
|
langCode: 'ja-jp',
|
||||||
label: i18n.t('languages.japanese'),
|
label: () => i18n.t('languages.japanese'),
|
||||||
emoji: '🇯🇵'
|
emoji: '🇯🇵'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const KOREAN: Language = {
|
||||||
value: 'Korean',
|
value: 'Korean',
|
||||||
langCode: 'ko-kr',
|
langCode: 'ko-kr',
|
||||||
label: i18n.t('languages.korean'),
|
label: () => i18n.t('languages.korean'),
|
||||||
emoji: '🇰🇷'
|
emoji: '🇰🇷'
|
||||||
},
|
}
|
||||||
|
|
||||||
{
|
export const FRENCH: Language = {
|
||||||
value: 'French',
|
value: 'French',
|
||||||
langCode: 'fr-fr',
|
langCode: 'fr-fr',
|
||||||
label: i18n.t('languages.french'),
|
label: () => i18n.t('languages.french'),
|
||||||
emoji: '🇫🇷'
|
emoji: '🇫🇷'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const GERMAN: Language = {
|
||||||
value: 'German',
|
value: 'German',
|
||||||
langCode: 'de-de',
|
langCode: 'de-de',
|
||||||
label: i18n.t('languages.german'),
|
label: () => i18n.t('languages.german'),
|
||||||
emoji: '🇩🇪'
|
emoji: '🇩🇪'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const ITALIAN: Language = {
|
||||||
value: 'Italian',
|
value: 'Italian',
|
||||||
langCode: 'it-it',
|
langCode: 'it-it',
|
||||||
label: i18n.t('languages.italian'),
|
label: () => i18n.t('languages.italian'),
|
||||||
emoji: '🇮🇹'
|
emoji: '🇮🇹'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const SPANISH: Language = {
|
||||||
value: 'Spanish',
|
value: 'Spanish',
|
||||||
langCode: 'es-es',
|
langCode: 'es-es',
|
||||||
label: i18n.t('languages.spanish'),
|
label: () => i18n.t('languages.spanish'),
|
||||||
emoji: '🇪🇸'
|
emoji: '🇪🇸'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const PORTUGUESE: Language = {
|
||||||
value: 'Portuguese',
|
value: 'Portuguese',
|
||||||
langCode: 'pt-pt',
|
langCode: 'pt-pt',
|
||||||
label: i18n.t('languages.portuguese'),
|
label: () => i18n.t('languages.portuguese'),
|
||||||
emoji: '🇵🇹'
|
emoji: '🇵🇹'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const RUSSIAN: Language = {
|
||||||
value: 'Russian',
|
value: 'Russian',
|
||||||
langCode: 'ru-ru',
|
langCode: 'ru-ru',
|
||||||
label: i18n.t('languages.russian'),
|
label: () => i18n.t('languages.russian'),
|
||||||
emoji: '🇷🇺'
|
emoji: '🇷🇺'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const POLISH: Language = {
|
||||||
value: 'Polish',
|
value: 'Polish',
|
||||||
langCode: 'pl-pl',
|
langCode: 'pl-pl',
|
||||||
label: i18n.t('languages.polish'),
|
label: () => i18n.t('languages.polish'),
|
||||||
emoji: '🇵🇱'
|
emoji: '🇵🇱'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const ARABIC: Language = {
|
||||||
value: 'Arabic',
|
value: 'Arabic',
|
||||||
langCode: 'ar-ar',
|
langCode: 'ar-ar',
|
||||||
label: i18n.t('languages.arabic'),
|
label: () => i18n.t('languages.arabic'),
|
||||||
emoji: '🇸🇦'
|
emoji: '🇸🇦'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const TURKISH: Language = {
|
||||||
value: 'Turkish',
|
value: 'Turkish',
|
||||||
langCode: 'tr-tr',
|
langCode: 'tr-tr',
|
||||||
label: i18n.t('languages.turkish'),
|
label: () => i18n.t('languages.turkish'),
|
||||||
emoji: '🇹🇷'
|
emoji: '🇹🇷'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const THAI: Language = {
|
||||||
value: 'Thai',
|
value: 'Thai',
|
||||||
langCode: 'th-th',
|
langCode: 'th-th',
|
||||||
label: i18n.t('languages.thai'),
|
label: () => i18n.t('languages.thai'),
|
||||||
emoji: '🇹🇭'
|
emoji: '🇹🇭'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const VIETNAMESE: Language = {
|
||||||
value: 'Vietnamese',
|
value: 'Vietnamese',
|
||||||
langCode: 'vi-vn',
|
langCode: 'vi-vn',
|
||||||
label: i18n.t('languages.vietnamese'),
|
label: () => i18n.t('languages.vietnamese'),
|
||||||
emoji: '🇻🇳'
|
emoji: '🇻🇳'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const INDONESIAN: Language = {
|
||||||
value: 'Indonesian',
|
value: 'Indonesian',
|
||||||
langCode: 'id-id',
|
langCode: 'id-id',
|
||||||
label: i18n.t('languages.indonesian'),
|
label: () => i18n.t('languages.indonesian'),
|
||||||
emoji: '🇮🇩'
|
emoji: '🇮🇩'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const URDU: Language = {
|
||||||
value: 'Urdu',
|
value: 'Urdu',
|
||||||
langCode: 'ur-pk',
|
langCode: 'ur-pk',
|
||||||
label: i18n.t('languages.urdu'),
|
label: () => i18n.t('languages.urdu'),
|
||||||
emoji: '🇵🇰'
|
emoji: '🇵🇰'
|
||||||
},
|
}
|
||||||
{
|
|
||||||
|
export const MALAY: Language = {
|
||||||
value: 'Malay',
|
value: 'Malay',
|
||||||
langCode: 'ms-my',
|
langCode: 'ms-my',
|
||||||
label: i18n.t('languages.malay'),
|
label: () => i18n.t('languages.malay'),
|
||||||
emoji: '🇲🇾'
|
emoji: '🇲🇾'
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
|
||||||
export const translateLanguageOptions = (): typeof TranslateLanguageOptions => {
|
export const LanguagesEnum = {
|
||||||
return TranslateLanguageOptions.map((option) => {
|
enUS: ENGLISH,
|
||||||
return {
|
zhCN: CHINESE_SIMPLIFIED,
|
||||||
value: option.value,
|
zhTW: CHINESE_TRADITIONAL,
|
||||||
label: option.label,
|
jaJP: JAPANESE,
|
||||||
emoji: option.emoji
|
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 type { Message as NewMessage, MessageBlock } from '@renderer/types/newMessage'
|
||||||
import { Dexie, type EntityTable } from 'dexie'
|
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)
|
// Database declaration (move this to its own module also)
|
||||||
export const db = new Dexie('CherryStudio') as Dexie & {
|
export const db = new Dexie('CherryStudio') as Dexie & {
|
||||||
@ -74,4 +74,17 @@ db.version(7)
|
|||||||
})
|
})
|
||||||
.upgrade((tx) => upgradeToV7(tx))
|
.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
|
export default db
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import Logger from '@renderer/config/logger'
|
import Logger from '@renderer/config/logger'
|
||||||
import type { LegacyMessage as OldMessage, Topic } from '@renderer/types'
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { FileTypes } from '@renderer/types' // Import FileTypes enum
|
import type { LanguageCode, LegacyMessage as OldMessage, Topic } from '@renderer/types'
|
||||||
import { WebSearchSource } from '@renderer/types'
|
import { FileTypes, WebSearchSource } from '@renderer/types' // Import FileTypes enum
|
||||||
import type {
|
import type {
|
||||||
BaseMessageBlock,
|
BaseMessageBlock,
|
||||||
CitationMessageBlock,
|
CitationMessageBlock,
|
||||||
@ -308,3 +308,78 @@ export async function upgradeToV7(tx: Transaction): Promise<void> {
|
|||||||
|
|
||||||
Logger.log('DB migration to version 7 finished successfully.')
|
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,
|
updateMessageAndBlocksThunk,
|
||||||
updateTranslationBlockThunk
|
updateTranslationBlockThunk
|
||||||
} from '@renderer/store/thunk/messageThunk'
|
} 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 type { Message, MessageBlock } from '@renderer/types/newMessage'
|
||||||
import { MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
import { MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||||
import { abortCompletion } from '@renderer/utils/abortController'
|
import { abortCompletion } from '@renderer/utils/abortController'
|
||||||
@ -195,9 +195,9 @@ export function useMessageOperations(topic: Topic) {
|
|||||||
const getTranslationUpdater = useCallback(
|
const getTranslationUpdater = useCallback(
|
||||||
async (
|
async (
|
||||||
messageId: string,
|
messageId: string,
|
||||||
targetLanguage: string,
|
targetLanguage: LanguageCode,
|
||||||
sourceBlockId?: string,
|
sourceBlockId?: string,
|
||||||
sourceLanguage?: string
|
sourceLanguage?: LanguageCode
|
||||||
): Promise<((accumulatedText: string, isComplete?: boolean) => void) | null> => {
|
): Promise<((accumulatedText: string, isComplete?: boolean) => void) | null> => {
|
||||||
if (!topic.id) return 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 { classNames, delay, formatFileSize, getFileExtension } from '@renderer/utils'
|
||||||
import { formatQuotedText } from '@renderer/utils/formats'
|
import { formatQuotedText } from '@renderer/utils/formats'
|
||||||
import { getFilesFromDropEvent, getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
import { getFilesFromDropEvent, getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
||||||
|
import { getLanguageByLangcode } from '@renderer/utils/translate'
|
||||||
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
import { documentExts, imageExts, textExts } from '@shared/config/constant'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { Button, Tooltip } from 'antd'
|
import { Button, Tooltip } from 'antd'
|
||||||
@ -253,7 +254,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsTranslating(true)
|
setIsTranslating(true)
|
||||||
const translatedText = await translateText(text, targetLanguage)
|
const translatedText = await translateText(text, getLanguageByLangcode(targetLanguage))
|
||||||
translatedText && setText(translatedText)
|
translatedText && setText(translatedText)
|
||||||
setTimeout(() => resizeTextArea(), 0)
|
setTimeout(() => resizeTextArea(), 0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { CheckOutlined, EditOutlined, QuestionCircleOutlined, SyncOutlined } fro
|
|||||||
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
||||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||||
import { isVisionModel } from '@renderer/config/models'
|
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 { useMessageEditing } from '@renderer/context/MessageEditingContext'
|
||||||
import { useChatContext } from '@renderer/hooks/useChatContext'
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
||||||
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
|
||||||
@ -13,7 +13,7 @@ import { translateText } from '@renderer/services/TranslateService'
|
|||||||
import store, { RootState } from '@renderer/store'
|
import store, { RootState } from '@renderer/store'
|
||||||
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
||||||
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
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 { type Message, MessageBlockType } from '@renderer/types/newMessage'
|
||||||
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, classNames } from '@renderer/utils'
|
import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, classNames } from '@renderer/utils'
|
||||||
import { copyMessageAsPlainText } from '@renderer/utils/copy'
|
import { copyMessageAsPlainText } from '@renderer/utils/copy'
|
||||||
@ -153,12 +153,12 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
}, [message.id, startEditing])
|
}, [message.id, startEditing])
|
||||||
|
|
||||||
const handleTranslate = useCallback(
|
const handleTranslate = useCallback(
|
||||||
async (language: string) => {
|
async (language: Language) => {
|
||||||
if (isTranslating) return
|
if (isTranslating) return
|
||||||
|
|
||||||
setIsTranslating(true)
|
setIsTranslating(true)
|
||||||
const messageId = message.id
|
const messageId = message.id
|
||||||
const translationUpdater = await getTranslationUpdater(messageId, language)
|
const translationUpdater = await getTranslationUpdater(messageId, language.langCode)
|
||||||
if (!translationUpdater) return
|
if (!translationUpdater) return
|
||||||
try {
|
try {
|
||||||
await translateText(mainTextContent, language, translationUpdater)
|
await translateText(mainTextContent, language, translationUpdater)
|
||||||
@ -457,10 +457,10 @@ const MessageMenubar: FC<Props> = (props) => {
|
|||||||
backgroundClip: 'border-box'
|
backgroundClip: 'border-box'
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
...TranslateLanguageOptions.map((item) => ({
|
...translateLanguageOptions.map((item) => ({
|
||||||
label: item.emoji + ' ' + item.label,
|
label: item.emoji + ' ' + item.label(),
|
||||||
key: item.value,
|
key: item.langCode,
|
||||||
onClick: () => handleTranslate(item.value)
|
onClick: () => handleTranslate(item)
|
||||||
})),
|
})),
|
||||||
...(hasTranslationBlocks
|
...(hasTranslationBlocks
|
||||||
? [
|
? [
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
isSupportedFlexServiceTier,
|
isSupportedFlexServiceTier,
|
||||||
isSupportedReasoningEffortOpenAIModel
|
isSupportedReasoningEffortOpenAIModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
|
import { translateLanguageOptions } from '@renderer/config/translate'
|
||||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
@ -44,14 +45,7 @@ import {
|
|||||||
setShowTranslateConfirm,
|
setShowTranslateConfirm,
|
||||||
setThoughtAutoCollapse
|
setThoughtAutoCollapse
|
||||||
} from '@renderer/store/settings'
|
} from '@renderer/store/settings'
|
||||||
import {
|
import { Assistant, AssistantSettings, CodeStyleVarious, MathEngine, ThemeMode } from '@renderer/types'
|
||||||
Assistant,
|
|
||||||
AssistantSettings,
|
|
||||||
CodeStyleVarious,
|
|
||||||
MathEngine,
|
|
||||||
ThemeMode,
|
|
||||||
TranslateLanguageVarious
|
|
||||||
} from '@renderer/types'
|
|
||||||
import { modalConfirm } from '@renderer/utils'
|
import { modalConfirm } from '@renderer/utils'
|
||||||
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
import { getSendMessageShortcutLabel } from '@renderer/utils/input'
|
||||||
import { Button, Col, InputNumber, Row, Slider, Switch, Tooltip } from 'antd'
|
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>
|
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
|
||||||
<Selector
|
<Selector
|
||||||
value={targetLanguage}
|
value={targetLanguage}
|
||||||
onChange={(value) => setTargetLanguage(value as TranslateLanguageVarious)}
|
onChange={(value) => setTargetLanguage(value)}
|
||||||
options={[
|
options={translateLanguageOptions.map((item) => {
|
||||||
{ value: 'chinese', label: t('settings.input.target_language.chinese') },
|
return { value: item.langCode, label: item.emoji + ' ' + item.label() }
|
||||||
{ 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') }
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
|||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
import { getProviderLogo } from '@renderer/config/providers'
|
||||||
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
@ -543,7 +544,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsTranslating(true)
|
setIsTranslating(true)
|
||||||
const translatedText = await translateText(painting.prompt, 'english')
|
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||||
updatePaintingState({ prompt: translatedText })
|
updatePaintingState({ prompt: translatedText })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Translation failed:', error)
|
console.error('Translation failed:', error)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
|||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { TEXT_TO_IMAGES_MODELS } from '@renderer/config/models'
|
import { TEXT_TO_IMAGES_MODELS } from '@renderer/config/models'
|
||||||
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
@ -302,7 +303,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsTranslating(true)
|
setIsTranslating(true)
|
||||||
const translatedText = await translateText(painting.prompt, 'english')
|
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||||
updatePaintingState({ prompt: translatedText })
|
updatePaintingState({ prompt: translatedText })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Translation failed:', error)
|
console.error('Translation failed:', error)
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Scrollbar from '@renderer/components/Scrollbar'
|
|||||||
import TranslateButton from '@renderer/components/TranslateButton'
|
import TranslateButton from '@renderer/components/TranslateButton'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
import { getProviderLogo } from '@renderer/config/providers'
|
||||||
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
import { usePaintings } from '@renderer/hooks/usePaintings'
|
import { usePaintings } from '@renderer/hooks/usePaintings'
|
||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
@ -255,7 +256,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsTranslating(true)
|
setIsTranslating(true)
|
||||||
const translatedText = await translateText(painting.prompt, 'english')
|
const translatedText = await translateText(painting.prompt, LanguagesEnum.enUS)
|
||||||
updatePaintingState({ prompt: translatedText })
|
updatePaintingState({ prompt: translatedText })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Translation failed:', error)
|
console.error('Translation failed:', error)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
|||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { isEmbeddingModel } from '@renderer/config/models'
|
import { isEmbeddingModel } from '@renderer/config/models'
|
||||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
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 { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
@ -15,13 +15,14 @@ import { getDefaultTranslateAssistant } from '@renderer/services/AssistantServic
|
|||||||
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
import { getModelUniqId, hasModel } from '@renderer/services/ModelService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setTranslateModelPrompt } from '@renderer/store/settings'
|
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 { runAsyncFunction, uuid } from '@renderer/utils'
|
||||||
import {
|
import {
|
||||||
createInputScrollHandler,
|
createInputScrollHandler,
|
||||||
createOutputScrollHandler,
|
createOutputScrollHandler,
|
||||||
detectLanguage,
|
detectLanguage,
|
||||||
determineTargetLanguage
|
determineTargetLanguage,
|
||||||
|
getLanguageByLangcode
|
||||||
} from '@renderer/utils/translate'
|
} from '@renderer/utils/translate'
|
||||||
import { Button, Dropdown, Empty, Flex, Modal, Popconfirm, Select, Space, Switch, Tooltip } from 'antd'
|
import { Button, Dropdown, Empty, Flex, Modal, Popconfirm, Select, Space, Switch, Tooltip } from 'antd'
|
||||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
@ -35,7 +36,7 @@ import styled from 'styled-components'
|
|||||||
|
|
||||||
let _text = ''
|
let _text = ''
|
||||||
let _result = ''
|
let _result = ''
|
||||||
let _targetLanguage = 'english'
|
let _targetLanguage = LanguagesEnum.enUS
|
||||||
|
|
||||||
const TranslateSettings: FC<{
|
const TranslateSettings: FC<{
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@ -46,8 +47,8 @@ const TranslateSettings: FC<{
|
|||||||
setIsBidirectional: (value: boolean) => void
|
setIsBidirectional: (value: boolean) => void
|
||||||
enableMarkdown: boolean
|
enableMarkdown: boolean
|
||||||
setEnableMarkdown: (value: boolean) => void
|
setEnableMarkdown: (value: boolean) => void
|
||||||
bidirectionalPair: [string, string]
|
bidirectionalPair: [Language, Language]
|
||||||
setBidirectionalPair: (value: [string, string]) => void
|
setBidirectionalPair: (value: [Language, Language]) => void
|
||||||
translateModel: Model | undefined
|
translateModel: Model | undefined
|
||||||
onModelChange: (model: Model) => void
|
onModelChange: (model: Model) => void
|
||||||
allModels: Model[]
|
allModels: Model[]
|
||||||
@ -71,7 +72,7 @@ const TranslateSettings: FC<{
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { translateModelPrompt } = useSettings()
|
const { translateModelPrompt } = useSettings()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const [localPair, setLocalPair] = useState<[string, string]>(bidirectionalPair)
|
const [localPair, setLocalPair] = useState<[Language, Language]>(bidirectionalPair)
|
||||||
const [showPrompt, setShowPrompt] = useState(false)
|
const [showPrompt, setShowPrompt] = useState(false)
|
||||||
const [localPrompt, setLocalPrompt] = useState(translateModelPrompt)
|
const [localPrompt, setLocalPrompt] = useState(translateModelPrompt)
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ const TranslateSettings: FC<{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setBidirectionalPair(localPair)
|
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:scroll:sync', value: isScrollSyncEnabled })
|
||||||
db.settings.put({ id: 'translate:markdown:enabled', value: enableMarkdown })
|
db.settings.put({ id: 'translate:markdown:enabled', value: enableMarkdown })
|
||||||
db.settings.put({ id: 'translate:model:prompt', value: localPrompt })
|
db.settings.put({ id: 'translate:model:prompt', value: localPrompt })
|
||||||
@ -189,16 +190,16 @@ const TranslateSettings: FC<{
|
|||||||
<Flex align="center" justify="space-between" gap={10}>
|
<Flex align="center" justify="space-between" gap={10}>
|
||||||
<Select
|
<Select
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
value={localPair[0]}
|
value={localPair[0].langCode}
|
||||||
onChange={(value) => setLocalPair([value, localPair[1]])}
|
onChange={(value) => setLocalPair([getLanguageByLangcode(value), localPair[1]])}
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={translateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.langCode,
|
||||||
label: (
|
label: (
|
||||||
<Space.Compact direction="horizontal" block>
|
<Space.Compact direction="horizontal" block>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||||
{lang.emoji}
|
{lang.emoji}
|
||||||
</span>
|
</span>
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}))}
|
}))}
|
||||||
@ -206,16 +207,16 @@ const TranslateSettings: FC<{
|
|||||||
<span>⇆</span>
|
<span>⇆</span>
|
||||||
<Select
|
<Select
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
value={localPair[1]}
|
value={localPair[1].langCode}
|
||||||
onChange={(value) => setLocalPair([localPair[0], value])}
|
onChange={(value) => setLocalPair([localPair[0], getLanguageByLangcode(value)])}
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={translateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.langCode,
|
||||||
label: (
|
label: (
|
||||||
<Space.Compact direction="horizontal" block>
|
<Space.Compact direction="horizontal" block>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||||
{lang.emoji}
|
{lang.emoji}
|
||||||
</span>
|
</span>
|
||||||
<div style={{ textAlign: 'left', flex: 1 }}>{lang.label}</div>
|
<div style={{ textAlign: 'left', flex: 1 }}>{lang.label()}</div>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}))}
|
}))}
|
||||||
@ -275,7 +276,6 @@ const TranslateSettings: FC<{
|
|||||||
const TranslatePage: FC = () => {
|
const TranslatePage: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { shikiMarkdownIt } = useCodeStyle()
|
const { shikiMarkdownIt } = useCodeStyle()
|
||||||
const [targetLanguage, setTargetLanguage] = useState(_targetLanguage)
|
|
||||||
const [text, setText] = useState(_text)
|
const [text, setText] = useState(_text)
|
||||||
const [result, setResult] = useState(_result)
|
const [result, setResult] = useState(_result)
|
||||||
const [renderedMarkdown, setRenderedMarkdown] = useState<string>('')
|
const [renderedMarkdown, setRenderedMarkdown] = useState<string>('')
|
||||||
@ -286,10 +286,14 @@ const TranslatePage: FC = () => {
|
|||||||
const [isScrollSyncEnabled, setIsScrollSyncEnabled] = useState(false)
|
const [isScrollSyncEnabled, setIsScrollSyncEnabled] = useState(false)
|
||||||
const [isBidirectional, setIsBidirectional] = useState(false)
|
const [isBidirectional, setIsBidirectional] = useState(false)
|
||||||
const [enableMarkdown, setEnableMarkdown] = 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 [settingsVisible, setSettingsVisible] = useState(false)
|
||||||
const [detectedLanguage, setDetectedLanguage] = useState<string | null>(null)
|
const [detectedLanguage, setDetectedLanguage] = useState<Language | null>(null)
|
||||||
const [sourceLanguage, setSourceLanguage] = useState<string>('auto')
|
const [sourceLanguage, setSourceLanguage] = useState<Language | 'auto'>('auto')
|
||||||
|
const [targetLanguage, setTargetLanguage] = useState<Language>(_targetLanguage)
|
||||||
const contentContainerRef = useRef<HTMLDivElement>(null)
|
const contentContainerRef = useRef<HTMLDivElement>(null)
|
||||||
const textAreaRef = useRef<TextAreaRef>(null)
|
const textAreaRef = useRef<TextAreaRef>(null)
|
||||||
const outputTextRef = useRef<HTMLDivElement>(null)
|
const outputTextRef = useRef<HTMLDivElement>(null)
|
||||||
@ -329,8 +333,8 @@ const TranslatePage: FC = () => {
|
|||||||
const saveTranslateHistory = async (
|
const saveTranslateHistory = async (
|
||||||
sourceText: string,
|
sourceText: string,
|
||||||
targetText: string,
|
targetText: string,
|
||||||
sourceLanguage: string,
|
sourceLanguage: LanguageCode,
|
||||||
targetLanguage: string
|
targetLanguage: LanguageCode
|
||||||
) => {
|
) => {
|
||||||
const history: TranslateHistory = {
|
const history: TranslateHistory = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@ -364,7 +368,7 @@ const TranslatePage: FC = () => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
// 确定源语言:如果用户选择了特定语言,使用用户选择的;如果选择'auto',则自动检测
|
// 确定源语言:如果用户选择了特定语言,使用用户选择的;如果选择'auto',则自动检测
|
||||||
let actualSourceLanguage: string
|
let actualSourceLanguage: Language
|
||||||
if (sourceLanguage === 'auto') {
|
if (sourceLanguage === 'auto') {
|
||||||
actualSourceLanguage = await detectLanguage(text)
|
actualSourceLanguage = await detectLanguage(text)
|
||||||
setDetectedLanguage(actualSourceLanguage)
|
setDetectedLanguage(actualSourceLanguage)
|
||||||
@ -389,7 +393,7 @@ const TranslatePage: FC = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const actualTargetLanguage = result.language as string
|
const actualTargetLanguage = result.language as Language
|
||||||
if (isBidirectional) {
|
if (isBidirectional) {
|
||||||
setTargetLanguage(actualTargetLanguage)
|
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)
|
setLoading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Translation error:', error)
|
console.error('Translation error:', error)
|
||||||
@ -432,7 +436,7 @@ const TranslatePage: FC = () => {
|
|||||||
const onHistoryItemClick = (history: TranslateHistory) => {
|
const onHistoryItemClick = (history: TranslateHistory) => {
|
||||||
setText(history.sourceText)
|
setText(history.sourceText)
|
||||||
setResult(history.targetText)
|
setResult(history.targetText)
|
||||||
setTargetLanguage(history.targetLanguage)
|
setTargetLanguage(getLanguageByLangcode(history.targetLanguage))
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -460,20 +464,32 @@ const TranslatePage: FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const targetLang = await db.settings.get({ id: 'translate:target:language' })
|
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' })
|
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' })
|
const bidirectionalPairSetting = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||||
if (bidirectionalPairSetting) {
|
if (bidirectionalPairSetting) {
|
||||||
const langPair = bidirectionalPairSetting.value
|
const langPair = bidirectionalPairSetting.value
|
||||||
|
let source: undefined | Language
|
||||||
|
let target: undefined | Language
|
||||||
|
|
||||||
if (Array.isArray(langPair) && langPair.length === 2 && langPair[0] !== langPair[1]) {
|
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 {
|
} else {
|
||||||
const defaultPair: [string, string] = ['english', 'chinese']
|
const defaultPair: [Language, Language] = [LanguagesEnum.enUS, LanguagesEnum.zhCN]
|
||||||
setBidirectionalPair(defaultPair)
|
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 onKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
const isEnterPressed = e.keyCode == 13
|
const isEnterPressed = e.key === 'Enter'
|
||||||
if (isEnterPressed && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
if (isEnterPressed && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
onTranslate()
|
onTranslate()
|
||||||
@ -501,32 +517,37 @@ const TranslatePage: FC = () => {
|
|||||||
|
|
||||||
// 获取当前语言状态显示
|
// 获取当前语言状态显示
|
||||||
const getLanguageDisplay = () => {
|
const getLanguageDisplay = () => {
|
||||||
|
try {
|
||||||
if (isBidirectional) {
|
if (isBidirectional) {
|
||||||
return (
|
return (
|
||||||
<Flex align="center" style={{ width: 160 }}>
|
<Flex align="center" style={{ width: 160 }}>
|
||||||
<BidirectionalLanguageDisplay>
|
<BidirectionalLanguageDisplay>
|
||||||
{`${t(`languages.${bidirectionalPair[0]}`)} ⇆ ${t(`languages.${bidirectionalPair[1]}`)}`}
|
{`${bidirectionalPair[0].label()} ⇆ ${bidirectionalPair[1].label()}`}
|
||||||
</BidirectionalLanguageDisplay>
|
</BidirectionalLanguageDisplay>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting language display:', error)
|
||||||
|
setBidirectionalPair([LanguagesEnum.enUS, LanguagesEnum.zhCN])
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
style={{ width: 160 }}
|
style={{ width: 160 }}
|
||||||
value={targetLanguage}
|
value={targetLanguage.langCode}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setTargetLanguage(value)
|
setTargetLanguage(getLanguageByLangcode(value))
|
||||||
db.settings.put({ id: 'translate:target:language', value })
|
db.settings.put({ id: 'translate:target:language', value })
|
||||||
}}
|
}}
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={translateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.langCode,
|
||||||
label: (
|
label: (
|
||||||
<Space.Compact direction="horizontal" block>
|
<Space.Compact direction="horizontal" block>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||||
{lang.emoji}
|
{lang.emoji}
|
||||||
</span>
|
</span>
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}))}
|
}))}
|
||||||
@ -603,28 +624,29 @@ const TranslatePage: FC = () => {
|
|||||||
<Flex align="center" gap={20}>
|
<Flex align="center" gap={20}>
|
||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
value={sourceLanguage}
|
value={sourceLanguage !== 'auto' ? sourceLanguage.langCode : 'auto'}
|
||||||
style={{ width: 180 }}
|
style={{ width: 180 }}
|
||||||
optionFilterProp="label"
|
optionFilterProp="label"
|
||||||
onChange={(value) => {
|
onChange={(value: LanguageCode | 'auto') => {
|
||||||
setSourceLanguage(value)
|
if (value !== 'auto') setSourceLanguage(getLanguageByLangcode(value))
|
||||||
|
else setSourceLanguage('auto')
|
||||||
db.settings.put({ id: 'translate:source:language', value })
|
db.settings.put({ id: 'translate:source:language', value })
|
||||||
}}
|
}}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: 'auto',
|
value: 'auto',
|
||||||
label: detectedLanguage
|
label: detectedLanguage
|
||||||
? `${t('translate.detected.language')} (${t(`languages.${detectedLanguage.toLowerCase()}`)})`
|
? `${t('translate.detected.language')} (${detectedLanguage.label()})`
|
||||||
: t('translate.detected.language')
|
: t('translate.detected.language')
|
||||||
},
|
},
|
||||||
...translateLanguageOptions().map((lang) => ({
|
...translateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.langCode,
|
||||||
label: (
|
label: (
|
||||||
<Space.Compact direction="horizontal" block>
|
<Space.Compact direction="horizontal" block>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||||
{lang.emoji}
|
{lang.emoji}
|
||||||
</span>
|
</span>
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@
|
|||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { addAssistant } from '@renderer/store/assistants'
|
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'
|
import { uuid } from '@renderer/utils'
|
||||||
|
|
||||||
export function getDefaultAssistant(): Assistant {
|
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 translateModel = getTranslateModel()
|
||||||
const assistant: Assistant = getDefaultAssistant()
|
const assistant: Assistant = getDefaultAssistant()
|
||||||
assistant.model = translateModel
|
assistant.model = translateModel
|
||||||
@ -39,7 +39,7 @@ export function getDefaultTranslateAssistant(targetLanguage: string, text: strin
|
|||||||
|
|
||||||
assistant.prompt = store
|
assistant.prompt = store
|
||||||
.getState()
|
.getState()
|
||||||
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage)
|
.settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value)
|
||||||
.replaceAll('{{text}}', text)
|
.replaceAll('{{text}}', text)
|
||||||
return assistant
|
return assistant
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Logger from '@renderer/config/logger'
|
import Logger from '@renderer/config/logger'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { upgradeToV7 } from '@renderer/databases/upgrades'
|
import { upgradeToV7, upgradeToV8 } from '@renderer/databases/upgrades'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { setWebDAVSyncState } from '@renderer/store/backup'
|
import { setWebDAVSyncState } from '@renderer/store/backup'
|
||||||
@ -637,7 +637,7 @@ export function stopAutoSync() {
|
|||||||
export async function getBackupData() {
|
export async function getBackupData() {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
version: 4,
|
version: 5,
|
||||||
localStorage,
|
localStorage,
|
||||||
indexedDB: await backupDatabase()
|
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' })
|
window.message.success({ content: i18n.t('message.restore.success'), key: 'restore' })
|
||||||
setTimeout(() => window.api.reload(), 1000)
|
setTimeout(() => window.api.reload(), 1000)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
|
import { Language } from '@renderer/types'
|
||||||
|
|
||||||
import { fetchTranslate } from './ApiService'
|
import { fetchTranslate } from './ApiService'
|
||||||
import { getDefaultTranslateAssistant } from './AssistantService'
|
import { getDefaultTranslateAssistant } from './AssistantService'
|
||||||
|
|
||||||
export const translateText = async (
|
export const translateText = async (
|
||||||
text: string,
|
text: string,
|
||||||
targetLanguage: string,
|
targetLanguage: Language,
|
||||||
onResponse?: (text: string, isComplete: boolean) => void
|
onResponse?: (text: string, isComplete: boolean) => void
|
||||||
) => {
|
) => {
|
||||||
const translateModel = store.getState().llm.translateModel
|
const translateModel = store.getState().llm.translateModel
|
||||||
|
|||||||
@ -54,7 +54,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 119,
|
version: 120,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { SYSTEM_MODELS } from '@renderer/config/models'
|
|||||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import i18n from '@renderer/i18n'
|
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 { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils'
|
||||||
import { UpgradeChannel } from '@shared/config/constant'
|
import { UpgradeChannel } from '@shared/config/constant'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
@ -897,6 +897,7 @@ const migrateConfig = {
|
|||||||
},
|
},
|
||||||
'65': (state: RootState) => {
|
'65': (state: RootState) => {
|
||||||
try {
|
try {
|
||||||
|
// @ts-ignore expect error
|
||||||
state.settings.targetLanguage = 'english'
|
state.settings.targetLanguage = 'english'
|
||||||
return state
|
return state
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1736,6 +1737,25 @@ const migrateConfig = {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return state
|
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',
|
assistantsTabSortType: 'list',
|
||||||
sendMessageShortcut: 'Enter',
|
sendMessageShortcut: 'Enter',
|
||||||
language: navigator.language as LanguageVarious,
|
language: navigator.language as LanguageVarious,
|
||||||
targetLanguage: 'english' as TranslateLanguageVarious,
|
targetLanguage: 'en-us',
|
||||||
proxyMode: 'system',
|
proxyMode: 'system',
|
||||||
proxyUrl: undefined,
|
proxyUrl: undefined,
|
||||||
userName: '',
|
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 LanguageVarious = 'zh-CN' | 'zh-TW' | 'el-GR' | 'en-US' | 'es-ES' | 'fr-FR' | 'ja-JP' | 'pt-PT' | 'ru-RU'
|
||||||
|
|
||||||
export type TranslateLanguageVarious =
|
export type TranslateLanguageVarious = LanguageCode
|
||||||
| 'chinese'
|
|
||||||
| 'chinese-traditional'
|
|
||||||
| 'greek'
|
|
||||||
| 'english'
|
|
||||||
| 'spanish'
|
|
||||||
| 'french'
|
|
||||||
| 'japanese'
|
|
||||||
| 'portuguese'
|
|
||||||
| 'russian'
|
|
||||||
|
|
||||||
export type CodeStyleVarious = 'auto' | string
|
export type CodeStyleVarious = 'auto' | string
|
||||||
|
|
||||||
@ -489,12 +480,41 @@ export type GenerateImageResponse = {
|
|||||||
images: string[]
|
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 {
|
export interface TranslateHistory {
|
||||||
id: string
|
id: string
|
||||||
sourceText: string
|
sourceText: string
|
||||||
targetText: string
|
targetText: string
|
||||||
sourceLanguage: string
|
sourceLanguage: LanguageCode
|
||||||
targetLanguage: string
|
targetLanguage: LanguageCode
|
||||||
createdAt: string
|
createdAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
|
import { LanguagesEnum } from '@renderer/config/translate'
|
||||||
|
import { Language, LanguageCode } from '@renderer/types'
|
||||||
import { franc } from 'franc-min'
|
import { franc } from 'franc-min'
|
||||||
import React, { MutableRefObject } from 'react'
|
import React, { MutableRefObject } from 'react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用Unicode字符范围检测语言
|
* 使用Unicode字符范围检测语言
|
||||||
* 适用于较短文本的语言检测
|
* 适用于较短文本的语言检测
|
||||||
* @param {string} text 需要检测语言的文本
|
* @param text 需要检测语言的文本
|
||||||
* @returns {string} 检测到的语言代码
|
* @returns 检测到的语言
|
||||||
*/
|
*/
|
||||||
export const detectLanguageByUnicode = (text: string): string => {
|
export const detectLanguageByUnicode = (text: string): Language => {
|
||||||
const counts = {
|
const counts = {
|
||||||
zh: 0,
|
zh: 0,
|
||||||
ja: 0,
|
ja: 0,
|
||||||
@ -40,8 +42,8 @@ export const detectLanguageByUnicode = (text: string): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalChars === 0) return 'en'
|
if (totalChars === 0) return LanguagesEnum.enUS
|
||||||
let maxLang = 'en'
|
let maxLang = ''
|
||||||
let maxCount = 0
|
let maxCount = 0
|
||||||
|
|
||||||
for (const [lang, count] of Object.entries(counts)) {
|
for (const [lang, count] of Object.entries(counts)) {
|
||||||
@ -52,73 +54,68 @@ export const detectLanguageByUnicode = (text: string): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (maxCount / totalChars < 0.3) {
|
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 需要检测语言的文本
|
* @param inputText 需要检测语言的文本
|
||||||
* @returns {Promise<string>} 检测到的语言代码
|
* @returns 检测到的语言
|
||||||
*/
|
*/
|
||||||
export const detectLanguage = async (inputText: string): Promise<string> => {
|
export const detectLanguage = async (inputText: string): Promise<Language> => {
|
||||||
const text = inputText.trim()
|
const text = inputText.trim()
|
||||||
if (!text) return 'any'
|
if (!text) return LanguagesEnum.zhCN
|
||||||
let code: string
|
let lang: Language
|
||||||
|
|
||||||
// 如果文本长度小于20个字符,使用Unicode范围检测
|
// 如果文本长度小于20个字符,使用Unicode范围检测
|
||||||
if (text.length < 20) {
|
if (text.length < 20) {
|
||||||
code = detectLanguageByUnicode(text)
|
lang = detectLanguageByUnicode(text)
|
||||||
} else {
|
} else {
|
||||||
// franc 返回 ISO 639-3 代码
|
// franc 返回 ISO 639-3 代码
|
||||||
const iso3 = franc(text)
|
const iso3 = franc(text)
|
||||||
const isoMap: Record<string, string> = {
|
const isoMap: Record<string, Language> = {
|
||||||
cmn: 'zh',
|
cmn: LanguagesEnum.zhCN,
|
||||||
jpn: 'ja',
|
jpn: LanguagesEnum.jaJP,
|
||||||
kor: 'ko',
|
kor: LanguagesEnum.koKR,
|
||||||
rus: 'ru',
|
rus: LanguagesEnum.ruRU,
|
||||||
ara: 'ar',
|
ara: LanguagesEnum.arAR,
|
||||||
spa: 'es',
|
spa: LanguagesEnum.esES,
|
||||||
fra: 'fr',
|
fra: LanguagesEnum.frFR,
|
||||||
deu: 'de',
|
deu: LanguagesEnum.deDE,
|
||||||
ita: 'it',
|
ita: LanguagesEnum.itIT,
|
||||||
por: 'pt',
|
por: LanguagesEnum.ptPT,
|
||||||
eng: 'en',
|
eng: LanguagesEnum.enUS,
|
||||||
pol: 'pl',
|
pol: LanguagesEnum.plPL,
|
||||||
tur: 'tr',
|
tur: LanguagesEnum.trTR,
|
||||||
tha: 'th',
|
tha: LanguagesEnum.thTH,
|
||||||
vie: 'vi',
|
vie: LanguagesEnum.viVN,
|
||||||
ind: 'id',
|
ind: LanguagesEnum.idID,
|
||||||
urd: 'ur',
|
urd: LanguagesEnum.urPK,
|
||||||
zsm: 'ms'
|
zsm: LanguagesEnum.msMY
|
||||||
}
|
}
|
||||||
code = isoMap[iso3] || 'en'
|
lang = isoMap[iso3] || LanguagesEnum.enUS
|
||||||
}
|
}
|
||||||
|
|
||||||
// 映射到应用使用的语言键
|
return lang
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -127,10 +124,13 @@ export const detectLanguage = async (inputText: string): Promise<string> => {
|
|||||||
* @param languagePair 配置的语言对
|
* @param languagePair 配置的语言对
|
||||||
* @returns 目标语言
|
* @returns 目标语言
|
||||||
*/
|
*/
|
||||||
export const getTargetLanguageForBidirectional = (sourceLanguage: string, languagePair: [string, string]): string => {
|
export const getTargetLanguageForBidirectional = (
|
||||||
if (sourceLanguage === languagePair[0]) {
|
sourceLanguage: Language,
|
||||||
|
languagePair: [Language, Language]
|
||||||
|
): Language => {
|
||||||
|
if (sourceLanguage.langCode === languagePair[0].langCode) {
|
||||||
return languagePair[1]
|
return languagePair[1]
|
||||||
} else if (sourceLanguage === languagePair[1]) {
|
} else if (sourceLanguage.langCode === languagePair[1].langCode) {
|
||||||
return languagePair[0]
|
return languagePair[0]
|
||||||
}
|
}
|
||||||
return languagePair[0] !== sourceLanguage ? languagePair[0] : languagePair[1]
|
return languagePair[0] !== sourceLanguage ? languagePair[0] : languagePair[1]
|
||||||
@ -142,8 +142,8 @@ export const getTargetLanguageForBidirectional = (sourceLanguage: string, langua
|
|||||||
* @param languagePair 配置的语言对
|
* @param languagePair 配置的语言对
|
||||||
* @returns 是否在语言对中
|
* @returns 是否在语言对中
|
||||||
*/
|
*/
|
||||||
export const isLanguageInPair = (sourceLanguage: string, languagePair: [string, string]): boolean => {
|
export const isLanguageInPair = (sourceLanguage: Language, languagePair: [Language, Language]): boolean => {
|
||||||
return [languagePair[0], languagePair[1]].includes(sourceLanguage)
|
return [languagePair[0].langCode, languagePair[1].langCode].includes(sourceLanguage.langCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,11 +155,11 @@ export const isLanguageInPair = (sourceLanguage: string, languagePair: [string,
|
|||||||
* @returns 处理结果对象
|
* @returns 处理结果对象
|
||||||
*/
|
*/
|
||||||
export const determineTargetLanguage = (
|
export const determineTargetLanguage = (
|
||||||
sourceLanguage: string,
|
sourceLanguage: Language,
|
||||||
targetLanguage: string,
|
targetLanguage: Language,
|
||||||
isBidirectional: boolean,
|
isBidirectional: boolean,
|
||||||
bidirectionalPair: [string, string]
|
bidirectionalPair: [Language, Language]
|
||||||
): { success: boolean; language?: string; errorType?: 'same_language' | 'not_in_pair' } => {
|
): { success: boolean; language?: Language; errorType?: 'same_language' | 'not_in_pair' } => {
|
||||||
if (isBidirectional) {
|
if (isBidirectional) {
|
||||||
if (!isLanguageInPair(sourceLanguage, bidirectionalPair)) {
|
if (!isLanguageInPair(sourceLanguage, bidirectionalPair)) {
|
||||||
return { success: false, errorType: 'not_in_pair' }
|
return { success: false, errorType: 'not_in_pair' }
|
||||||
@ -169,7 +169,7 @@ export const determineTargetLanguage = (
|
|||||||
language: getTargetLanguageForBidirectional(sourceLanguage, bidirectionalPair)
|
language: getTargetLanguageForBidirectional(sourceLanguage, bidirectionalPair)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (sourceLanguage === targetLanguage) {
|
if (sourceLanguage.langCode === targetLanguage.langCode) {
|
||||||
return { success: false, errorType: 'same_language' }
|
return { success: false, errorType: 'same_language' }
|
||||||
}
|
}
|
||||||
return { success: true, language: targetLanguage }
|
return { success: true, language: targetLanguage }
|
||||||
@ -228,3 +228,21 @@ export const createOutputScrollHandler = (
|
|||||||
handleScrollSync(e.currentTarget, inputEl, isProgrammaticScrollRef)
|
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 { SwapOutlined } from '@ant-design/icons'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
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 db from '@renderer/databases'
|
||||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
import { fetchTranslate } from '@renderer/services/ApiService'
|
import { fetchTranslate } from '@renderer/services/ApiService'
|
||||||
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
import { getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||||
import { Assistant } from '@renderer/types'
|
import { Assistant, Language } from '@renderer/types'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
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 { isEmpty } from 'lodash'
|
||||||
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
@ -18,11 +19,11 @@ interface Props {
|
|||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
let _targetLanguage = 'chinese'
|
let _targetLanguage = (await db.settings.get({ id: 'translate:target:language' }))?.value || LanguagesEnum.zhCN
|
||||||
|
|
||||||
const Translate: FC<Props> = ({ text }) => {
|
const Translate: FC<Props> = ({ text }) => {
|
||||||
const [result, setResult] = useState('')
|
const [result, setResult] = useState('')
|
||||||
const [targetLanguage, setTargetLanguage] = useState(_targetLanguage)
|
const [targetLanguage, setTargetLanguage] = useState<Language>(_targetLanguage)
|
||||||
const { translateModel } = useDefaultModel()
|
const { translateModel } = useDefaultModel()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const translatingRef = useRef(false)
|
const translatingRef = useRef(false)
|
||||||
@ -37,8 +38,7 @@ const Translate: FC<Props> = ({ text }) => {
|
|||||||
try {
|
try {
|
||||||
translatingRef.current = true
|
translatingRef.current = true
|
||||||
|
|
||||||
const targetLang = await db.settings.get({ id: 'translate:target:language' })
|
const assistant: Assistant = getDefaultTranslateAssistant(targetLanguage, text)
|
||||||
const assistant: Assistant = getDefaultTranslateAssistant(targetLang?.value || targetLanguage, text)
|
|
||||||
// const message: Message = {
|
// const message: Message = {
|
||||||
// id: uuid(),
|
// id: uuid(),
|
||||||
// role: 'user',
|
// role: 'user',
|
||||||
@ -64,7 +64,7 @@ const Translate: FC<Props> = ({ text }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const targetLang = await db.settings.get({ id: 'translate:target:language' })
|
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 />
|
<SwapOutlined />
|
||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
value={targetLanguage}
|
value={targetLanguage.langCode}
|
||||||
style={{ maxWidth: 200, minWidth: 130, flex: 1 }}
|
style={{ maxWidth: 200, minWidth: 130, flex: 1 }}
|
||||||
optionFilterProp="label"
|
optionFilterProp="label"
|
||||||
options={TranslateLanguageOptions}
|
options={translateLanguageOptions.map((option) => ({
|
||||||
|
value: option.langCode,
|
||||||
|
label: option.emoji + ' ' + option.label()
|
||||||
|
}))}
|
||||||
onChange={async (value) => {
|
onChange={async (value) => {
|
||||||
await db.settings.put({ id: 'translate:target:language', 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>
|
</MenuContainer>
|
||||||
<Main>
|
<Main>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import CopyButton from '@renderer/components/CopyButton'
|
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 db from '@renderer/databases'
|
||||||
import { useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
import { useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
@ -11,11 +11,11 @@ import {
|
|||||||
getDefaultTopic,
|
getDefaultTopic,
|
||||||
getTranslateModel
|
getTranslateModel
|
||||||
} from '@renderer/services/AssistantService'
|
} 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 type { ActionItem } from '@renderer/types/selectionTypes'
|
||||||
import { runAsyncFunction } from '@renderer/utils'
|
import { runAsyncFunction } from '@renderer/utils'
|
||||||
import { abortCompletion } from '@renderer/utils/abortController'
|
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 { Select, Space, Tooltip } from 'antd'
|
||||||
import { ArrowRightFromLine, ArrowRightToLine, ChevronDown, CircleHelp, Globe } from 'lucide-react'
|
import { ArrowRightFromLine, ArrowRightToLine, ChevronDown, CircleHelp, Globe } from 'lucide-react'
|
||||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
@ -33,8 +33,8 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { translateModelPrompt, language } = useSettings()
|
const { translateModelPrompt, language } = useSettings()
|
||||||
|
|
||||||
const [targetLanguage, setTargetLanguage] = useState('')
|
const [targetLanguage, setTargetLanguage] = useState<Language>(LanguagesEnum.enUS)
|
||||||
const [alterLanguage, setAlterLanguage] = useState('')
|
const [alterLanguage, setAlterLanguage] = useState<Language>(LanguagesEnum.zhCN)
|
||||||
|
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [showOriginal, setShowOriginal] = useState(false)
|
const [showOriginal, setShowOriginal] = useState(false)
|
||||||
@ -52,24 +52,24 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
|||||||
runAsyncFunction(async () => {
|
runAsyncFunction(async () => {
|
||||||
const biDirectionLangPair = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
const biDirectionLangPair = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||||
|
|
||||||
let targetLang = ''
|
let targetLang: Language
|
||||||
let alterLang = ''
|
let alterLang: Language
|
||||||
|
|
||||||
if (!biDirectionLangPair || !biDirectionLangPair.value[0]) {
|
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) {
|
if (lang) {
|
||||||
targetLang = lang.value
|
targetLang = lang
|
||||||
} else {
|
} else {
|
||||||
targetLang = 'chinese'
|
targetLang = LanguagesEnum.zhCN
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
targetLang = biDirectionLangPair.value[0]
|
targetLang = getLanguageByLangcode(biDirectionLangPair.value[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!biDirectionLangPair || !biDirectionLangPair.value[1]) {
|
if (!biDirectionLangPair || !biDirectionLangPair.value[1]) {
|
||||||
alterLang = 'english'
|
alterLang = LanguagesEnum.enUS
|
||||||
} else {
|
} else {
|
||||||
alterLang = biDirectionLangPair.value[1]
|
alterLang = getLanguageByLangcode(biDirectionLangPair.value[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
setTargetLanguage(targetLang)
|
setTargetLanguage(targetLang)
|
||||||
@ -120,8 +120,8 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
|||||||
|
|
||||||
const sourceLanguage = await detectLanguage(action.selectedText)
|
const sourceLanguage = await detectLanguage(action.selectedText)
|
||||||
|
|
||||||
let translateLang = ''
|
let translateLang: Language
|
||||||
if (sourceLanguage === targetLanguage) {
|
if (sourceLanguage.langCode === targetLanguage.langCode) {
|
||||||
translateLang = alterLanguage
|
translateLang = alterLanguage
|
||||||
} else {
|
} else {
|
||||||
translateLang = targetLanguage
|
translateLang = targetLanguage
|
||||||
@ -129,7 +129,7 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
|||||||
|
|
||||||
// Initialize prompt content
|
// Initialize prompt content
|
||||||
const userContent = translateModelPrompt
|
const userContent = translateModelPrompt
|
||||||
.replaceAll('{{target_language}}', translateLang)
|
.replaceAll('{{target_language}}', translateLang.value)
|
||||||
.replaceAll('{{text}}', action.selectedText)
|
.replaceAll('{{text}}', action.selectedText)
|
||||||
|
|
||||||
processMessages(assistantRef.current, topicRef.current, userContent, setAskId, onStream, onFinish, onError)
|
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
|
return lastAssistantMessage ? <MessageContent key={lastAssistantMessage.id} message={lastAssistantMessage} /> : null
|
||||||
}, [allMessages])
|
}, [allMessages])
|
||||||
|
|
||||||
const handleChangeLanguage = (targetLanguage: string, alterLanguage: string) => {
|
const handleChangeLanguage = (targetLanguage: Language, alterLanguage: Language) => {
|
||||||
setTargetLanguage(targetLanguage)
|
setTargetLanguage(targetLanguage)
|
||||||
setAlterLanguage(alterLanguage)
|
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 = () => {
|
const handlePause = () => {
|
||||||
@ -177,46 +177,46 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
|||||||
<ArrowRightToLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
<ArrowRightToLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
||||||
<Tooltip placement="bottom" title={t('translate.target_language')} arrow>
|
<Tooltip placement="bottom" title={t('translate.target_language')} arrow>
|
||||||
<Select
|
<Select
|
||||||
value={targetLanguage}
|
value={targetLanguage.langCode}
|
||||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||||
listHeight={160}
|
listHeight={160}
|
||||||
title={t('translate.target_language')}
|
title={t('translate.target_language')}
|
||||||
optionFilterProp="label"
|
optionFilterProp="label"
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={translateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.langCode,
|
||||||
label: (
|
label: (
|
||||||
<Space.Compact direction="horizontal" block>
|
<Space.Compact direction="horizontal" block>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||||
{lang.emoji}
|
{lang.emoji}
|
||||||
</span>
|
</span>
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}))}
|
}))}
|
||||||
onChange={(value) => handleChangeLanguage(value, alterLanguage)}
|
onChange={(value) => handleChangeLanguage(getLanguageByLangcode(value), alterLanguage)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<ArrowRightFromLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
<ArrowRightFromLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
||||||
<Tooltip placement="bottom" title={t('translate.alter_language')} arrow>
|
<Tooltip placement="bottom" title={t('translate.alter_language')} arrow>
|
||||||
<Select
|
<Select
|
||||||
value={alterLanguage}
|
value={alterLanguage.langCode}
|
||||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||||
listHeight={160}
|
listHeight={160}
|
||||||
title={t('translate.alter_language')}
|
title={t('translate.alter_language')}
|
||||||
optionFilterProp="label"
|
optionFilterProp="label"
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={translateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.langCode,
|
||||||
label: (
|
label: (
|
||||||
<Space.Compact direction="horizontal" block>
|
<Space.Compact direction="horizontal" block>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
||||||
{lang.emoji}
|
{lang.emoji}
|
||||||
</span>
|
</span>
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
<Space.Compact block>{lang.label()}</Space.Compact>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
)
|
)
|
||||||
}))}
|
}))}
|
||||||
onChange={(value) => handleChangeLanguage(targetLanguage, value)}
|
onChange={(value) => handleChangeLanguage(targetLanguage, getLanguageByLangcode(value))}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user