From fe097a937cb6102d6c1525b466ebc5a16d4ed17b Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Thu, 21 Aug 2025 12:48:27 +0800 Subject: [PATCH] feat: search translate history (#9342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(翻译历史): 添加搜索翻译历史UI 在翻译历史页面添加搜索框 * feat(翻译历史): 优化搜索功能并添加延迟渲染 - 将搜索逻辑提取为独立函数并使用useDeferredValue优化性能 - 重构类型命名和状态管理 - 格式化日期显示并移入memo计算 * feat(i18n): 为翻译历史添加搜索框占位文本 * refactor(translate): 移除未使用的InputRef引用和inputRef变量 --- 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 | 73 ++++++++++++++++--- 10 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2af6c5a9ad..eef4f124aa 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -3723,6 +3723,9 @@ "error": { "save": "Failed to save translation history" }, + "search": { + "placeholder": "Search translation history" + }, "title": "Translation History" }, "input": { diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index c70be947da..289914cca6 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -3723,6 +3723,9 @@ "error": { "save": "保存翻訳履歴に失敗しました" }, + "search": { + "placeholder": "翻訳履歴を検索する" + }, "title": "翻訳履歴" }, "input": { diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 22494fb87b..ca219486dd 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -3723,6 +3723,9 @@ "error": { "save": "Не удалось сохранить историю переводов" }, + "search": { + "placeholder": "Поиск истории переводов" + }, "title": "История переводов" }, "input": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index a17b0f2247..ff692f3bbd 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -3723,6 +3723,9 @@ "error": { "save": "保存翻译历史失败" }, + "search": { + "placeholder": "搜索翻译历史" + }, "title": "翻译历史" }, "input": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 03e6f671ef..aef5a324dd 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -3723,6 +3723,9 @@ "error": { "save": "保存翻譯歷史失敗" }, + "search": { + "placeholder": "搜索翻譯歷史" + }, "title": "翻譯歷史" }, "input": { diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index c694511a2f..7e60d79b49 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -3723,6 +3723,9 @@ "error": { "save": "Αποτυχία αποθήκευσης του ιστορικού μεταφράσεων" }, + "search": { + "placeholder": "Αναζήτηση ιστορικού μεταφράσεων" + }, "title": "Ιστορικό μετάφρασης" }, "input": { diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 1a31c3861d..ef4234f334 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -3723,6 +3723,9 @@ "error": { "save": "Error al guardar el historial de traducciones" }, + "search": { + "placeholder": "Historial de búsqueda de traducción" + }, "title": "Historial de traducciones" }, "input": { diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index ed45fd124d..9e2f724425 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -3723,6 +3723,9 @@ "error": { "save": "Échec de la sauvegarde de l'historique des traductions" }, + "search": { + "placeholder": "Rechercher l'historique des traductions" + }, "title": "Historique des traductions" }, "input": { diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 2157fcb51e..9bed40be77 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -3723,6 +3723,9 @@ "error": { "save": "Falha ao guardar o histórico de traduções" }, + "search": { + "placeholder": "Pesquisar histórico de tradução" + }, "title": "Histórico de Tradução" }, "input": { diff --git a/src/renderer/src/pages/translate/TranslateHistory.tsx b/src/renderer/src/pages/translate/TranslateHistory.tsx index 1b65285b72..371237f9eb 100644 --- a/src/renderer/src/pages/translate/TranslateHistory.tsx +++ b/src/renderer/src/pages/translate/TranslateHistory.tsx @@ -1,28 +1,32 @@ import { DeleteOutlined } 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 { TranslateHistory, TranslateLanguage } from '@renderer/types' -import { Button, Drawer, Dropdown, Empty, Flex, Popconfirm } from 'antd' +import { Button, Drawer, Dropdown, Empty, Flex, Input, Popconfirm } from 'antd' import dayjs from 'dayjs' import { useLiveQuery } from 'dexie-react-hooks' import { isEmpty } from 'lodash' -import { FC, useMemo } from 'react' +import { SearchIcon } from 'lucide-react' +import { FC, useCallback, useDeferredValue, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -type DisplayedTranslateHistory = TranslateHistory & { +type DisplayedTranslateHistoryItem = TranslateHistory & { _sourceLanguage: TranslateLanguage _targetLanguage: TranslateLanguage } type TranslateHistoryProps = { isOpen: boolean - onHistoryItemClick: (history: DisplayedTranslateHistory) => void + onHistoryItemClick: (history: DisplayedTranslateHistoryItem) => void onClose: () => void } +// const logger = loggerService.withContext('TranslateHistory') + // px const ITEM_HEIGHT = 140 @@ -30,17 +34,35 @@ const TranslateHistoryList: FC = ({ isOpen, onHistoryItem const { t } = useTranslation() const { getLanguageByLangcode } = useTranslate() const _translateHistory = useLiveQuery(() => db.translate_history.orderBy('createdAt').reverse().toArray(), []) + const [search, setSearch] = useState('') + const [displayedHistory, setDisplayedHistory] = useState([]) - const translateHistory: DisplayedTranslateHistory[] = useMemo(() => { + const translateHistory: DisplayedTranslateHistoryItem[] = useMemo(() => { if (!_translateHistory) return [] return _translateHistory.map((item) => ({ ...item, _sourceLanguage: getLanguageByLangcode(item.sourceLanguage), - _targetLanguage: getLanguageByLangcode(item.targetLanguage) + _targetLanguage: getLanguageByLangcode(item.targetLanguage), + createdAt: dayjs(item.createdAt).format('MM/DD HH:mm') })) }, [_translateHistory, getLanguageByLangcode]) + const searchFilter = useCallback( + (item: DisplayedTranslateHistoryItem) => { + if (isEmpty(search)) return true + const content = `${item._sourceLanguage.label()} ${item._targetLanguage.label()} ${item.sourceText} ${item.targetText} ${item.createdAt}` + return content.includes(search) + }, + [search] + ) + + useEffect(() => { + setDisplayedHistory(translateHistory.filter(searchFilter)) + }, [searchFilter, translateHistory]) + + const deferredHistory = useDeferredValue(displayedHistory) + return ( = ({ isOpen, onHistoryItem } }}> - {translateHistory && translateHistory.length ? ( + {/* Search Bar */} + + + + + } + placeholder={t('translate.history.search.placeholder')} + value={search} + onChange={(e) => { + setSearch(e.target.value) + }} + allowClear + autoFocus + spellCheck={false} + style={{ paddingLeft: 0, height: '3em' }} + variant="borderless" + size="middle" + /> + + + {/* Virtual List */} + {deferredHistory.length > 0 ? ( - ITEM_HEIGHT}> + ITEM_HEIGHT}> {(item) => { return ( = ({ isOpen, onHistoryItem {item._sourceLanguage.label()} → {item._targetLanguage.label()} - {dayjs(item.createdAt).format('MM/DD HH:mm')} + {item.createdAt} {item.sourceText} @@ -192,4 +237,14 @@ const HistoryListItemLanguage = styled.div` color: var(--color-text-3); ` +const IconWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 30px; + width: 30px; + border-radius: 15px; + background-color: var(--color-background-soft); +` + export default TranslateHistoryList