From 8925d7d546e3061f2918987cde63e7dd57c82664 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Mon, 25 Aug 2025 00:10:41 +0800 Subject: [PATCH] feat: translate history star (#9433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(types): 为翻译历史记录添加收藏状态字段 * feat(翻译服务): 添加更新翻译历史记录功能 新增updateTranslateHistory方法用于更新翻译历史记录,支持修改原文、译文、语言及收藏状态 * refactor(TranslateService): 简化更新翻译历史记录的参数结构 * fix(TranslateService): 添加删除翻译历史的错误处理 捕获删除翻译历史时的异常并记录日志,避免静默失败 * feat(翻译历史): 添加收藏功能并优化删除操作 - 新增翻译历史项的收藏功能 - 将删除操作从右键菜单移至显式按钮 - 增加删除失败的国际化提示 - 调整列表项高度以适应新功能 * feat(翻译历史): 添加收藏筛选功能 新增显示已收藏翻译历史的功能,用户可以通过点击星标按钮切换筛选状态 * feat(i18n): 添加翻译历史删除失败的错误消息 为翻译历史功能添加删除操作失败时的错误提示消息,支持多语言显示 * fix(翻译历史): 将删除按钮文本改为"删除翻译历史"并添加确认弹窗 修改删除按钮文本使其更明确,并添加确认弹窗防止误操作 * style(TabContainer): 移除多余的空行以保持代码整洁 --- src/renderer/src/i18n/locales/en-us.json | 3 +- src/renderer/src/i18n/locales/ja-jp.json | 3 +- src/renderer/src/i18n/locales/ru-ru.json | 3 +- src/renderer/src/i18n/locales/zh-cn.json | 3 +- src/renderer/src/i18n/locales/zh-tw.json | 3 +- src/renderer/src/i18n/translate/el-gr.json | 3 +- src/renderer/src/i18n/translate/es-es.json | 3 +- src/renderer/src/i18n/translate/fr-fr.json | 3 +- src/renderer/src/i18n/translate/pt-pt.json | 3 +- .../src/pages/translate/TranslateHistory.tsx | 140 +++++++++++++----- src/renderer/src/services/TranslateService.ts | 26 +++- src/renderer/src/types/index.ts | 2 + 12 files changed, 151 insertions(+), 44 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index faeebeb541..428b7693e0 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -3734,9 +3734,10 @@ "history": { "clear": "Clear History", "clear_description": "Clear history will delete all translation history, continue?", - "delete": "Delete", + "delete": "Delete translation history", "empty": "No translation history", "error": { + "delete": "Deletion failed", "save": "Failed to save translation history" }, "search": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 42dbead394..5db622e895 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -3734,9 +3734,10 @@ "history": { "clear": "履歴をクリア", "clear_description": "履歴をクリアすると、すべての翻訳履歴が削除されます。続行しますか?", - "delete": "削除", + "delete": "翻訳履歴を削除する", "empty": "翻訳履歴がありません", "error": { + "delete": "削除に失敗しました", "save": "保存翻訳履歴に失敗しました" }, "search": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 28880dbf96..2a04e0ccef 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -3734,9 +3734,10 @@ "history": { "clear": "Очистить историю", "clear_description": "Очистка истории удалит все записи переводов. Продолжить?", - "delete": "Удалить", + "delete": "Удалить историю переводов", "empty": "История переводов отсутствует", "error": { + "delete": "Удаление не удалось", "save": "Не удалось сохранить историю переводов" }, "search": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 8cd92d2649..9db6cf963b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -3734,9 +3734,10 @@ "history": { "clear": "清空历史", "clear_description": "清空历史将删除所有翻译历史记录,是否继续?", - "delete": "删除", + "delete": "删除翻译历史", "empty": "暂无翻译历史", "error": { + "delete": "删除失败", "save": "保存翻译历史失败" }, "search": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index a193d23e07..04ce4ed230 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -3734,9 +3734,10 @@ "history": { "clear": "清空歷史", "clear_description": "清空歷史將刪除所有翻譯歷史記錄,是否繼續?", - "delete": "刪除", + "delete": "刪除翻譯歷史", "empty": "翻譯歷史為空", "error": { + "delete": "删除失败", "save": "保存翻譯歷史失敗" }, "search": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 91d2fc46f0..e095cbf0da 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -3734,9 +3734,10 @@ "history": { "clear": "Καθαρισμός ιστορικού", "clear_description": "Η διαγραφή του ιστορικού θα διαγράψει όλα τα απομνημονεύματα μετάφρασης. Θέλετε να συνεχίσετε;", - "delete": "Διαγραφή", + "delete": "Διαγραφή του ιστορικού μετάφρασης", "empty": "δεν υπάρχουν απομνημονεύματα μετάφρασης", "error": { + "delete": "Αποτυχία διαγραφής", "save": "Αποτυχία αποθήκευσης του ιστορικού μεταφράσεων" }, "search": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 99f7637098..2ee02a11ec 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -3734,9 +3734,10 @@ "history": { "clear": "Borrar historial", "clear_description": "Borrar el historial eliminará todos los registros de traducciones, ¿desea continuar?", - "delete": "Eliminar", + "delete": "Eliminar historial de traducción", "empty": "Sin historial de traducciones por el momento", "error": { + "delete": "Eliminación fallida", "save": "Error al guardar el historial de traducciones" }, "search": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index d8059651f3..20364b838f 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -3734,9 +3734,10 @@ "history": { "clear": "Effacer l'historique", "clear_description": "L'effacement de l'historique supprimera toutes les entrées d'historique de traduction, voulez-vous continuer ?", - "delete": "Supprimer", + "delete": "Supprimer l'historique des traductions", "empty": "Aucun historique de traduction pour le moment", "error": { + "delete": "Échec de la suppression", "save": "Échec de la sauvegarde de l'historique des traductions" }, "search": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index c10a01f78a..5d54ecc50a 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -3734,9 +3734,10 @@ "history": { "clear": "Limpar Histórico", "clear_description": "Limpar histórico irá deletar todos os registros de tradução. Deseja continuar?", - "delete": "Excluir", + "delete": "Apagar histórico de traduções", "empty": "Nenhum histórico de tradução disponível", "error": { + "delete": "Falha ao excluir", "save": "Falha ao guardar o histórico de traduções" }, "search": { diff --git a/src/renderer/src/pages/translate/TranslateHistory.tsx b/src/renderer/src/pages/translate/TranslateHistory.tsx index 371237f9eb..5930fa9e64 100644 --- a/src/renderer/src/pages/translate/TranslateHistory.tsx +++ b/src/renderer/src/pages/translate/TranslateHistory.tsx @@ -1,11 +1,11 @@ -import { DeleteOutlined } from '@ant-design/icons' +import { DeleteOutlined, StarFilled, StarOutlined } from '@ant-design/icons' import { HStack } from '@renderer/components/Layout' import { DynamicVirtualList } from '@renderer/components/VirtualList' import db from '@renderer/databases' import useTranslate from '@renderer/hooks/useTranslate' -import { clearHistory, deleteHistory } from '@renderer/services/TranslateService' +import { clearHistory, deleteHistory, updateTranslateHistory } from '@renderer/services/TranslateService' import { TranslateHistory, TranslateLanguage } from '@renderer/types' -import { Button, Drawer, Dropdown, Empty, Flex, Input, Popconfirm } from 'antd' +import { Button, Drawer, Empty, Flex, Input, Popconfirm } from 'antd' import dayjs from 'dayjs' import { useLiveQuery } from 'dexie-react-hooks' import { isEmpty } from 'lodash' @@ -28,7 +28,7 @@ type TranslateHistoryProps = { // const logger = loggerService.withContext('TranslateHistory') // px -const ITEM_HEIGHT = 140 +const ITEM_HEIGHT = 160 const TranslateHistoryList: FC = ({ isOpen, onHistoryItemClick, onClose }) => { const { t } = useTranslation() @@ -36,6 +36,7 @@ const TranslateHistoryList: FC = ({ isOpen, onHistoryItem const _translateHistory = useLiveQuery(() => db.translate_history.orderBy('createdAt').reverse().toArray(), []) const [search, setSearch] = useState('') const [displayedHistory, setDisplayedHistory] = useState([]) + const [showStared, setShowStared] = useState(false) const translateHistory: DisplayedTranslateHistoryItem[] = useMemo(() => { if (!_translateHistory) return [] @@ -57,15 +58,64 @@ const TranslateHistoryList: FC = ({ isOpen, onHistoryItem [search] ) + const starFilter = useMemo( + () => (showStared ? (item: DisplayedTranslateHistoryItem) => !!item.star : () => true), + [showStared] + ) + + const finalFilter = useCallback( + (item: DisplayedTranslateHistoryItem) => searchFilter(item) && starFilter(item), + [searchFilter, starFilter] + ) + + const handleStar = useCallback( + (id: string) => { + const origin = translateHistory.find((item) => item.id === id) + if (!origin) { + return + } + updateTranslateHistory(id, { star: !origin.star }) + }, + [translateHistory] + ) + + const handleDelete = useCallback( + (id: string) => { + try { + deleteHistory(id) + } catch (e) { + window.message.error(t('translate.history.error.delete')) + } + }, + [t] + ) + useEffect(() => { - setDisplayedHistory(translateHistory.filter(searchFilter)) - }, [searchFilter, translateHistory]) + setDisplayedHistory(translateHistory.filter(finalFilter)) + }, [finalFilter, translateHistory]) + + const Title = () => { + return ( + + {t('translate.history.title')} +