feat: transTag && transOnly

This commit is contained in:
Gabe Yuan
2024-03-13 16:35:40 +08:00
parent 47f9635b10
commit 9d9c0633f0
5 changed files with 172 additions and 58 deletions

View File

@@ -188,7 +188,7 @@ export const I18N = {
en: `Translate Timing`, en: `Translate Timing`,
}, },
mk_disable: { mk_disable: {
zh: `滚动加载(建议`, zh: `滚动加载(推荐`,
en: `Rolling Loading (Suggested)`, en: `Rolling Loading (Suggested)`,
}, },
mk_pageopen: { mk_pageopen: {
@@ -743,6 +743,18 @@ export const I18N = {
zh: `支持用换行或英文逗号“,”分隔多个KEY轮询调用。`, zh: `支持用换行或英文逗号“,”分隔多个KEY轮询调用。`,
en: `Supports multiple KEY polling calls separated by newlines or English commas ",".`, en: `Supports multiple KEY polling calls separated by newlines or English commas ",".`,
}, },
translation_element_tag: {
zh: `译文元素标签`,
en: `Translation Element Tag`,
},
show_only_translations: {
zh: `仅显示译文`,
en: `Show Only Translations`,
},
show_only_translations_help: {
zh: `非完美实现,某些页面可能有样式等问题。`,
en: `It is not a perfect implementation and some pages may have style issues.`,
},
translate_page_title: { translate_page_title: {
zh: `是否同时翻译页面标题`, zh: `是否同时翻译页面标题`,
en: `Translate Page Title`, en: `Translate Page Title`,

View File

@@ -437,7 +437,7 @@ export const DEFAULT_BLACKLIST = [
"https://translate.google.com", "https://translate.google.com",
"https://www.deepl.com/translator", "https://www.deepl.com/translator",
"oapi.dingtalk.com", "oapi.dingtalk.com",
"login.dingtalk.com" "login.dingtalk.com",
]; // 禁用翻译名单 ]; // 禁用翻译名单
export const DEFAULT_SETTING = { export const DEFAULT_SETTING = {
@@ -454,6 +454,8 @@ export const DEFAULT_SETTING = {
detectRemote: false, // 是否使用远程语言检测 detectRemote: false, // 是否使用远程语言检测
contextMenus: true, // 是否添加右键菜单(作废) contextMenus: true, // 是否添加右键菜单(作废)
contextMenuType: 1, // 右键菜单类型(0不显示1简单菜单2多级菜单) contextMenuType: 1, // 右键菜单类型(0不显示1简单菜单2多级菜单)
transTag: "span", // 译文元素标签
transOnly: false, // 是否仅显示译文
transTitle: false, // 是否同时翻译页面标题 transTitle: false, // 是否同时翻译页面标题
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表 subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则 owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则

View File

@@ -372,31 +372,64 @@ export class Translator {
// 解除节点显示监听 // 解除节点显示监听
// this._interseObserver.disconnect(); // this._interseObserver.disconnect();
if ( // 移除键盘监听
!this._setting.mouseKey || window.removeEventListener("keydown", this._handleKeydown);
this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
) { this._tranNodes.forEach((innerHTML, node) => {
// 解除节点显示监听 if (
this._tranNodes.forEach((_, node) => { !this._setting.mouseKey ||
this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
) {
// 解除节点显示监听
this._interseObserver.unobserve(node); this._interseObserver.unobserve(node);
// 移除已插入元素 } else if (this._setting.mouseKey !== OPT_MOUSEKEY_PAGEOPEN) {
node.querySelector(APP_LCNAME)?.remove(); // 移除鼠标悬停监听
});
} else if (this._setting.mouseKey === OPT_MOUSEKEY_PAGEOPEN) {
this._tranNodes.forEach((_, node) => {
node.querySelector(APP_LCNAME)?.remove();
});
} else {
// 移除鼠标悬停监听
window.removeEventListener("keydown", this._handleKeydown);
this._tranNodes.forEach((_, node) => {
// node.style.pointerEvents = "none"; // node.style.pointerEvents = "none";
node.removeEventListener("mouseenter", this._handleMouseover); node.removeEventListener("mouseenter", this._handleMouseover);
node.removeEventListener("mouseleave", this._handleMouseout); node.removeEventListener("mouseleave", this._handleMouseout);
// 移除已插入元素 }
node.querySelector(APP_LCNAME)?.remove();
}); // 移除已插入元素
} node.querySelector(APP_LCNAME)?.remove();
if (innerHTML && this._setting.transOnly) {
node.innerHTML = innerHTML;
}
});
// if (
// !this._setting.mouseKey ||
// this._setting.mouseKey === OPT_MOUSEKEY_DISABLE
// ) {
// // 解除节点显示监听
// this._tranNodes.forEach((innerHTML, node) => {
// this._interseObserver.unobserve(node);
// // 移除已插入元素
// node.querySelector(APP_LCNAME)?.remove();
// if (innerHTML) {
// node.innerHTML = innerHTML;
// }
// });
// } else if (this._setting.mouseKey === OPT_MOUSEKEY_PAGEOPEN) {
// this._tranNodes.forEach((innerHTML, node) => {
// node.querySelector(APP_LCNAME)?.remove();
// if (innerHTML) {
// node.innerHTML = innerHTML;
// }
// });
// } else {
// // 移除鼠标悬停监听
// window.removeEventListener("keydown", this._handleKeydown);
// this._tranNodes.forEach((innerHTML, node) => {
// // node.style.pointerEvents = "none";
// node.removeEventListener("mouseenter", this._handleMouseover);
// node.removeEventListener("mouseleave", this._handleMouseout);
// // 移除已插入元素
// node.querySelector(APP_LCNAME)?.remove();
// if (innerHTML) {
// node.innerHTML = innerHTML;
// }
// });
// }
// 清空节点集合 // 清空节点集合
this._rootNodes.clear(); this._rootNodes.clear();
@@ -422,6 +455,10 @@ export class Translator {
// 已翻译 // 已翻译
if (traEl) { if (traEl) {
if (this._setting.transOnly) {
return;
}
const preText = this._tranNodes.get(el); const preText = this._tranNodes.get(el);
const curText = el.innerText.trim(); const curText = el.innerText.trim();
// const traText = traEl.innerText.trim(); // const traText = traEl.innerText.trim();
@@ -437,7 +474,11 @@ export class Translator {
} }
let q = el.innerText.trim(); let q = el.innerText.trim();
this._tranNodes.set(el, q); if (this._setting.transOnly) {
this._tranNodes.set(el, el.innerHTML);
} else {
this._tranNodes.set(el, q);
}
const keeps = []; const keeps = [];
// 保留元素 // 保留元素
@@ -490,6 +531,9 @@ export class Translator {
traEl = document.createElement(APP_LCNAME); traEl = document.createElement(APP_LCNAME);
traEl.style.visibility = "visible"; traEl.style.visibility = "visible";
if (this._setting.transOnly) {
el.innerHTML = "";
}
el.appendChild(traEl); el.appendChild(traEl);
el.style.cssText += el.style.cssText +=
"-webkit-line-clamp: unset; max-height: none; height: auto;"; "-webkit-line-clamp: unset; max-height: none; height: auto;";

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useMemo } from "react";
import LoadingIcon from "./LoadingIcon"; import LoadingIcon from "./LoadingIcon";
import { import {
OPT_STYLE_LINE, OPT_STYLE_LINE,
@@ -16,7 +16,9 @@ import {
import { useTranslate } from "../../hooks/Translate"; import { useTranslate } from "../../hooks/Translate";
import { styled } from "@mui/material/styles"; import { styled } from "@mui/material/styles";
const LineSpan = styled("span")` const Span = styled("span")``;
const LineSpan = styled(Span)`
opacity: 0.6; opacity: 0.6;
-webkit-opacity: 0.6; -webkit-opacity: 0.6;
text-decoration-line: underline; text-decoration-line: underline;
@@ -35,7 +37,7 @@ const LineSpan = styled("span")`
} }
`; `;
const BlockquoteSpan = styled("span")` const BlockquoteSpan = styled(Span)`
opacity: 0.6; opacity: 0.6;
-webkit-opacity: 0.6; -webkit-opacity: 0.6;
display: block; display: block;
@@ -47,7 +49,7 @@ const BlockquoteSpan = styled("span")`
} }
`; `;
const FuzzySpan = styled("span")` const FuzzySpan = styled(Span)`
filter: blur(0.2em); filter: blur(0.2em);
-webkit-filter: blur(0.2em); -webkit-filter: blur(0.2em);
&:hover { &:hover {
@@ -56,59 +58,63 @@ const FuzzySpan = styled("span")`
} }
`; `;
const HighlightSpan = styled("span")` const HighlightSpan = styled(Span)`
color: #fff; color: #fff;
background-color: ${(props) => props.$bgColor}; background-color: ${(props) => props.$bgColor};
`; `;
const DiySpan = styled("span")` const DiySpan = styled(Span)`
${(props) => props.$diyStyle} ${(props) => props.$diyStyle}
`; `;
function StyledSpan({ textStyle, textDiyStyle, bgColor, children }) { function StyledSpan({ textStyle, textDiyStyle, bgColor, children, ...props }) {
switch (textStyle) { switch (textStyle) {
case OPT_STYLE_LINE: // 下划线 case OPT_STYLE_LINE: // 下划线
return ( return (
<LineSpan $lineStyle="solid" $lineColor={bgColor}> <LineSpan $lineStyle="solid" $lineColor={bgColor} {...props}>
{children} {children}
</LineSpan> </LineSpan>
); );
case OPT_STYLE_DOTLINE: // 点状线 case OPT_STYLE_DOTLINE: // 点状线
return ( return (
<LineSpan $lineStyle="dotted" $lineColor={bgColor}> <LineSpan $lineStyle="dotted" $lineColor={bgColor} {...props}>
{children} {children}
</LineSpan> </LineSpan>
); );
case OPT_STYLE_DASHLINE: // 虚线 case OPT_STYLE_DASHLINE: // 虚线
return ( return (
<LineSpan $lineStyle="dashed" $lineColor={bgColor}> <LineSpan $lineStyle="dashed" $lineColor={bgColor} {...props}>
{children} {children}
</LineSpan> </LineSpan>
); );
case OPT_STYLE_WAVYLINE: // 波浪线 case OPT_STYLE_WAVYLINE: // 波浪线
return ( return (
<LineSpan $lineStyle="wavy" $lineColor={bgColor}> <LineSpan $lineStyle="wavy" $lineColor={bgColor} {...props}>
{children} {children}
</LineSpan> </LineSpan>
); );
case OPT_STYLE_FUZZY: // 模糊 case OPT_STYLE_FUZZY: // 模糊
return <FuzzySpan>{children}</FuzzySpan>; return <FuzzySpan {...props}>{children}</FuzzySpan>;
case OPT_STYLE_HIGHLIGHT: // 高亮 case OPT_STYLE_HIGHLIGHT: // 高亮
return ( return (
<HighlightSpan $bgColor={bgColor || DEFAULT_COLOR}> <HighlightSpan $bgColor={bgColor || DEFAULT_COLOR} {...props}>
{children} {children}
</HighlightSpan> </HighlightSpan>
); );
case OPT_STYLE_BLOCKQUOTE: // 引用 case OPT_STYLE_BLOCKQUOTE: // 引用
return ( return (
<BlockquoteSpan $lineColor={bgColor || DEFAULT_COLOR}> <BlockquoteSpan $lineColor={bgColor || DEFAULT_COLOR} {...props}>
{children} {children}
</BlockquoteSpan> </BlockquoteSpan>
); );
case OPT_STYLE_DIY: // 自定义 case OPT_STYLE_DIY: // 自定义
return <DiySpan $diyStyle={textDiyStyle}>{children}</DiySpan>; return (
<DiySpan $diyStyle={textDiyStyle} {...props}>
{children}
</DiySpan>
);
default: default:
return <span>{children}</span>; return <Span {...props}>{children}</Span>;
} }
} }
@@ -117,7 +123,11 @@ export default function Content({ q, keeps, translator }) {
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting); const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
const { textStyle, bgColor = "", textDiyStyle = "" } = rule; const { textStyle, bgColor = "", textDiyStyle = "" } = rule;
const { newlineLength = TRANS_NEWLINE_LENGTH } = translator.setting; const {
newlineLength = TRANS_NEWLINE_LENGTH,
transTag = "span",
transOnly = false,
} = translator.setting;
const handleKissEvent = (e) => { const handleKissEvent = (e) => {
const { action, args } = e.detail; const { action, args } = e.detail;
@@ -136,10 +146,27 @@ export default function Content({ q, keeps, translator }) {
}; };
}, [translator.eventName]); }, [translator.eventName]);
const gap = useMemo(() => {
if (transOnly) {
return "";
}
return q.length >= newlineLength ? <br /> : " ";
}, [q, transOnly, newlineLength]);
const styles = useMemo(
() => ({
textStyle,
textDiyStyle,
bgColor,
as: transTag,
}),
[textStyle, textDiyStyle, bgColor, transTag]
);
if (loading) { if (loading) {
return ( return (
<> <>
{q.length >= newlineLength ? <br /> : " "} {gap}
<LoadingIcon /> <LoadingIcon />
</> </>
); );
@@ -149,24 +176,24 @@ export default function Content({ q, keeps, translator }) {
return; return;
} }
if (keeps.length > 0) {
return (
<>
{gap}
<StyledSpan
{...styles}
dangerouslySetInnerHTML={{
__html: text.replace(/\[(\d+)\]/g, (_, p) => keeps[parseInt(p)]),
}}
/>
</>
);
}
return ( return (
<> <>
{q.length >= newlineLength ? <br /> : " "} {gap}
<StyledSpan <StyledSpan {...styles}>{text}</StyledSpan>
textStyle={textStyle}
textDiyStyle={textDiyStyle}
bgColor={bgColor}
>
{keeps.length > 0 ? (
<span
dangerouslySetInnerHTML={{
__html: text.replace(/\[(\d+)\]/g, (_, p) => keeps[parseInt(p)]),
}}
/>
) : (
text
)}
</StyledSpan>
</> </>
); );
} }

View File

@@ -97,6 +97,8 @@ export default function Settings() {
mouseKey = OPT_MOUSEKEY_DISABLE, mouseKey = OPT_MOUSEKEY_DISABLE,
detectRemote = false, detectRemote = false,
contextMenuType = 1, contextMenuType = 1,
transTag = "span",
transOnly = false,
transTitle = false, transTitle = false,
touchTranslate = 2, touchTranslate = 2,
blacklist = DEFAULT_BLACKLIST.join(",\n"), blacklist = DEFAULT_BLACKLIST.join(",\n"),
@@ -184,6 +186,33 @@ export default function Settings() {
</Select> </Select>
</FormControl> </FormControl>
<FormControl size="small">
<InputLabel>{i18n("translation_element_tag")}</InputLabel>
<Select
name="transTag"
value={transTag}
label={i18n("translation_element_tag")}
onChange={handleChange}
>
<MenuItem value={"span"}>{`<span>`}</MenuItem>
<MenuItem value={"font"}>{`<font>`}</MenuItem>
</Select>
</FormControl>
<FormControl size="small">
<InputLabel>{i18n("show_only_translations")}</InputLabel>
<Select
name="transOnly"
value={transOnly}
label={i18n("show_only_translations")}
onChange={handleChange}
>
<MenuItem value={false}>{i18n("disable")}</MenuItem>
<MenuItem value={true}>{i18n("enable")}</MenuItem>
</Select>
<FormHelperText>{i18n("show_only_translations_help")}</FormHelperText>
</FormControl>
<FormControl size="small"> <FormControl size="small">
<InputLabel>{i18n("translate_page_title")}</InputLabel> <InputLabel>{i18n("translate_page_title")}</InputLabel>
<Select <Select