diff --git a/src/renderer/src/pages/translate/TranslatePage.tsx b/src/renderer/src/pages/translate/TranslatePage.tsx index 11db557bf9..353f75bf81 100644 --- a/src/renderer/src/pages/translate/TranslatePage.tsx +++ b/src/renderer/src/pages/translate/TranslatePage.tsx @@ -4,6 +4,7 @@ import CopyIcon from '@renderer/components/Icons/CopyIcon' import { HStack } from '@renderer/components/Layout' import { isEmbeddingModel } from '@renderer/config/models' import { translateLanguageOptions } from '@renderer/config/translate' +import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import db from '@renderer/databases' import { useDefaultModel } from '@renderer/hooks/useAssistant' import { useProviders } from '@renderer/hooks/useProvider' @@ -26,7 +27,6 @@ import { find, isEmpty, sortBy } from 'lodash' import { HelpCircle, Settings2, TriangleAlert } from 'lucide-react' import { FC, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import ReactMarkdown from 'react-markdown' import styled from 'styled-components' let _text = '' @@ -216,9 +216,11 @@ const TranslateSettings: FC<{ const TranslatePage: FC = () => { const { t } = useTranslation() + const { shikiMarkdownIt } = useCodeStyle() const [targetLanguage, setTargetLanguage] = useState(_targetLanguage) const [text, setText] = useState(_text) const [result, setResult] = useState(_result) + const [renderedMarkdown, setRenderedMarkdown] = useState('') const { translateModel, setTranslateModel } = useDefaultModel() const [loading, setLoading] = useState(false) const [copied, setCopied] = useState(false) @@ -379,6 +381,24 @@ const TranslatePage: FC = () => { isEmpty(text) && setResult('') }, [text]) + // Render markdown content when result or enableMarkdown changes + useEffect(() => { + if (enableMarkdown && result) { + let isMounted = true + shikiMarkdownIt(result).then((rendered) => { + if (isMounted) { + setRenderedMarkdown(rendered) + } + }) + return () => { + isMounted = false + } + } else { + setRenderedMarkdown('') + return undefined + } + }, [result, enableMarkdown, shikiMarkdownIt]) + useEffect(() => { runAsyncFunction(async () => { const targetLang = await db.settings.get({ id: 'translate:target:language' }) @@ -607,13 +627,13 @@ const TranslatePage: FC = () => { /> - + {!result ? ( t('translate.output.placeholder') ) : enableMarkdown ? ( - {result} +
) : ( - result +
{result}
)} @@ -703,7 +723,19 @@ const OutputText = styled.div` flex: 1; padding: 5px 16px; overflow-y: auto; - white-space: pre-wrap; + + .plain { + white-space: pre-wrap; + overflow-wrap: break-word; + } + + .markdown { + /* for shiki code block overflow */ + .line * { + white-space: pre-wrap; + overflow-wrap: break-word; + } + } ` const TranslateButton = styled(Button)`` diff --git a/src/renderer/src/utils/shiki.ts b/src/renderer/src/utils/shiki.ts index 0a08a9bd0d..7ad0913ffe 100644 --- a/src/renderer/src/utils/shiki.ts +++ b/src/renderer/src/utils/shiki.ts @@ -149,13 +149,27 @@ export async function getMarkdownIt(theme: string, markdown: string) { const md = await mdInitializer.get() const { fromHighlighter } = await import('@shikijs/markdown-it/core') + let actualTheme = theme + try { + actualTheme = await loadThemeIfNeeded(highlighter, theme) + } catch (error) { + console.debug(`Failed to load theme '${theme}', using 'one-light' as fallback:`, error) + actualTheme = 'one-light' + } + + const themes: Record = { + 'one-light': 'one-light', + 'material-theme-darker': 'material-theme-darker' + } + + if (actualTheme !== 'one-light' && actualTheme !== 'material-theme-darker') { + themes[actualTheme] = actualTheme + } + md.use( fromHighlighter(highlighter, { - themes: { - 'one-light': 'one-light', - 'material-theme-darker': 'material-theme-darker' - }, - defaultColor: theme, + themes, + defaultColor: actualTheme, defaultLanguage: 'json', fallbackLanguage: 'json' })