mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-02-21 18:14:45 +08:00
refactor: simplify translate action by removing dual language selector (#12343)
* feat: enhance ActionTranslate component with language detection and settings dropdown - Added state management for detected and actual target languages. - Implemented a settings dropdown for selecting preferred and alternate languages. - Updated UI to display detected language and integrated new settings for language selection. - Refactored language change handling to improve user experience and maintain state consistency. * refactor(ActionTranslate): reorganize layout and improve component structure - Introduced LeftGroup for better alignment of detected language, target language selector, and settings dropdown. - Removed unnecessary Spacer component to streamline the layout. - Enhanced styling for improved visual consistency and user experience. * fix(ActionTranslate): simplify loading state display for detected language - Removed loading spinner and adjusted the display for the detected language during the detection process to enhance clarity and reduce visual clutter.
This commit is contained in:
parent
f9f550cecd
commit
78c6f97248
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Auto Detect"
|
||||
},
|
||||
"detected_source": "Detected",
|
||||
"detecting": "Detecting...",
|
||||
"empty": "Translation content is empty",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Qwen MT model cannot be used in chat. Please go to the translation page.",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "Source language is different from the set language",
|
||||
"same": "Source and target languages are the same"
|
||||
},
|
||||
"language_settings": "Language Settings",
|
||||
"menu": {
|
||||
"description": "Translate the content of the current input box"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Translation"
|
||||
},
|
||||
"preferred_target": "Preferred Target",
|
||||
"processing": "Translation in progress...",
|
||||
"settings": {
|
||||
"autoCopy": "Copy after translation ",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "自动检测"
|
||||
},
|
||||
"detected_source": "检测到",
|
||||
"detecting": "检测中...",
|
||||
"empty": "翻译内容为空",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Qwen MT 模型不可在对话中使用,请转至翻译页面",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "源语言与设置的语言不同",
|
||||
"same": "源语言和目标语言相同"
|
||||
},
|
||||
"language_settings": "语言设置",
|
||||
"menu": {
|
||||
"description": "对当前输入框内容进行翻译"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "翻译"
|
||||
},
|
||||
"preferred_target": "首选目标",
|
||||
"processing": "翻译中...",
|
||||
"settings": {
|
||||
"autoCopy": "翻译完成后自动复制",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "自動偵測"
|
||||
},
|
||||
"detected_source": "偵測到",
|
||||
"detecting": "偵測中...",
|
||||
"empty": "翻譯內容為空",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Qwen MT 模型無法在對話中使用,請前往翻譯頁面",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "來源語言與設定的語言不同",
|
||||
"same": "來源語言和目標語言相同"
|
||||
},
|
||||
"language_settings": "語言設定",
|
||||
"menu": {
|
||||
"description": "對目前輸入框內容進行翻譯"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "翻譯"
|
||||
},
|
||||
"preferred_target": "首選目標",
|
||||
"processing": "翻譯中...",
|
||||
"settings": {
|
||||
"autoCopy": "翻譯完成後自動複製",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Automatische Erkennung"
|
||||
},
|
||||
"detected_source": "Erfasst",
|
||||
"detecting": "Erkenne...",
|
||||
"empty": "Übersetzungsinhalt leer",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Qwen MT-Modell kann nicht in der Konversation verwendet werden, bitte gehen Sie zur Übersetzungsseite",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "Quellsprache unterscheidet sich von eingestellter Sprache",
|
||||
"same": "Quell- und Zielsprache sind identisch"
|
||||
},
|
||||
"language_settings": "Spracheinstellungen",
|
||||
"menu": {
|
||||
"description": "Inhalt des aktuellen Eingabefelds übersetzen"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Übersetzen"
|
||||
},
|
||||
"preferred_target": "Bevorzugtes Ziel",
|
||||
"processing": "Wird übersetzt...",
|
||||
"settings": {
|
||||
"autoCopy": "Nach Übersetzung automatisch kopieren",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Αυτόματη ανίχνευση"
|
||||
},
|
||||
"detected_source": "Εντοπίστηκε",
|
||||
"detecting": "Ανίχνευση...",
|
||||
"empty": "Το μεταφρασμένο κείμενο είναι κενό",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Τα μοντέλα Qwen MT δεν είναι διαθέσιμα για χρήση σε διαλόγους, παρακαλώ μεταβείτε στη σελίδα μετάφρασης",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "Η γλώσσα πηγής διαφέρει από την οριζόμενη γλώσσα",
|
||||
"same": "Η γλώσσα πηγής και η γλώσσα προορισμού είναι ίδιες"
|
||||
},
|
||||
"language_settings": "Ρυθμίσεις Γλώσσας",
|
||||
"menu": {
|
||||
"description": "Μεταφράστε το περιεχόμενο του τρέχοντος πεδίου εισαγωγής"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Μετάφραση"
|
||||
},
|
||||
"preferred_target": "Προτιμώμενος Στόχος",
|
||||
"processing": "Μεταφράζεται...",
|
||||
"settings": {
|
||||
"autoCopy": "Μετά τη μετάφραση, αντιγράφεται αυτόματα",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Detección automática"
|
||||
},
|
||||
"detected_source": "Detectado",
|
||||
"detecting": "Detectando...",
|
||||
"empty": "El contenido de traducción está vacío",
|
||||
"error": {
|
||||
"chat_qwen_mt": "El modelo Qwen MT no está disponible para uso en conversaciones, por favor vaya a la página de traducción.",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "El idioma de origen es diferente al idioma configurado",
|
||||
"same": "El idioma de origen y el idioma de destino son iguales"
|
||||
},
|
||||
"language_settings": "Configuración de idioma",
|
||||
"menu": {
|
||||
"description": "Traducir el contenido del campo de entrada actual"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Traducción"
|
||||
},
|
||||
"preferred_target": "Objetivo Preferido",
|
||||
"processing": "Traduciendo...",
|
||||
"settings": {
|
||||
"autoCopy": "Copiar automáticamente después de completar la traducción",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Détection automatique"
|
||||
},
|
||||
"detected_source": "Détecté",
|
||||
"detecting": "Détection...",
|
||||
"empty": "Le contenu à traduire est vide",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Les modèles Qwen MT ne peuvent pas être utilisés dans les conversations, veuillez vous rendre sur la page de traduction.",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "La langue source est différente de la langue définie",
|
||||
"same": "La langue source et la langue cible sont identiques"
|
||||
},
|
||||
"language_settings": "Paramètres de langue",
|
||||
"menu": {
|
||||
"description": "Traduire le contenu de la zone de saisie actuelle"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "traduction"
|
||||
},
|
||||
"preferred_target": "Cible préférée",
|
||||
"processing": "en cours de traduction...",
|
||||
"settings": {
|
||||
"autoCopy": "Copié automatiquement après la traduction",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "自動検出"
|
||||
},
|
||||
"detected_source": "検出されました",
|
||||
"detecting": "検出中...",
|
||||
"empty": "翻訳内容が空です",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Qwen MT モデルは対話で使用できません。翻訳ページに移動してください",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "ソース言語が設定された言語と異なります",
|
||||
"same": "ソース言語と目標言語が同じです"
|
||||
},
|
||||
"language_settings": "言語設定",
|
||||
"menu": {
|
||||
"description": "對當前輸入框內容進行翻譯"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "翻訳"
|
||||
},
|
||||
"preferred_target": "優先ターゲット",
|
||||
"processing": "翻訳中...",
|
||||
"settings": {
|
||||
"autoCopy": "翻訳完了後、自動的にコピー",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Detecção automática"
|
||||
},
|
||||
"detected_source": "Detectado",
|
||||
"detecting": "Detectando...",
|
||||
"empty": "O conteúdo de tradução está vazio",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Modelos Qwen MT não estão disponíveis para uso em conversas. Por favor, vá para a página de tradução.",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "O idioma de origem é diferente do idioma definido",
|
||||
"same": "O idioma de origem e o idioma de destino são iguais"
|
||||
},
|
||||
"language_settings": "Configurações de Idioma",
|
||||
"menu": {
|
||||
"description": "Traduzir o conteúdo da caixa de entrada atual"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Tradução"
|
||||
},
|
||||
"preferred_target": "Alvo Preferencial",
|
||||
"processing": "Traduzindo...",
|
||||
"settings": {
|
||||
"autoCopy": "Cópia automática após a tradução",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Detectare automată"
|
||||
},
|
||||
"detected_source": "Detectat",
|
||||
"detecting": "Se detectează...",
|
||||
"empty": "Conținutul traducerii este gol",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Modelul Qwen MT nu poate fi folosit în chat. Te rugăm să mergi la pagina de traducere.",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "Limba sursă este diferită de limba setată",
|
||||
"same": "Limbile sursă și țintă sunt aceleași"
|
||||
},
|
||||
"language_settings": "Setări limbă",
|
||||
"menu": {
|
||||
"description": "Tradu conținutul casetei de intrare curente"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Traducere"
|
||||
},
|
||||
"preferred_target": "Țintă Preferată",
|
||||
"processing": "Traducere în curs...",
|
||||
"settings": {
|
||||
"autoCopy": "Copiază după traducere ",
|
||||
|
||||
@ -5208,6 +5208,8 @@
|
||||
"detected": {
|
||||
"language": "Автоматическое обнаружение"
|
||||
},
|
||||
"detected_source": "Обнаружено",
|
||||
"detecting": "Обнаружение...",
|
||||
"empty": "Содержимое перевода пусто",
|
||||
"error": {
|
||||
"chat_qwen_mt": "Модель Qwen MT недоступна для использования в диалоге, перейдите на страницу перевода",
|
||||
@ -5260,6 +5262,7 @@
|
||||
"not_pair": "Исходный язык отличается от настроенного",
|
||||
"same": "Исходный и целевой языки совпадают"
|
||||
},
|
||||
"language_settings": "Языковые настройки",
|
||||
"menu": {
|
||||
"description": "Перевести содержимое текущего ввода"
|
||||
},
|
||||
@ -5269,6 +5272,7 @@
|
||||
"output": {
|
||||
"placeholder": "Перевод"
|
||||
},
|
||||
"preferred_target": "Предпочтительная цель",
|
||||
"processing": "Перевод в процессе...",
|
||||
"settings": {
|
||||
"autoCopy": "Автоматически копировать после завершения перевода",
|
||||
|
||||
@ -15,12 +15,12 @@ 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 { Dropdown, Tooltip } from 'antd'
|
||||
import { ArrowRight, ChevronDown, CircleHelp, Settings2 } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
import styled, { createGlobalStyle } from 'styled-components'
|
||||
|
||||
import { processMessages } from './ActionUtils'
|
||||
import WindowFooter from './WindowFooter'
|
||||
@ -47,12 +47,15 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
})
|
||||
|
||||
const [alterLanguage, setAlterLanguage] = useState<TranslateLanguage>(LanguagesEnum.enUS)
|
||||
const [detectedLanguage, setDetectedLanguage] = useState<TranslateLanguage | null>(null)
|
||||
const [actualTargetLanguage, setActualTargetLanguage] = useState<TranslateLanguage>(targetLanguage)
|
||||
|
||||
const [error, setError] = useState('')
|
||||
const [showOriginal, setShowOriginal] = useState(false)
|
||||
const [status, setStatus] = useState<'preparing' | 'streaming' | 'finished'>('preparing')
|
||||
const [contentToCopy, setContentToCopy] = useState('')
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
|
||||
// Use useRef for values that shouldn't trigger re-renders
|
||||
const assistantRef = useRef<Assistant | null>(null)
|
||||
@ -156,6 +159,10 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
return
|
||||
}
|
||||
|
||||
// Set detected language for UI display
|
||||
const detectedLang = getLanguageByLangcode(sourceLanguageCode)
|
||||
setDetectedLanguage(detectedLang)
|
||||
|
||||
let translateLang: TranslateLanguage
|
||||
|
||||
if (sourceLanguageCode === UNKNOWN.langCode) {
|
||||
@ -170,11 +177,14 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Set actual target language for UI display
|
||||
setActualTargetLanguage(translateLang)
|
||||
|
||||
const assistant = getDefaultTranslateAssistant(translateLang, 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, alterLanguage, scrollToBottom, initialized, getLanguageByLangcode])
|
||||
|
||||
useEffect(() => {
|
||||
fetchResult()
|
||||
@ -213,16 +223,88 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
const isPreparing = status === 'preparing'
|
||||
const isStreaming = status === 'streaming'
|
||||
|
||||
const handleChangeLanguage = (targetLanguage: TranslateLanguage, alterLanguage: TranslateLanguage) => {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
setTargetLanguage(targetLanguage)
|
||||
targetLangRef.current = targetLanguage
|
||||
setAlterLanguage(alterLanguage)
|
||||
const handleChangeLanguage = useCallback(
|
||||
(newTargetLanguage: TranslateLanguage, newAlterLanguage: TranslateLanguage) => {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
setTargetLanguage(newTargetLanguage)
|
||||
targetLangRef.current = newTargetLanguage
|
||||
setAlterLanguage(newAlterLanguage)
|
||||
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [targetLanguage.langCode, alterLanguage.langCode] })
|
||||
}
|
||||
db.settings.put({
|
||||
id: 'translate:bidirectional:pair',
|
||||
value: [newTargetLanguage.langCode, newAlterLanguage.langCode]
|
||||
})
|
||||
},
|
||||
[initialized]
|
||||
)
|
||||
|
||||
// Handle direct target language change from the main dropdown
|
||||
const handleDirectTargetChange = useCallback(
|
||||
(langCode: TranslateLanguageCode) => {
|
||||
if (!initialized) return
|
||||
const newLang = getLanguageByLangcode(langCode)
|
||||
setActualTargetLanguage(newLang)
|
||||
|
||||
// Update settings: if new target equals current target, keep as is
|
||||
// Otherwise, swap if needed or just update target
|
||||
if (newLang.langCode !== targetLanguage.langCode && newLang.langCode !== alterLanguage.langCode) {
|
||||
// New language is different from both, update target
|
||||
setTargetLanguage(newLang)
|
||||
targetLangRef.current = newLang
|
||||
db.settings.put({ id: 'translate:bidirectional:pair', value: [newLang.langCode, alterLanguage.langCode] })
|
||||
}
|
||||
},
|
||||
[initialized, getLanguageByLangcode, targetLanguage.langCode, alterLanguage.langCode]
|
||||
)
|
||||
|
||||
// Settings dropdown menu items
|
||||
const settingsMenuItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'preferred',
|
||||
label: (
|
||||
<SettingsMenuItem>
|
||||
<SettingsLabel>{t('translate.preferred_target')}</SettingsLabel>
|
||||
<LanguageSelect
|
||||
value={targetLanguage.langCode}
|
||||
style={{ width: '100%' }}
|
||||
listHeight={160}
|
||||
size="small"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(value) => {
|
||||
handleChangeLanguage(getLanguageByLangcode(value), alterLanguage)
|
||||
setSettingsOpen(false)
|
||||
}}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
</SettingsMenuItem>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'alter',
|
||||
label: (
|
||||
<SettingsMenuItem>
|
||||
<SettingsLabel>{t('translate.alter_language')}</SettingsLabel>
|
||||
<LanguageSelect
|
||||
value={alterLanguage.langCode}
|
||||
style={{ width: '100%' }}
|
||||
listHeight={160}
|
||||
size="small"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(value) => {
|
||||
handleChangeLanguage(targetLanguage, getLanguageByLangcode(value))
|
||||
setSettingsOpen(false)
|
||||
}}
|
||||
disabled={isStreaming}
|
||||
/>
|
||||
</SettingsMenuItem>
|
||||
)
|
||||
}
|
||||
],
|
||||
[t, targetLanguage, alterLanguage, isStreaming, getLanguageByLangcode, handleChangeLanguage]
|
||||
)
|
||||
|
||||
const handlePause = () => {
|
||||
// FIXME: It doesn't work because abort signal is not set.
|
||||
@ -242,39 +324,58 @@ const ActionTranslate: FC<Props> = ({ action, scrollToBottom }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsDropdownStyles />
|
||||
<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>
|
||||
<LeftGroup>
|
||||
{/* Detected language display (read-only) */}
|
||||
<DetectedLanguageTag>
|
||||
{isPreparing ? (
|
||||
<span>{t('translate.detecting')}</span>
|
||||
) : (
|
||||
<>
|
||||
<span style={{ marginRight: 4 }}>{detectedLanguage?.emoji || '🌐'}</span>
|
||||
<span>{detectedLanguage?.label() || t('translate.detected_source')}</span>
|
||||
</>
|
||||
)}
|
||||
</DetectedLanguageTag>
|
||||
|
||||
<ArrowRight size={16} color="var(--color-text-3)" style={{ flexShrink: 0 }} />
|
||||
|
||||
{/* Target language selector */}
|
||||
<LanguageSelect
|
||||
value={targetLanguage.langCode}
|
||||
style={{ minWidth: 80, maxWidth: 200, flex: 'auto' }}
|
||||
value={actualTargetLanguage.langCode}
|
||||
style={{ minWidth: 100, maxWidth: 160 }}
|
||||
listHeight={160}
|
||||
title={t('translate.target_language')}
|
||||
size="small"
|
||||
optionFilterProp="label"
|
||||
onChange={(value) => handleChangeLanguage(getLanguageByLangcode(value), alterLanguage)}
|
||||
onChange={handleDirectTargetChange}
|
||||
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 />
|
||||
|
||||
{/* Settings dropdown */}
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: settingsMenuItems,
|
||||
selectable: false,
|
||||
className: 'settings-dropdown-menu'
|
||||
}}
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
open={settingsOpen}
|
||||
onOpenChange={setSettingsOpen}>
|
||||
<Tooltip title={t('translate.language_settings')} placement="bottom">
|
||||
<SettingsButton>
|
||||
<Settings2 size={14} />
|
||||
</SettingsButton>
|
||||
</Tooltip>
|
||||
</Dropdown>
|
||||
|
||||
<Tooltip title={t('selection.action.translate.smart_translate_tips')} placement="bottom">
|
||||
<HelpIcon size={14} />
|
||||
</Tooltip>
|
||||
</LeftGroup>
|
||||
|
||||
<OriginalHeader onClick={() => setShowOriginal(!showOriginal)}>
|
||||
<span>
|
||||
{showOriginal ? t('selection.action.window.original_hide') : t('selection.action.window.original_show')}
|
||||
@ -390,12 +491,72 @@ const ErrorMsg = styled.div`
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
const Spacer = styled.div`
|
||||
flex-grow: 0.5;
|
||||
const LeftGroup = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
`
|
||||
const QuestionIcon = styled(CircleHelp)`
|
||||
|
||||
const DetectedLanguageTag = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
background-color: var(--color-background-soft);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
const SettingsButton = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-3);
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
color: var(--color-text);
|
||||
}
|
||||
`
|
||||
|
||||
const SettingsMenuItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 4px 0;
|
||||
min-width: 180px;
|
||||
cursor: default;
|
||||
`
|
||||
|
||||
const SettingsLabel = styled.span`
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
`
|
||||
|
||||
const HelpIcon = styled(CircleHelp)`
|
||||
cursor: pointer;
|
||||
color: var(--color-text-3);
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
const SettingsDropdownStyles = createGlobalStyle`
|
||||
.settings-dropdown-menu {
|
||||
.ant-dropdown-menu-item {
|
||||
cursor: default !important;
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default ActionTranslate
|
||||
|
||||
Loading…
Reference in New Issue
Block a user