mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-13 21:57:30 +08:00
refactor: simplify translate action by removing dual language selector
Remove the confusing "smart translation" feature that had two language selectors (target + alternative). The UI was misleading as users expected a source-to-target translation flow, but the feature auto-switched between languages based on detected source language. Now uses a single target language selector for clearer UX. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d0a1512f23
commit
44d814cfd6
@ -2938,9 +2938,6 @@
|
||||
"summary": "Summarize",
|
||||
"translate": "Translate"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Smart Translation: Content will be translated to the target language first; content already in the target language will be translated to the alternative language"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C: Copy",
|
||||
"esc_close": "Esc: Close",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Call Chain Window"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Alternative Language",
|
||||
"any": {
|
||||
"language": "Any language"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "总结",
|
||||
"translate": "翻译"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "智能翻译:内容将优先翻译为目标语言;内容已是目标语言的,将翻译为备选语言"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C 复制",
|
||||
"esc_close": "Esc 关闭",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "调用链窗口"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "备用语言",
|
||||
"any": {
|
||||
"language": "任意语言"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "總結",
|
||||
"translate": "翻譯"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "智慧翻譯:內容將優先翻譯為目標語言;內容已是目標語言時,將翻譯為備用語言"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C 複製",
|
||||
"esc_close": "Esc 關閉",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "呼叫鏈視窗"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "備用語言",
|
||||
"any": {
|
||||
"language": "任意語言"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Zusammenfassen",
|
||||
"translate": "Übersetzen"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Intelligente Übersetzung: Inhalt wird bevorzugt in Zielsprache übersetzt; wenn Inhalt bereits in Zielsprache, Übersetzung in Alternativsprache"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C zum Kopieren",
|
||||
"esc_close": "Esc Schließen",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Aufrufkette-Fenster"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Alternative Sprache",
|
||||
"any": {
|
||||
"language": "Beliebige Sprache"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Σύνοψη",
|
||||
"translate": "Μετάφραση"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Έξυπνη μετάφραση: το περιεχόμενο θα μεταφραστεί προτεραιακά στη στόχος γλώσσα· αν το περιεχόμενο είναι ήδη στη στόχος γλώσσα, θα μεταφραστεί στην εναλλακτική γλώσσα"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "Αντιγραφή C",
|
||||
"esc_close": "Esc Κλείσιμο",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Παράθυρο αλυσίδας κλήσης"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Εναλλακτική γλώσσα",
|
||||
"any": {
|
||||
"language": " οποιαδήποτε γλώσσα"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Resumen",
|
||||
"translate": "Traducir"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Traducción inteligente: el contenido se traducirá primero al idioma de destino; si el contenido ya está en el idioma de destino, se traducirá al idioma alternativo"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C Copiar",
|
||||
"esc_close": "Esc Cerrar",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Ventana de cadena de llamadas"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Idioma alternativo",
|
||||
"any": {
|
||||
"language": "cualquier idioma"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Résumé",
|
||||
"translate": "Traduire"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Traduction intelligente : le contenu sera d'abord traduit dans la langue cible ; si le contenu est déjà dans la langue cible, il sera traduit dans la langue secondaire"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C Copier",
|
||||
"esc_close": "Esc Fermer",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Fenêtre de chaîne d'appel"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Langue de secours",
|
||||
"any": {
|
||||
"language": "langue arbitraire"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "要約",
|
||||
"translate": "翻訳"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "スマート翻訳:内容は優先的に目標言語に翻訳されます。すでに目標言語の場合は、備用言語に翻訳されます。"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "Cでコピー",
|
||||
"esc_close": "Escで閉じる",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "呼び出しチェーンウィンドウ"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "備用言語",
|
||||
"any": {
|
||||
"language": "任意の言語"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Resumir",
|
||||
"translate": "Traduzir"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Tradução inteligente: o conteúdo será priorizado para tradução no idioma de destino; se o conteúdo já estiver no idioma de destino, será traduzido para o idioma alternativo"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C Copiar",
|
||||
"esc_close": "Esc Fechar",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Janela de rastreamento"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Idioma alternativo",
|
||||
"any": {
|
||||
"language": "qualquer idioma"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Rezumat",
|
||||
"translate": "Tradu"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Traducere inteligentă: Conținutul va fi tradus mai întâi în limba țintă; conținutul aflat deja în limba țintă va fi tradus în limba alternativă"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C: Copiază",
|
||||
"esc_close": "Esc: Închide",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Fereastră lanț de apelare"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Limbă alternativă",
|
||||
"any": {
|
||||
"language": "Orice limbă"
|
||||
},
|
||||
|
||||
@ -2938,9 +2938,6 @@
|
||||
"summary": "Суммаризировать",
|
||||
"translate": "Перевести"
|
||||
},
|
||||
"translate": {
|
||||
"smart_translate_tips": "Смарт-перевод: содержимое будет переведено на целевой язык; содержимое уже на целевом языке будет переведено на альтернативный язык"
|
||||
},
|
||||
"window": {
|
||||
"c_copy": "C - копировать",
|
||||
"esc_close": "Esc - закрыть",
|
||||
@ -4969,7 +4966,6 @@
|
||||
"traceWindow": "Окно цепочки вызовов"
|
||||
},
|
||||
"translate": {
|
||||
"alter_language": "Альтернативный язык",
|
||||
"any": {
|
||||
"language": "Любой язык"
|
||||
},
|
||||
|
||||
@ -10,13 +10,12 @@ import useTranslate from '@renderer/hooks/useTranslate'
|
||||
import MessageContent from '@renderer/pages/home/Messages/MessageContent'
|
||||
import { getDefaultTopic, getDefaultTranslateAssistant } from '@renderer/services/AssistantService'
|
||||
import { pauseTrace } from '@renderer/services/SpanManagerService'
|
||||
import type { Assistant, Topic, TranslateLanguage, TranslateLanguageCode } from '@renderer/types'
|
||||
import type { Assistant, Topic, TranslateLanguage } from '@renderer/types'
|
||||
import { AssistantMessageStatus } from '@renderer/types/newMessage'
|
||||
import type { ActionItem } from '@renderer/types/selectionTypes'
|
||||
import { abortCompletion } from '@renderer/utils/abortController'
|
||||
import { detectLanguage } from '@renderer/utils/translate'
|
||||
import { Tooltip } from 'antd'
|
||||
import { ArrowRightFromLine, ArrowRightToLine, ChevronDown, CircleHelp, Globe } from 'lucide-react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -46,8 +45,6 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
}
|
||||
})
|
||||
|
||||
const [alterLanguage, setAlterLanguage] = useState<TranslateLanguage>(LanguagesEnum.enUS)
|
||||
|
||||
const [error, setError] = useState('')
|
||||
const [showOriginal, setShowOriginal] = useState(false)
|
||||
const [status, setStatus] = useState<'preparing' | 'streaming' | 'finished'>('preparing')
|
||||
@ -61,27 +58,22 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
const targetLangRef = useRef(targetLanguage)
|
||||
|
||||
// It's called only in initialization.
|
||||
// It will change target/alter language, so fetchResult will be triggered. Be careful!
|
||||
const updateLanguagePair = useCallback(async () => {
|
||||
// It will change target language, so fetchResult will be triggered. Be careful!
|
||||
const updateTargetLanguage = useCallback(async () => {
|
||||
// Only called is when languages loaded.
|
||||
// It ensure we could get right language from getLanguageByLangcode.
|
||||
if (!isLanguagesLoaded) {
|
||||
logger.silly('[updateLanguagePair] Languages are not loaded. Skip.')
|
||||
logger.silly('[updateTargetLanguage] Languages are not loaded. Skip.')
|
||||
return
|
||||
}
|
||||
|
||||
const biDirectionLangPair = await db.settings.get({ id: 'translate:bidirectional:pair' })
|
||||
const savedTargetLang = await db.settings.get({ id: 'translate:target:language' })
|
||||
|
||||
if (biDirectionLangPair && biDirectionLangPair.value[0]) {
|
||||
const targetLang = getLanguageByLangcode(biDirectionLangPair.value[0])
|
||||
if (savedTargetLang && savedTargetLang.value) {
|
||||
const targetLang = getLanguageByLangcode(savedTargetLang.value)
|
||||
setTargetLanguage(targetLang)
|
||||
targetLangRef.current = targetLang
|
||||
}
|
||||
|
||||
if (biDirectionLangPair && biDirectionLangPair.value[1]) {
|
||||
const alterLang = getLanguageByLangcode(biDirectionLangPair.value[1])
|
||||
setAlterLanguage(alterLang)
|
||||
}
|
||||
}, [getLanguageByLangcode, isLanguagesLoaded])
|
||||
|
||||
// Initialize values only once
|
||||
@ -91,7 +83,7 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
return
|
||||
}
|
||||
|
||||
// Only try to initialize when languages loaded, so updateLanguagePair would not fail.
|
||||
// Only try to initialize when languages loaded, so updateTargetLanguage would not fail.
|
||||
if (!isLanguagesLoaded) {
|
||||
logger.silly('[initialize] Languages not loaded. Skip initialization.')
|
||||
return
|
||||
@ -104,10 +96,10 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
}
|
||||
logger.silly('[initialize] Start initialization.')
|
||||
|
||||
// Initialize language pair.
|
||||
// Initialize target language.
|
||||
// It will update targetLangRef, so we could get latest target language in the following code
|
||||
await updateLanguagePair()
|
||||
logger.silly('[initialize] UpdateLanguagePair completed.')
|
||||
await updateTargetLanguage()
|
||||
logger.silly('[initialize] updateTargetLanguage completed.')
|
||||
|
||||
// Initialize assistant
|
||||
const currentAssistant = getDefaultTranslateAssistant(targetLangRef.current, action.selectedText)
|
||||
@ -117,12 +109,12 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
// Initialize topic
|
||||
topicRef.current = getDefaultTopic(currentAssistant.id)
|
||||
setInitialized(true)
|
||||
}, [action.selectedText, initialized, isLanguagesLoaded, updateLanguagePair])
|
||||
}, [action.selectedText, initialized, isLanguagesLoaded, updateTargetLanguage])
|
||||
|
||||
// Try to initialize when:
|
||||
// 1. action.selectedText change (generally will not)
|
||||
// 2. isLanguagesLoaded change (only initialize when languages loaded)
|
||||
// 3. updateLanguagePair change (depend on translateLanguages and isLanguagesLoaded)
|
||||
// 3. updateTargetLanguage change (depend on translateLanguages and isLanguagesLoaded)
|
||||
useEffect(() => {
|
||||
initialize()
|
||||
}, [initialize])
|
||||
@ -146,35 +138,11 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
setError(error.message)
|
||||
}
|
||||
|
||||
let sourceLanguageCode: TranslateLanguageCode
|
||||
|
||||
try {
|
||||
sourceLanguageCode = await detectLanguage(action.selectedText)
|
||||
} catch (err) {
|
||||
onError(err instanceof Error ? err : new Error('An error occurred'))
|
||||
logger.error('Error detecting language:', err as Error)
|
||||
return
|
||||
}
|
||||
|
||||
let translateLang: TranslateLanguage
|
||||
|
||||
if (sourceLanguageCode === UNKNOWN.langCode) {
|
||||
logger.debug('Unknown source language. Just use target language.')
|
||||
translateLang = targetLanguage
|
||||
} else {
|
||||
logger.debug('Detected Language: ', { sourceLanguage: sourceLanguageCode })
|
||||
if (sourceLanguageCode === targetLanguage.langCode) {
|
||||
translateLang = alterLanguage
|
||||
} else {
|
||||
translateLang = targetLanguage
|
||||
}
|
||||
}
|
||||
|
||||
const assistant = getDefaultTranslateAssistant(translateLang, action.selectedText)
|
||||
const assistant = getDefaultTranslateAssistant(targetLanguage, action.selectedText)
|
||||
assistantRef.current = assistant
|
||||
logger.debug('process once')
|
||||
processMessages(assistant, topicRef.current, assistant.content, setAskId, onStream, onFinish, onError)
|
||||
}, [action, targetLanguage, alterLanguage, scrollToBottom, initialized])
|
||||
}, [action, targetLanguage, scrollToBottom, initialized])
|
||||
|
||||
useEffect(() => {
|
||||
fetchResult()
|
||||
@ -213,15 +181,14 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
const isPreparing = status === 'preparing'
|
||||
const isStreaming = status === 'streaming'
|
||||
|
||||
const handleChangeLanguage = (targetLanguage: TranslateLanguage, alterLanguage: TranslateLanguage) => {
|
||||
const handleChangeLanguage = (newTargetLanguage: TranslateLanguage) => {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
setTargetLanguage(targetLanguage)
|
||||
targetLangRef.current = targetLanguage
|
||||
setAlterLanguage(alterLanguage)
|
||||
setTargetLanguage(newTargetLanguage)
|
||||
targetLangRef.current = newTargetLanguage
|
||||
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [targetLanguage.langCode, alterLanguage.langCode] })
|
||||
db.settings.put({ id: 'translate:target:language', value: newTargetLanguage.langCode })
|
||||
}
|
||||
|
||||
const handlePause = () => {
|
||||
@ -244,36 +211,17 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
<>
|
||||
<Container>
|
||||
<MenuContainer>
|
||||
<Tooltip placement="bottom" title={t('translate.any.language')} arrow>
|
||||
<Globe size={16} style={{ flexShrink: 0 }} />
|
||||
</Tooltip>
|
||||
<ArrowRightToLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
||||
<Tooltip placement="bottom" title={t('translate.target_language')} arrow>
|
||||
<LanguageSelect
|
||||
value={targetLanguage.langCode}
|
||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||
style={{ minWidth: 100, maxWidth: 200, flex: 'auto' }}
|
||||
listHeight={160}
|
||||
title={t('translate.target_language')}
|
||||
optionFilterProp="label"
|
||||
onChange={(value) => handleChangeLanguage(getLanguageByLangcode(value), alterLanguage)}
|
||||
onChange={(value) => handleChangeLanguage(getLanguageByLangcode(value))}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
</Tooltip>
|
||||
<ArrowRightFromLine size={16} color="var(--color-text-3)" style={{ margin: '0 2px' }} />
|
||||
<Tooltip placement="bottom" title={t('translate.alter_language')} arrow>
|
||||
<LanguageSelect
|
||||
value={alterLanguage.langCode}
|
||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||
listHeight={160}
|
||||
title={t('translate.alter_language')}
|
||||
optionFilterProp="label"
|
||||
onChange={(value) => handleChangeLanguage(targetLanguage, getLanguageByLangcode(value))}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip placement="bottom" title={t('selection.action.translate.smart_translate_tips')} arrow>
|
||||
<QuestionIcon size={14} style={{ marginLeft: 4 }} />
|
||||
</Tooltip>
|
||||
<Spacer />
|
||||
<OriginalHeader onClick={() => setShowOriginal(!showOriginal)}>
|
||||
<span>
|
||||
@ -393,9 +341,5 @@ const ErrorMsg = styled.div`
|
||||
const Spacer = styled.div`
|
||||
flex-grow: 0.5;
|
||||
`
|
||||
const QuestionIcon = styled(CircleHelp)`
|
||||
cursor: pointer;
|
||||
color: var(--color-text-3);
|
||||
`
|
||||
|
||||
export default ActionTranslate
|
||||
|
||||
Loading…
Reference in New Issue
Block a user