Compare commits

..

43 Commits

Author SHA1 Message Date
Gabe Yuan
4b3e91fa84 v1.8.1 2024-02-02 15:45:33 +08:00
Gabe Yuan
0973a0b60e fix: some js syntax 2024-02-02 15:44:44 +08:00
Gabe Yuan
de5f61126d fix: terms hepler text 2024-02-02 12:35:56 +08:00
Gabe Yuan
0c20ca761f fix: update toggle_translate CN text 2024-02-02 12:22:08 +08:00
Gabe Yuan
4bce56207e fix: optimize terms function 2024-02-02 12:10:27 +08:00
Gabe Yuan
dca54e0033 feat: setting: translate page title 2024-02-02 11:20:39 +08:00
Gabe Yuan
309646bf1d feat: setting: translate page title 2024-02-02 11:13:41 +08:00
Gabe Yuan
18b9961b39 fix: try add context menux on startup 2024-02-02 10:49:15 +08:00
Gabe Yuan
1e51ff17f2 v1.8.0 2024-01-22 13:19:37 +08:00
Gabe Yuan
63b5f707e2 fix: update ui when shortcut changed 2024-01-22 13:11:02 +08:00
Gabe Yuan
30efb6ee7a fix: title translate 2024-01-19 21:03:51 +08:00
Gabe Yuan
61b017618a feat: supported translation all when page opened 2024-01-19 17:55:18 +08:00
Gabe Yuan
1e0397adc9 feat: translate page title 2024-01-19 17:18:05 +08:00
Gabe Yuan
48b34bf95f fix: save new rule with hostname 2024-01-19 16:13:46 +08:00
Gabe Yuan
d5fc69e210 feat: support custom terms 2024-01-19 16:02:53 +08:00
Gabe Yuan
59f9dd697f fix: update baidu translate api 2024-01-18 15:26:37 +08:00
Gabe Yuan
c9d72323f1 keep selector support for sub-element 2024-01-12 16:04:34 +08:00
Gabe Yuan
e87f7f3abe fix: help text 2024-01-12 09:42:49 +08:00
Gabe Yuan
82ebbcb6d6 v1.7.16 2024-01-04 15:55:28 +08:00
Gabe Yuan
2db11070c5 fix: move clear_cache button to bottom of popup 2024-01-04 15:41:20 +08:00
Gabe Yuan
5efd2517e7 fix: tranbox shortcut in usserscript 2024-01-04 12:18:36 +08:00
Gabe Yuan
c0ba654678 fix: remove bgcolor input from popup 2024-01-04 10:49:44 +08:00
Gabe Yuan
546a5a549b fix: comment text 2024-01-04 10:39:40 +08:00
Gabe Yuan
cbf02c34e3 fix: remove position limit for tranbtn 2024-01-04 10:34:12 +08:00
Gabe Yuan
74a7258f10 fix: optimize key pick 2024-01-04 09:40:03 +08:00
Gabe Yuan
1006c044bc fix: update readme 2024-01-03 15:48:34 +08:00
Gabe
ef4ea719f3 fix: Update README.md 2024-01-03 15:47:24 +08:00
Gabe
8b34afe69f fix: Update README.md 2024-01-03 15:25:40 +08:00
Gabe Yuan
01292af298 feat: move open_tranbox shortcurt to browser commands 2024-01-03 13:10:02 +08:00
Gabe Yuan
cff8b2fe39 feat: move open_tranbox shortcurt to browser commands 2024-01-03 11:59:41 +08:00
Gabe Yuan
2cb20b5cc0 fix: update rules 2024-01-03 10:43:02 +08:00
Gabe Yuan
8f2aed18fe fix: contextMenus created on page and selection 2024-01-03 10:32:11 +08:00
Gabe Yuan
d85831cc9a fix: keep the translated image size unchanged 2024-01-03 10:10:54 +08:00
Gabe Yuan
55dc3a5556 feat: keep unchanged elements 2024-01-02 17:57:04 +08:00
Gabe Yuan
591afe08bd feat: keep unchanged elements 2024-01-02 17:55:59 +08:00
Gabe Yuan
748f2002ab fix: run webfix before translate 2023-12-27 15:44:02 +08:00
Gabe Yuan
d2d18a2384 fix: instagram input translate: addEventListener keyup 2023-12-27 11:25:53 +08:00
Gabe Yuan
35f4fa6aa7 fix: register menu command when hide fab button 2023-12-26 10:08:36 +08:00
Gabe Yuan
66fc2d22ed feat: toto: selection translation on mobile support 2023-12-25 17:25:00 +08:00
Gabe Yuan
16cf9ee1ed feat: toto: selection translation on mobile support 2023-12-25 17:21:59 +08:00
Gabe Yuan
d9d97bf14c fix: selection button position 2023-12-25 14:42:13 +08:00
Gabe Yuan
dc811bd3c7 feat: selection translation on mobile support 2023-12-25 11:50:30 +08:00
Gabe Yuan
b939d1849a feat: multi key calling support 2023-12-22 11:35:46 +08:00
38 changed files with 828 additions and 250 deletions

2
.env
View File

@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
REACT_APP_NAME=KISS Translator
REACT_APP_NAME_CN=简约翻译
REACT_APP_VERSION=1.7.15
REACT_APP_VERSION=1.8.1
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator

View File

@@ -26,6 +26,7 @@ A simple, open source [bilingual translation extension & Greasemonkey script](ht
- [x] WebDAV
- [x] Custom translation rules
- [x] Rule subscription/rule sharing
- [x] Customized terminology
- [x] Custom translation style
- [x] Custom shortcut keys
- `Alt+Q` Toggle Translation
@@ -84,3 +85,7 @@ pnpm build
## Discussion
- Join [Telegram Group](https://t.me/+RRCu_4oNwrM2NmFl)
## Appreciate
![appreciate](https://github.com/fishjar/kiss-translator/assets/1157624/ebaecabe-2934-4172-8085-af236f5ee399)

View File

@@ -26,9 +26,10 @@
- [x] WebDAV
- [x] 自定义翻译规则
- [x] 规则订阅/规则分享
- [x] 自定义专业术语
- [x] 自定义译文样式
- [x] 自定义快捷键
- `Alt+Q` 启翻译
- `Alt+Q`翻译
- `Alt+C` 切换样式
- `Alt+K` 打开设置弹窗
- `Alt+S` 打开翻译弹窗/翻译选中文字
@@ -84,3 +85,7 @@ pnpm build
## 交流
- 加入 [Telegram 群](https://t.me/+RRCu_4oNwrM2NmFl)
## 赞赏
![appreciate](https://github.com/fishjar/kiss-translator/assets/1157624/ebaecabe-2934-4172-8085-af236f5ee399)

View File

@@ -1,7 +1,7 @@
{
"name": "kiss-translator",
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
"version": "1.7.15",
"version": "1.8.1",
"author": "Gabe<yugang2002@gmail.com>",
"private": true,
"dependencies": {

View File

@@ -6,15 +6,15 @@
"message": "A simple bilingual translation extension & Greasemonkey script"
},
"toggle_translate": {
"message": "Toggle Translate (Alt+Q)"
"message": "Toggle Translate"
},
"toggle_style": {
"message": "Toggle Style (Alt+C)"
"message": "Toggle Style"
},
"open_options": {
"message": "Open Options (Alt+O)"
"message": "Open Options"
},
"open_tranbox": {
"message": "Translate Popup/Selected (Alt+S)"
"message": "Translate Popup/Selected"
}
}

View File

@@ -6,15 +6,15 @@
"message": "一个简约的双语对照翻译扩展 & 油猴脚本"
},
"toggle_translate": {
"message": "开启翻译 (Alt+Q)"
"message": "启停翻译"
},
"toggle_style": {
"message": "切换样式 (Alt+C)"
"message": "切换样式"
},
"open_options": {
"message": "打开设置 (Alt+O)"
"message": "打开设置"
},
"open_tranbox": {
"message": "翻译弹窗/选中文字 (Alt+S)"
"message": "翻译弹窗/选中文字"
}
}

View File

@@ -64,6 +64,45 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<p>You need to enable <code>JavaScript</code> to run <span>this app.</span></p>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<div id="content">
<p>You need to enable JavaScript to run <span>this app.</span></p>
The <span>embargo</span> has just lifted to confirm that AmpereOne is

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.7.15",
"version": "1.8.1",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",
@@ -28,6 +28,12 @@
},
"description": "__MSG_toggle_translate__"
},
"openTranbox": {
"suggested_key": {
"default": "Alt+S"
},
"description": "__MSG_open_tranbox__"
},
"toggleStyle": {
"suggested_key": {
"default": "Alt+C"
@@ -35,9 +41,6 @@
"description": "__MSG_toggle_style__"
},
"openOptions": {
"suggested_key": {
"default": "Alt+O"
},
"description": "__MSG_open_options__"
}
},

View File

@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.7.15",
"version": "1.8.1",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",
@@ -29,6 +29,12 @@
},
"description": "__MSG_toggle_translate__"
},
"openTranbox": {
"suggested_key": {
"default": "Alt+S"
},
"description": "__MSG_open_tranbox__"
},
"toggleStyle": {
"suggested_key": {
"default": "Alt+C"
@@ -36,9 +42,6 @@
"description": "__MSG_toggle_style__"
},
"openOptions": {
"suggested_key": {
"default": "Alt+O"
},
"description": "__MSG_open_options__"
}
},

View File

@@ -1,6 +1,10 @@
import queryString from "query-string";
import { getBdauth, setBdauth } from "../libs/storage";
import { URL_BAIDU_WEB, URL_BAIDU_TRAN } from "../config";
import {
URL_BAIDU_WEB,
URL_BAIDU_TRANSAPI_V2,
URL_BAIDU_TRANSAPI,
} from "../config";
import { fetchApi } from "../libs/fetch";
/* eslint-disable */
@@ -203,7 +207,12 @@ const _bdAuth = () => {
const bdAuth = _bdAuth();
export const genBaidu = async ({ text, from, to }) => {
/**
* 失效作废
* @param {*} param0
* @returns
*/
export const genBaiduV2 = async ({ text, from, to }) => {
const { token, gtk } = await bdAuth();
const sign = getSign(text, gtk);
const data = {
@@ -217,7 +226,7 @@ export const genBaidu = async ({ text, from, to }) => {
ts: Date.now(),
};
const input = `${URL_BAIDU_TRAN}?from=${from}&to=${to}`;
const input = `${URL_BAIDU_TRANSAPI_V2}?from=${from}&to=${to}`;
const init = {
headers: {
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
@@ -228,3 +237,22 @@ export const genBaidu = async ({ text, from, to }) => {
return [input, init];
};
export const genBaidu = async ({ text, from, to }) => {
const data = {
from,
to,
query: text,
source: "txt",
};
const init = {
headers: {
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
method: "POST",
body: queryString.stringify(data),
};
return [URL_BAIDU_TRANSAPI, init];
};

View File

@@ -125,11 +125,14 @@ export const apiTranslate = async ({
return [trText, isSame];
}
// 版本号一/二位升级,旧缓存失效
const [v1, v2] = process.env.REACT_APP_VERSION.split(".");
const cacheOpts = {
translator,
text,
fromLang,
toLang,
version: [v1, v2].join("."),
};
const transOpts = {
@@ -155,7 +158,9 @@ export const apiTranslate = async ({
isSame = to === res.src;
break;
case OPT_TRANS_MICROSOFT:
trText = res[0].translations.map((item) => item.text).join(" ");
trText = res
.map((item) => item.translations.map((item) => item.text).join(" "))
.join(" ");
isSame = text === trText;
break;
case OPT_TRANS_DEEPL:
@@ -171,19 +176,28 @@ export const apiTranslate = async ({
isSame = to === res.source_lang;
break;
case OPT_TRANS_BAIDU:
trText = res.trans_result?.data.map((item) => item.dst).join(" ");
isSame = res.trans_result?.to === res.trans_result?.from;
// trText = res.trans_result?.data.map((item) => item.dst).join(" ");
// isSame = res.trans_result?.to === res.trans_result?.from;
if (res.type === 1) {
trText = Object.keys(JSON.parse(res.result).content[0].mean[0].cont)[0];
isSame = to === res.from;
} else if (res.type === 2) {
trText = res.data.map((item) => item.dst).join(" ");
isSame = to === res.from;
}
break;
case OPT_TRANS_TENCENT:
trText = res.auto_translation;
isSame = text === trText;
break;
case OPT_TRANS_OPENAI:
trText = res?.choices?.[0].message.content;
trText = res?.choices?.map((item) => item.message.content).join(" ");
isSame = text === trText;
break;
case OPT_TRANS_GEMINI:
trText = res?.candidates?.[0].content.parts[0].text;
trText = res?.candidates
?.map((item) => item.content.parts.map((item) => item.text).join(" "))
.join(" ");
isSame = text === trText;
break;
case OPT_TRANS_CLOUDFLAREAI:

View File

@@ -9,6 +9,7 @@ import {
MSG_TRANS_TOGGLE_STYLE,
MSG_OPEN_TRANBOX,
MSG_CONTEXT_MENUS,
MSG_COMMAND_SHORTCUTS,
CMD_TOGGLE_TRANSLATE,
CMD_TOGGLE_STYLE,
CMD_OPEN_OPTIONS,
@@ -31,27 +32,27 @@ function addContextMenus() {
browser.contextMenus.create({
id: CMD_TOGGLE_TRANSLATE,
title: browser.i18n.getMessage("toggle_translate"),
contexts: ["all"],
contexts: ["page", "selection"],
});
browser.contextMenus.create({
id: CMD_TOGGLE_STYLE,
title: browser.i18n.getMessage("toggle_style"),
contexts: ["all"],
contexts: ["page", "selection"],
});
browser.contextMenus.create({
id: CMD_OPEN_TRANBOX,
title: browser.i18n.getMessage("open_tranbox"),
contexts: ["all"],
contexts: ["page", "selection"],
});
browser.contextMenus.create({
id: "options_separator",
type: "separator",
contexts: ["all"],
contexts: ["page", "selection"],
});
browser.contextMenus.create({
id: CMD_OPEN_OPTIONS,
title: browser.i18n.getMessage("open_options"),
contexts: ["all"],
contexts: ["page", "selection"],
});
}
@@ -91,7 +92,9 @@ browser.runtime.onStartup.addListener(async () => {
}
// 右键菜单
if (!contextMenus) {
if (contextMenus) {
addContextMenus();
} else {
removeContextMenus();
}
@@ -138,6 +141,16 @@ browser.runtime.onMessage.addListener(
removeContextMenus();
}
break;
case MSG_COMMAND_SHORTCUTS:
browser.commands
.getAll()
.then((commands) => {
sendResponse({ data: commands });
})
.catch((error) => {
sendResponse({ error: error.message });
});
break;
default:
sendResponse({ error: `message action is unavailable: ${action}` });
}
@@ -154,6 +167,9 @@ browser.commands.onCommand.addListener((command) => {
case CMD_TOGGLE_TRANSLATE:
sendTabMsg(MSG_TRANS_TOGGLE);
break;
case CMD_OPEN_TRANBOX:
sendTabMsg(MSG_OPEN_TRANBOX);
break;
case CMD_TOGGLE_STYLE:
sendTabMsg(MSG_TRANS_TOGGLE_STYLE);
break;

View File

@@ -20,7 +20,7 @@ import { touchTapListener } from "./libs/touch";
import { debounce, genEventName } from "./libs/utils";
import { handlePing, injectScript } from "./libs/gm";
import { browser } from "./libs/browser";
import { runWebfix } from "./libs/webfix";
import { matchFixer } from "./libs/webfix";
import { matchRule } from "./libs/rules";
import { trySyncAllSubRules } from "./libs/subRules";
import { isInBlacklist } from "./libs/blacklist";
@@ -112,10 +112,6 @@ function runIframe(setting) {
*/
async function showFab(translator) {
const fab = await getFabWithDefault();
if (fab.isHide) {
return;
}
const $action = document.createElement("div");
$action.setAttribute("id", APP_LCNAME);
document.body.parentElement.appendChild($action);
@@ -256,11 +252,11 @@ export async function run(isUserscript = false) {
}
// 不规范网页修复
await runWebfix(setting);
const fixerSetting = await matchFixer(href, setting);
// 翻译网页
const rule = await matchRule(href, setting);
const translator = new Translator(rule, setting);
const translator = new Translator(rule, setting, fixerSetting);
// 监听消息
windowListener(rule);

View File

@@ -112,8 +112,8 @@ export const I18N = {
en: customApiHelpEN,
},
translate_alt: {
zh: `翻译 (Alt+Q)`,
en: `Translate (Alt+Q)`,
zh: `翻译`,
en: `Translate`,
},
basic_setting: {
zh: `基本设置`,
@@ -183,13 +183,17 @@ export const I18N = {
zh: `翻译服务`,
en: `Translate Service`,
},
mouseover_translation: {
zh: `鼠标悬停翻译`,
en: `Mouseover translation`,
translate_timing: {
zh: `翻译时机`,
en: `Translate Timing`,
},
mk_disable: {
zh: `禁用`,
en: `Disable`,
zh: `滚动加载(建议)`,
en: `Rolling Loading (Suggested)`,
},
mk_pageopen: {
zh: `页面打开`,
en: `Page Open`,
},
mk_mouseover: {
zh: `鼠标悬停`,
@@ -228,8 +232,8 @@ export const I18N = {
en: `Text Style`,
},
text_style_alt: {
zh: `文字样式 (Alt+C)`,
en: `Text Style (Alt+C)`,
zh: `文字样式`,
en: `Text Style`,
},
bg_color: {
zh: `样式颜色`,
@@ -356,23 +360,23 @@ export const I18N = {
en: `Follow the syntax of "CSS"`,
},
setting: {
zh: `设置 (Alt+O)`,
en: `Setting (Alt+O)`,
zh: `设置`,
en: `Setting`,
},
pattern: {
zh: `匹配网址`,
en: `URL pattern`,
},
pattern_helper: {
zh: `1、支持星号(*)通配符。2、多个URL用英文逗号“,”分隔。`,
en: `1. The asterisk (*) wildcard is supported. 2. Multiple URLs separated by English commas ",".`,
zh: `1、支持星号(*)通配符。2、多个URL用换行或英文逗号“,”分隔。`,
en: `1. Supports the asterisk (*) wildcard character. 2. Separate multiple URLs with newlines or English commas ",".`,
},
selector_helper: {
zh: `1、遵循CSS选择器语法。2、留空表示采用全局设置。3、多个CSS选择器之间用“;”隔开。4、“shadow root”选择器和内部选择器用“>>>”隔开。`,
en: `1. Follow CSS selector syntax. 2. Leave blank to adopt the global setting. 3. Separate multiple CSS selectors with ";". 4. The "shadow root" selector and the internal selector are separated by ">>>".`,
},
translate_switch: {
zh: `启翻译`,
zh: `翻译`,
en: `Translate Switch`,
},
default_enabled: {
@@ -387,6 +391,22 @@ export const I18N = {
zh: `选择器`,
en: `Selector`,
},
keep_selector: {
zh: `保留元素选择器`,
en: `Keep unchanged selector`,
},
keep_selector_helper: {
zh: `1、遵循CSS选择器语法。2、留空表示采用全局设置。3、子元素选择器用“>>>”隔开。`,
en: `1. Follow CSS selector syntax. 2. Leave blank to adopt the global setting. 3.Sub-element selectors are separated by ">>>".`,
},
terms: {
zh: `专业术语`,
en: `Terms`,
},
terms_helper: {
zh: `0、支持正则表达式匹配。1、多条术语用换行或分号“;”隔开。2、术语和译文用英文逗号“,”隔开。3、没有译文视为不翻译术语。4、留空表示采用全局设置。`,
en: `0. Supports regular expression matching. 1. Separate multiple terms with newlines or semicolons ";". 2. Terms and translations are separated by English commas ",". 3. If there is no translation, the term will be deemed not to be translated. 4. Leave blank to adopt the global setting.`,
},
root_selector: {
zh: `根选择器`,
en: `Root Selector`,
@@ -540,7 +560,7 @@ export const I18N = {
en: `Shortcuts Setting`,
},
toggle_translate_shortcut: {
zh: `"启翻译"快捷键`,
zh: `"启翻译"快捷键`,
en: `"Toggle Translate" Shortcut`,
},
toggle_style_shortcut: {
@@ -707,4 +727,12 @@ export const I18N = {
zh: `添加右键菜单`,
en: `Add Context Menus`,
},
mulkeys_help: {
zh: `支持用换行或英文逗号“,”分隔多个KEY轮询调用。`,
en: `Supports multiple KEY polling calls separated by newlines or English commas ",".`,
},
translate_page_title: {
zh: `是否同时翻译页面标题`,
en: `Translate Page Title`,
},
};

View File

@@ -1,5 +1,6 @@
import {
DEFAULT_SELECTOR,
DEFAULT_KEEP_SELECTOR,
GLOBAL_KEY,
REMAIN_KEY,
SHADOW_KEY,
@@ -64,6 +65,7 @@ export const MSG_TRANS_GETRULE = "trans_getrule";
export const MSG_TRANS_PUTRULE = "trans_putrule";
export const MSG_TRANS_CURRULE = "trans_currule";
export const MSG_CONTEXT_MENUS = "context_menus";
export const MSG_COMMAND_SHORTCUTS = "command_shortcuts";
export const THEME_LIGHT = "light";
export const THEME_DARK = "dark";
@@ -82,7 +84,8 @@ export const URL_MICROSOFT_TRAN =
export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth";
export const URL_BAIDU_LANGDETECT = "https://fanyi.baidu.com/langdetect";
export const URL_BAIDU_WEB = "https://fanyi.baidu.com/";
export const URL_BAIDU_TRAN = "https://fanyi.baidu.com/v2transapi";
export const URL_BAIDU_TRANSAPI = "https://fanyi.baidu.com/transapi";
export const URL_BAIDU_TRANSAPI_V2 = "https://fanyi.baidu.com/v2transapi";
export const URL_DEEPLFREE_TRAN = "https://www2.deepl.com/jsonrpc";
export const URL_TENCENT_TRANSMART = "https://transmart.qq.com/api/imt";
@@ -100,11 +103,11 @@ export const OPT_TRANS_CUSTOMIZE = "Custom";
export const OPT_TRANS_ALL = [
OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_DEEPL,
OPT_TRANS_DEEPLFREE,
OPT_TRANS_DEEPLX,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_OPENAI,
OPT_TRANS_GEMINI,
OPT_TRANS_CLOUDFLAREAI,
@@ -295,13 +298,15 @@ export const OPT_STYLE_USE_COLOR = [
OPT_STYLE_BLOCKQUOTE,
];
export const OPT_MOUSEKEY_DISABLE = "mk_disable";
export const OPT_MOUSEKEY_DISABLE = "mk_disable"; // 滚动加载翻译
export const OPT_MOUSEKEY_PAGEOPEN = "mk_pageopen"; // 直接翻译到底
export const OPT_MOUSEKEY_MOUSEOVER = "mk_mouseover";
export const OPT_MOUSEKEY_CONTROL = "mk_ctrlKey";
export const OPT_MOUSEKEY_SHIFT = "mk_shiftKey";
export const OPT_MOUSEKEY_ALT = "mk_altKey";
export const OPT_MOUSEKEY_ALL = [
OPT_MOUSEKEY_DISABLE,
OPT_MOUSEKEY_PAGEOPEN,
OPT_MOUSEKEY_MOUSEOVER,
OPT_MOUSEKEY_CONTROL,
OPT_MOUSEKEY_SHIFT,
@@ -321,6 +326,8 @@ export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
export const GLOBLA_RULE = {
pattern: "*",
selector: DEFAULT_SELECTOR,
keepSelector: DEFAULT_KEEP_SELECTOR,
terms: "",
translator: OPT_TRANS_MICROSOFT,
fromLang: "auto",
toLang: "zh-CN",
@@ -446,10 +453,11 @@ export const DEFAULT_SETTING = {
injectWebfix: true, // 是否注入修复补丁
detectRemote: false, // 是否使用远程语言检测
contextMenus: true, // 是否添加右键菜单
transTitle: false, // 是否同时翻译页面标题
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
transApis: DEFAULT_TRANS_APIS, // 翻译接口
mouseKey: OPT_MOUSEKEY_DISABLE, // 鼠标悬停翻译
mouseKey: OPT_MOUSEKEY_DISABLE, // 翻译时机/鼠标悬停翻译
shortcuts: DEFAULT_SHORTCUTS, // 快捷键
inputRule: DEFAULT_INPUT_RULE, // 输入框设置
tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置

View File

@@ -1,4 +1,5 @@
export const DEFAULT_SELECTOR = `:is(li, p, h1, h2, h3, h4, h5, h6, dd, blockquote)`;
export const DEFAULT_KEEP_SELECTOR = `code, img, svg`;
export const GLOBAL_KEY = "*";
export const REMAIN_KEY = "-";
@@ -8,6 +9,8 @@ export const SHADOW_KEY = ">>>";
export const DEFAULT_RULE = {
pattern: "",
selector: "",
keepSelector: "",
terms: "",
translator: GLOBAL_KEY,
fromLang: GLOBAL_KEY,
toLang: GLOBAL_KEY,
@@ -41,65 +44,155 @@ export const DEFAULT_OW_RULE = {
};
const RULES_MAP = {
"www.google.com/search": `h3, .IsZvec, .VwiC3b`,
"news.google.com": `[role="link"], .DY5T1d, .ifw3f, ${DEFAULT_SELECTOR}`,
"www.foxnews.com": `h1, h2, .title, .sidebar [data-type="Title"], .article-content ${DEFAULT_SELECTOR}; [data-spotim-module="conversation"]>div >>> [data-spot-im-class="message-text"] p, [data-spot-im-class="message-text"]`,
"bearblog.dev, www.theverge.com, www.tampermonkey.net/documentation.php": `${DEFAULT_SELECTOR}`,
"themessenger.com": `.leading-tight, .leading-tighter, .my-2 p, .font-body p, article ${DEFAULT_SELECTOR}`,
"www.telegraph.co.uk, go.dev/doc/": `article ${DEFAULT_SELECTOR}`,
"www.theguardian.com": `.show-underline, .dcr-hup5wm div, .dcr-7vl6y8 div, .dcr-12evv1c, figcaption, article ${DEFAULT_SELECTOR}, [data-cy="mostviewed-footer"] h4`,
"www.semafor.com": `${DEFAULT_SELECTOR}, .styles_intro__IYj__, [class*="styles_description"]`,
"www.noemamag.com": `.splash__title, .single-card__title, .single-card__type, .single-card__topic, .highlighted-content__title, .single-card__author, article ${DEFAULT_SELECTOR}, .quote__text, .wp-caption-text div`,
"restofworld.org": `${DEFAULT_SELECTOR}, .recirc-story__headline, .recirc-story__dek`,
"www.axios.com": `.h7, ${DEFAULT_SELECTOR}`,
"www.newyorker.com": `.summary-item__hed, .summary-item__dek, .summary-collection-grid__dek, .dqtvfu, .rubric__link, .caption, article ${DEFAULT_SELECTOR}, .HEhan ${DEFAULT_SELECTOR}, .ContributorBioBio-fBolsO`,
"time.com": `h1, h3, .summary, .video-title, #article-body ${DEFAULT_SELECTOR}, .image-wrap-container .credit.body-caption, .media-heading`,
"www.dw.com": `.ts-teaser-title a, .news-title a, .title a, .teaser-description a, .hbudab h3, .hbudab p, figcaption ,article ${DEFAULT_SELECTOR}`,
"www.bbc.com": `h1, h2, .media__link, .media__summary, article ${DEFAULT_SELECTOR}, .ssrcss-y7krbn-Stack, .ssrcss-17zglt8-PromoHeadline, .ssrcss-18cjaf3-Headline, .gs-c-promo-heading__title, .gs-c-promo-summary, .media__content h3, .article__intro`,
"www.chinadaily.com.cn": `h1, .tMain [shape="rect"], .cMain [shape="rect"], .photo_art [shape="rect"], .mai_r [shape="rect"], .lisBox li, #Content ${DEFAULT_SELECTOR}`,
"www.facebook.com": `[role="main"] [dir="auto"]`,
"www.reddit.com": `[slot="title"], [slot="text-body"] ${DEFAULT_SELECTOR}, #-post-rtjson-content p`,
"www.quora.com": `.qu-wordBreak--break-word`,
"edition.cnn.com": `.container__title, .container__headline, .headline__text, .image__caption, [data-type="Title"], .article__content ${DEFAULT_SELECTOR}`,
"www.reuters.com": `#main-content [data-testid="Heading"], #main-content [data-testid="Body"], .article-body__content__17Yit ${DEFAULT_SELECTOR}`,
"www.bloomberg.com": `[data-component="headline"], [data-component="related-item-headline"], [data-component="title"], article ${DEFAULT_SELECTOR}`,
"deno.land, docs.github.com": `main ${DEFAULT_SELECTOR}`,
"doc.rust-lang.org": `.content ${DEFAULT_SELECTOR}`,
"www.indiehackers.com": `h1, h3, .content ${DEFAULT_SELECTOR}, .feed-item__title-link`,
"platform.openai.com/docs": `.docs-body ${DEFAULT_SELECTOR}`,
"en.wikipedia.org": `h1, .mw-parser-output ${DEFAULT_SELECTOR}`,
"stackoverflow.com": `h1, .s-prose p, .comment-body .comment-copy`,
"www.npmjs.com/package, developer.chrome.com/docs, medium.com, developers.cloudflare.com, react.dev, create-react-app.dev, pytorch.org": `article ${DEFAULT_SELECTOR}`,
"news.ycombinator.com": `.title, .commtext`,
"github.com": `.markdown-body ${DEFAULT_SELECTOR}, .repo-description p, .Layout-sidebar .f4, .container-lg .py-4 .f5, .container-lg .my-4 .f5, .Box-row .pr-4, .Box-row article .mt-1, [itemprop="description"], .markdown-title, bdi, .ws-pre-wrap, .status-meta, span.status-meta, .col-10.color-fg-muted, .TimelineItem-body, .pinned-item-list-item-content .color-fg-muted, .markdown-body td, .markdown-body th`,
"twitter.com": `[data-testid="tweetText"]`,
"m.youtube.com": `.slim-video-information-title .yt-core-attributed-string, .media-item-headline .yt-core-attributed-string, .comment-text .yt-core-attributed-string, .typography-body-2b .yt-core-attributed-string, #ytp-caption-window-container .ytp-caption-segment`,
"www.youtube.com": `h1, #video-title, #content-text, #title, yt-attributed-string>span>span, #ytp-caption-window-container .ytp-caption-segment`,
"bard.google.com": `.query-content ${DEFAULT_SELECTOR}, message-content ${DEFAULT_SELECTOR}`,
"www.bing.com": `.b_algoSlug, .rwrl_padref; .cib-serp-main >>> .ac-textBlock ${DEFAULT_SELECTOR}, .text-message-content div`,
"www.phoronix.com": `article ${DEFAULT_SELECTOR}`,
"wx2.qq.com": `.js_message_plain`,
"app.slack.com/client/": `.p-rich_text_section, .c-message_attachment__text, .p-rich_text_list li`,
"discord.com/channels/": `div[id^=message-content]`,
"t.me/s/": `.js-message_text ${DEFAULT_SELECTOR}`,
"web.telegram.org/k/": `.message, .bot-commands-list-element-description, .reply-markup-button-text`,
"web.telegram.org/a/": `.message, .text-content, .bot-commands-list-element-description, .reply-markup-button-text`,
"chromereleases.googleblog.com": `.title, .publishdate, p, i, .header-desc, .header-title, .text`,
"www.instagram.com/": `h1, article span[dir=auto] > span[dir=auto], ._ab1y`,
"www.instagram.com/p/,www.instagram.com/reels/": `h1, div[class='x9f619 xjbqb8w x78zum5 x168nmei x13lgxp2 x5pf9jr xo71vjh x1uhb9sk x1plvlek xryxfnj x1c4vz4f x2lah0s xdt5ytf xqjyukv x1cy8zhl x1oa3qoh x1nhvcw1'] > span[class='x1lliihq x1plvlek xryxfnj x1n2onr6 x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x1i0vuye xvs91rp xo1l8bm x5n08af x10wh9bi x1wdrske x8viiok x18hxmgj'], span[class='x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs xt0psk2 x1i0vuye xvs91rp xo1l8bm x5n08af x10wh9bi x1wdrske x8viiok x18hxmgj']`,
"mail.google.com": `${DEFAULT_SELECTOR}, h2[data-thread-perm-id], span[data-thread-id], div[data-message-id] div[class=''], .messageBody, #views`,
"web.whatsapp.com": `.copyable-text > span`,
"chat.openai.com": `div[data-message-author-role] > div ${DEFAULT_SELECTOR}`,
"forum.ru-board.com": `.tit, .dats, span.post ${DEFAULT_SELECTOR}`,
"education.github.com": `${DEFAULT_SELECTOR}, a, summary, span.Button-content`,
"blogs.windows.com": `${DEFAULT_SELECTOR}, .c-uhf-nav-link, figcaption`,
"developer.apple.com/documentation/": `#main ${DEFAULT_SELECTOR}, #main .abstract .content, #main .abstract.content, #main .link span`,
"www.google.com/search": [`h3, .IsZvec, .VwiC3b`],
"news.google.com": [`[role="link"], .DY5T1d, .ifw3f, ${DEFAULT_SELECTOR}`],
"www.foxnews.com": [
`h1, h2, .title, .sidebar [data-type="Title"], .article-content ${DEFAULT_SELECTOR}; [data-spotim-module="conversation"]>div >>> [data-spot-im-class="message-text"] p, [data-spot-im-class="message-text"]`,
],
"bearblog.dev, www.theverge.com, www.tampermonkey.net/documentation.php": [
`${DEFAULT_SELECTOR}`,
],
"themessenger.com": [
`.leading-tight, .leading-tighter, .my-2 p, .font-body p, article ${DEFAULT_SELECTOR}`,
],
"www.telegraph.co.uk, go.dev/doc/": [`article ${DEFAULT_SELECTOR}`],
"www.theguardian.com": [
`.show-underline, .dcr-hup5wm div, .dcr-7vl6y8 div, .dcr-12evv1c, figcaption, article ${DEFAULT_SELECTOR}, [data-cy="mostviewed-footer"] h4`,
],
"www.semafor.com": [
`${DEFAULT_SELECTOR}, .styles_intro__IYj__, [class*="styles_description"]`,
],
"www.noemamag.com": [
`.splash__title, .single-card__title, .single-card__type, .single-card__topic, .highlighted-content__title, .single-card__author, article ${DEFAULT_SELECTOR}, .quote__text, .wp-caption-text div`,
],
"restofworld.org": [
`${DEFAULT_SELECTOR}, .recirc-story__headline, .recirc-story__dek`,
],
"www.axios.com": [`.h7, ${DEFAULT_SELECTOR}`],
"www.newyorker.com": [
`.summary-item__hed, .summary-item__dek, .summary-collection-grid__dek, .dqtvfu, .rubric__link, .caption, article ${DEFAULT_SELECTOR}, .HEhan ${DEFAULT_SELECTOR}, .ContributorBioBio-fBolsO, .BaseText-ewhhUZ`,
],
"time.com": [
`h1, h3, .summary, .video-title, #article-body ${DEFAULT_SELECTOR}, .image-wrap-container .credit.body-caption, .media-heading`,
],
"www.dw.com": [
`.ts-teaser-title a, .news-title a, .title a, .teaser-description a, .hbudab h3, .hbudab p, figcaption ,article ${DEFAULT_SELECTOR}`,
],
"www.bbc.com": [
`h1, h2, .media__link, .media__summary, article ${DEFAULT_SELECTOR}, .ssrcss-y7krbn-Stack, .ssrcss-17zglt8-PromoHeadline, .ssrcss-18cjaf3-Headline, .gs-c-promo-heading__title, .gs-c-promo-summary, .media__content h3, .article__intro, .lx-c-summary-points>li`,
],
"www.chinadaily.com.cn": [
`h1, .tMain [shape="rect"], .cMain [shape="rect"], .photo_art [shape="rect"], .mai_r [shape="rect"], .lisBox li, #Content ${DEFAULT_SELECTOR}`,
],
"www.facebook.com": [`[role="main"] [dir="auto"]`],
"www.reddit.com": [
`div:is(.tbIApBd2DM_drfZQJjIum, ._1zPvgKHteTOub9dKkvrOl4,.ULWj94BYSOqoJDetxgcnU),a:is([class^="_334yl59"],[class^="_2GrMpxD"]),h1,h2,h3,h4,h5,h6,p,button`,
],
"www.quora.com": [`.qu-wordBreak--break-word`],
"edition.cnn.com": [
`.container__title, .container__headline, .headline__text, .image__caption, [data-type="Title"], .article__content ${DEFAULT_SELECTOR}`,
],
"www.reuters.com": [
`#main-content [data-testid="Heading"], #main-content [data-testid="Body"], .article-body__content__17Yit ${DEFAULT_SELECTOR}`,
],
"www.bloomberg.com": [
`[data-component="headline"], [data-component="related-item-headline"], [data-component="title"], article ${DEFAULT_SELECTOR}`,
],
"deno.land, docs.github.com": [`main ${DEFAULT_SELECTOR}`, `code, img, svg`],
"doc.rust-lang.org": [`.content ${DEFAULT_SELECTOR}`, `code, img, svg`],
"www.indiehackers.com": [
`h1, h3, .content ${DEFAULT_SELECTOR}, .feed-item__title-link`,
],
"platform.openai.com/docs": [
`.docs-body ${DEFAULT_SELECTOR}`,
`code, img, svg`,
],
"en.wikipedia.org": [
`h1, .mw-parser-output ${DEFAULT_SELECTOR}`,
`.mwe-math-element`,
],
"stackoverflow.com": [
`h1, .s-prose p, .comment-body .comment-copy`,
`code, img, svg`,
],
"www.npmjs.com/package, developer.chrome.com/docs, medium.com, developers.cloudflare.com, react.dev, create-react-app.dev, pytorch.org":
[`article ${DEFAULT_SELECTOR}`],
"news.ycombinator.com": [`.title, .commtext`],
"github.com": [
`.markdown-body ${DEFAULT_SELECTOR}, .repo-description p, .Layout-sidebar .f4, .container-lg .py-4 .f5, .container-lg .my-4 .f5, .Box-row .pr-4, .Box-row article .mt-1, [itemprop="description"], .markdown-title, bdi, .ws-pre-wrap, .status-meta, span.status-meta, .col-10.color-fg-muted, .TimelineItem-body, .pinned-item-list-item-content .color-fg-muted, .markdown-body td, .markdown-body th`,
`code, img, svg`,
],
"twitter.com": [
`[data-testid="tweetText"], [data-testid="birdwatch-pivot"]>div.css-1rynq56`,
`img, a, .r-18u37iz, .css-175oi2r`,
],
"m.youtube.com": [
`.slim-video-information-title .yt-core-attributed-string, .media-item-headline .yt-core-attributed-string, .comment-text .yt-core-attributed-string, .typography-body-2b .yt-core-attributed-string, #ytp-caption-window-container .ytp-caption-segment`,
],
"www.youtube.com": [
`h1, #video-title, #content-text, #title, yt-attributed-string>span>span, #ytp-caption-window-container .ytp-caption-segment`,
],
"bard.google.com": [
`.query-content ${DEFAULT_SELECTOR}, message-content ${DEFAULT_SELECTOR}`,
],
"www.bing.com": [
`.b_algoSlug, .rwrl_padref; .cib-serp-main >>> .ac-textBlock ${DEFAULT_SELECTOR}, .text-message-content div`,
],
"www.phoronix.com": [`article ${DEFAULT_SELECTOR}`],
"wx2.qq.com": [`.js_message_plain`],
"app.slack.com/client/": [
`.p-rich_text_section, .c-message_attachment__text, .p-rich_text_list li`,
],
"discord.com/channels/": [
`li[id^=chat-messages] div[id^=message-content], div[class^=headerText], div[class^=name_], section[aria-label='Search Results'] div[id^=message-content]`,
],
"t.me/s/": [`.js-message_text ${DEFAULT_SELECTOR}`],
"web.telegram.org/k/": [
`.message, .bot-commands-list-element-description, .reply-markup-button-text`,
],
"web.telegram.org/a/": [
`.message, .text-content, .bot-commands-list-element-description, .reply-markup-button-text`,
],
"chromereleases.googleblog.com": [
`.title, .publishdate, p, i, .header-desc, .header-title, .text`,
],
"www.instagram.com/": [`h1, article span[dir=auto] > span[dir=auto], ._ab1y`],
"www.instagram.com/p/,www.instagram.com/reels/": [
`h1, div[class='x9f619 xjbqb8w x78zum5 x168nmei x13lgxp2 x5pf9jr xo71vjh x1uhb9sk x1plvlek xryxfnj x1c4vz4f x2lah0s xdt5ytf xqjyukv x1cy8zhl x1oa3qoh x1nhvcw1'] > span[class='x1lliihq x1plvlek xryxfnj x1n2onr6 x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x1i0vuye xvs91rp xo1l8bm x5n08af x10wh9bi x1wdrske x8viiok x18hxmgj'], span[class='x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs xt0psk2 x1i0vuye xvs91rp xo1l8bm x5n08af x10wh9bi x1wdrske x8viiok x18hxmgj']`,
],
"mail.google.com": [
`${DEFAULT_SELECTOR}, h2[data-thread-perm-id], span[data-thread-id], div[data-message-id] div[class=''], .messageBody, #views`,
],
"web.whatsapp.com": [`.copyable-text > span`],
"chat.openai.com": [
`div[data-message-author-role] > div ${DEFAULT_SELECTOR}`,
],
"forum.ru-board.com": [`.tit, .dats, span.post, .lgf ${DEFAULT_SELECTOR}`],
"education.github.com": [
`${DEFAULT_SELECTOR}, a, summary, span.Button-content`,
],
"blogs.windows.com": [`${DEFAULT_SELECTOR}, .c-uhf-nav-link, figcaption`],
"developer.apple.com/documentation/": [
`#main ${DEFAULT_SELECTOR}, #main .abstract .content, #main .abstract.content, #main .link span`,
`code, img, svg`,
],
"greasyfork.org": [
`h2, .script-link, .script-description, #additional-info ${DEFAULT_SELECTOR}`,
],
"www.fmkorea.com": [`#container ${DEFAULT_SELECTOR}`],
"forum.arduino.cc": [
`.top-row>.title, .featured-topic>.title, .link-top-line>.title, .category-description, .topic-excerpt, .fancy-title, .cooked ${DEFAULT_SELECTOR}`,
],
"docs.arduino.cc": [`[class^="tutorial-module--left"] ${DEFAULT_SELECTOR}`],
"www.historydefined.net": [`.wp-element-caption, ${DEFAULT_SELECTOR}`],
};
export const BUILTIN_RULES = Object.entries(RULES_MAP)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([pattern, selector]) => ({
.map(([pattern, [selector, keepSelector = "", terms = ""]]) => ({
...DEFAULT_RULE,
pattern,
selector,
keepSelector,
terms,
}));

View File

@@ -23,6 +23,12 @@ export function useTranslate(q, rule, setting) {
try {
setLoading(true);
if (!q.replace(/\[(\d+)\]/g, "").trim()) {
setText(q);
setSamelang(false);
return;
}
const deLang = await tryDetectLang(q, setting.detectRemote);
const disableLangs = setting.disableLangs || [];
if (

View File

@@ -10,4 +10,4 @@ import { DEFAULT_BLACKLIST } from "../config";
export const isInBlacklist = (
href,
{ blacklist = DEFAULT_BLACKLIST.join(",\n") }
) => blacklist.split(",").some((url) => isMatch(href, url.trim()));
) => blacklist.split(/\n|,/).some((url) => isMatch(href, url.trim()));

View File

@@ -21,6 +21,26 @@ import { msAuth } from "./auth";
import { genDeeplFree } from "../apis/deepl";
import { genBaidu } from "../apis/baidu";
const keyMap = new Map();
// 轮询key
const keyPick = (translator, key = "") => {
const keys = key
.split(/\n|,/)
.map((item) => item.trim())
.filter(Boolean);
if (keys.length === 0) {
return "";
}
const preIndex = keyMap.get(translator) ?? -1;
const curIndex = (preIndex + 1) % keys.length;
keyMap.set(translator, curIndex);
return keys[curIndex];
};
/**
* 构造缓存 request
* @param {*} request
@@ -257,6 +277,17 @@ const genCustom = ({ text, from, to, url, key }) => {
*/
export const newTransReq = ({ translator, text, from, to }, apiSetting) => {
const args = { text, from, to, ...apiSetting };
switch (translator) {
case OPT_TRANS_DEEPL:
case OPT_TRANS_OPENAI:
case OPT_TRANS_GEMINI:
case OPT_TRANS_CLOUDFLAREAI:
args.key = keyPick(translator, args.key);
break;
default:
}
switch (translator) {
case OPT_TRANS_GOOGLE:
return genGoogle(args);

View File

@@ -66,6 +66,8 @@ export const matchRule = async (
}
rule.selector = rule.selector?.trim() || globalRule.selector;
rule.keepSelector = rule.keepSelector?.trim() || globalRule.keepSelector;
rule.terms = rule.terms?.trim() || globalRule.terms;
if (rule.textStyle === GLOBAL_KEY) {
rule.textStyle = globalRule.textStyle;
rule.bgColor = globalRule.bgColor;
@@ -112,6 +114,8 @@ export const checkRules = (rules) => {
({
pattern,
selector,
keepSelector,
terms,
translator,
fromLang,
toLang,
@@ -122,6 +126,8 @@ export const checkRules = (rules) => {
}) => ({
pattern: pattern.trim(),
selector: type(selector) === "string" ? selector : "",
keepSelector: type(keepSelector) === "string" ? keepSelector : "",
terms: type(terms) === "string" ? terms : "",
bgColor: type(bgColor) === "string" ? bgColor : "",
textDiyStyle: type(textDiyStyle) === "string" ? textDiyStyle : "",
translator: matchValue([GLOBAL_KEY, ...OPT_TRANS_ALL], translator),

View File

@@ -36,8 +36,8 @@ export const shortcutListener = (fn, target = document, timeout = 3000) => {
}
};
target.addEventListener("keydown", handleKeydown);
target.addEventListener("keyup", handleKeyup);
target.addEventListener("keydown", handleKeydown, true);
target.addEventListener("keyup", handleKeyup, true);
return () => {
if (timer) {
clearTimeout(timer);

View File

@@ -42,7 +42,7 @@ export const syncSubRules = async (url) => {
* @returns
*/
export const syncAllSubRules = async (subrulesList) => {
for (let subrules of subrulesList) {
for (const subrules of subrulesList) {
try {
await syncSubRules(subrules.url);
await updateSyncDataCache(subrules.url);

View File

@@ -8,11 +8,15 @@ import {
OPT_STYLE_FUZZY,
SHADOW_KEY,
OPT_MOUSEKEY_DISABLE,
OPT_MOUSEKEY_PAGEOPEN,
OPT_MOUSEKEY_MOUSEOVER,
DEFAULT_TRANS_APIS,
} from "../config";
import Content from "../views/Content";
import { updateFetchPool, clearFetchPool } from "./fetch";
import { debounce, genEventName } from "./utils";
import { runFixer } from "./webfix";
import { apiTranslate } from "../apis";
/**
* 翻译类
@@ -20,6 +24,7 @@ import { debounce, genEventName } from "./utils";
export class Translator {
_rule = {};
_setting = {};
_fixerSetting = null;
_rootNodes = new Set();
_tranNodes = new Map();
_skipNodeNames = [
@@ -40,6 +45,9 @@ export class Translator {
];
_eventName = genEventName();
_mouseoverNode = null;
_keepSelector = [null, null];
_terms = [];
_docTitle = "";
// 显示
_interseObserver = new IntersectionObserver(
@@ -91,13 +99,22 @@ export class Translator {
};
};
constructor(rule, setting) {
constructor(rule, setting, fixerSetting) {
const { fetchInterval, fetchLimit } = setting;
updateFetchPool(fetchInterval, fetchLimit);
this._overrideAttachShadow();
this._setting = setting;
this._rule = rule;
this._fixerSetting = fixerSetting;
this._keepSelector = (rule.keepSelector || "")
.split(SHADOW_KEY)
.map((item) => item.trim());
this._terms = (rule.terms || "")
.split(/\n|;/)
.map((item) => item.split(",").map((item) => item.trim()))
.filter(([term]) => Boolean(term));
if (rule.transOpen === "true") {
this._register();
@@ -155,6 +172,20 @@ export class Translator {
this.rule = { ...this.rule, textStyle };
};
translateText = async (text) => {
const { translator, fromLang, toLang } = this._rule;
const apiSetting =
this._setting.transApis?.[translator] || DEFAULT_TRANS_APIS[translator];
const [trText] = await apiTranslate({
text,
translator,
fromLang,
toLang,
apiSetting,
});
return trText;
};
_querySelectorAll = (selector, node) => {
try {
return Array.from(node.querySelectorAll(selector));
@@ -235,6 +266,11 @@ export class Translator {
return;
}
// webfix
if (this._fixerSetting) {
runFixer(this._fixerSetting);
}
// 搜索节点
this._queryNodes();
@@ -255,6 +291,11 @@ export class Translator {
this._tranNodes.forEach((_, node) => {
this._interseObserver.observe(node);
});
} else if (this._setting.mouseKey === OPT_MOUSEKEY_PAGEOPEN) {
// 全文直接翻译
this._tranNodes.forEach((_, node) => {
this._render(node);
});
} else {
// 监听鼠标悬停
window.addEventListener("keydown", this._handleKeydown);
@@ -263,6 +304,15 @@ export class Translator {
node.addEventListener("mouseleave", this._handleMouseout);
});
}
// 翻译页面标题
if (this._setting.transTitle && !this._docTitle) {
const title = document.title;
this._docTitle = title;
this.translateText(title).then((trText) => {
document.title = `${trText} | ${title}`;
});
}
};
_handleMouseover = (e) => {
@@ -310,6 +360,12 @@ export class Translator {
};
_unRegister = () => {
// 恢复页面标题
if (this._docTitle) {
document.title = this._docTitle;
this._docTitle = "";
}
// 解除节点变化监听
this._mutaObserver.disconnect();
@@ -326,6 +382,10 @@ export class Translator {
// 移除已插入元素
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);
@@ -352,6 +412,11 @@ export class Translator {
}
}, 500);
_invalidLength = (q) =>
!q ||
q.length < (this._setting.minLength ?? TRANS_MIN_LENGTH) ||
q.length > (this._setting.maxLength ?? TRANS_MAX_LENGTH);
_render = (el) => {
let traEl = el.querySelector(APP_LCNAME);
@@ -371,19 +436,52 @@ export class Translator {
traEl.remove();
}
const q = el.innerText.trim();
let q = el.innerText.trim();
this._tranNodes.set(el, q);
const keeps = [];
// 保留元素
const [matchSelector, subSelector] = this._keepSelector;
if (matchSelector || subSelector) {
let text = "";
el.childNodes.forEach((child) => {
if (
child.nodeType === 1 &&
((matchSelector && child.matches(matchSelector)) ||
(subSelector && child.querySelector(subSelector)))
) {
if (child.nodeName === "IMG") {
child.style.cssText += `width: ${child.width}px;`;
child.style.cssText += `height: ${child.height}px;`;
}
text += `[${keeps.length}]`;
keeps.push(child.outerHTML);
} else {
text += child.textContent;
}
});
if (keeps.length > 0) {
q = text;
}
}
// 太长或太短
if (
!q ||
q.length < (this._setting.minLength ?? TRANS_MIN_LENGTH) ||
q.length > (this._setting.maxLength ?? TRANS_MAX_LENGTH)
) {
if (this._invalidLength(q.replace(/\[(\d+)\]/g, "").trim())) {
return;
}
// console.log("---> ", q);
// 专业术语
if (this._terms.length > 0) {
for (const term of this._terms) {
const re = new RegExp(term[0], "g");
q = q.replace(re, (t) => {
const text = `[${keeps.length}]`;
keeps.push(term[1] || t);
return text;
});
}
}
traEl = document.createElement(APP_LCNAME);
traEl.style.visibility = "visible";
@@ -395,7 +493,9 @@ export class Translator {
"-webkit-line-clamp: unset; max-height: none; height: auto;";
}
// console.log({ q, keeps });
const root = createRoot(traEl);
root.render(<Content q={q} translator={this} />);
root.render(<Content q={q} keeps={keeps} translator={this} />);
};
}

View File

@@ -69,8 +69,8 @@ function brFixer(node, tag = "p") {
}
node.setAttribute(fixedSign, "true");
var gapTags = ["BR", "WBR"];
var newlineTags = [
const gapTags = ["BR", "WBR"];
const newlineTags = [
"DIV",
"UL",
"OL",
@@ -87,7 +87,7 @@ function brFixer(node, tag = "p") {
"TABLE",
];
var html = "";
let html = "";
node.childNodes.forEach(function (child, index) {
if (index === 0) {
html += `<${tag} class="kiss-p">`;
@@ -99,8 +99,8 @@ function brFixer(node, tag = "p") {
html += `</${tag}>${child.outerHTML}<${tag} class="kiss-p">`;
} else if (child.outerHTML) {
html += child.outerHTML;
} else if (child.nodeValue) {
html += child.nodeValue;
} else if (child.textContent) {
html += child.textContent;
}
if (index === node.childNodes.length - 1) {
@@ -160,7 +160,7 @@ const fixerMap = {
* @param {*} rootSelector
*/
function run(selector, fixer, rootSelector) {
var mutaObserver = new MutationObserver(function (mutations) {
const mutaObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (addNode) {
if (addNode && addNode.querySelectorAll) {
@@ -172,7 +172,7 @@ function run(selector, fixer, rootSelector) {
});
});
var rootNodes = [document];
let rootNodes = [document];
if (rootSelector) {
rootNodes = document.querySelectorAll(rootSelector);
}
@@ -218,28 +218,38 @@ export const loadOrFetchWebfix = async (url) => {
};
/**
* 匹配站点
* 执行fixer
* @param {*} param0
*/
export async function runWebfix({ injectWebfix }) {
export async function runFixer({ selector, fixer, rootSelector }) {
try {
if (!injectWebfix) {
return;
}
run(selector, fixerMap[fixer], rootSelector);
} catch (err) {
console.error(`[kiss-webfix run]: ${err.message}`);
}
}
const href = document.location.href;
/**
* 匹配fixer配置
*/
export async function matchFixer(href, { injectWebfix }) {
if (!injectWebfix) {
return null;
}
try {
const userSites = await getWebfixRulesWithDefault();
const subSites = await loadOrFetchWebfix(process.env.REACT_APP_WEBFIXURL);
const sites = [...userSites, ...subSites];
for (var i = 0; i < sites.length; i++) {
var site = sites[i];
if (isMatch(href, site.pattern)) {
if (fixerMap[site.fixer]) {
run(site.selector, fixerMap[site.fixer], site.rootSelector);
}
break;
for (let i = 0; i < sites.length; i++) {
const site = sites[i];
if (isMatch(href, site.pattern) && fixerMap[site.fixer]) {
return site;
}
}
} catch (err) {
console.error(`[kiss-webfix]: ${err.message}`);
console.error(`[kiss-webfix match]: ${err.message}`);
}
return null;
}

View File

@@ -99,7 +99,7 @@ export default function Action({ translator, fab }) {
contextMenus &&
menuCommandIds.push(
GM.registerMenuCommand(
"Toggle Translate (Alt+q)",
"Toggle Translate",
(event) => {
translator.toggle();
sendIframeMsg(MSG_TRANS_TOGGLE);
@@ -108,7 +108,7 @@ export default function Action({ translator, fab }) {
"Q"
),
GM.registerMenuCommand(
"Toggle Style (Alt+c)",
"Toggle Style",
(event) => {
translator.toggleStyle();
sendIframeMsg(MSG_TRANS_TOGGLE_STYLE);
@@ -117,14 +117,14 @@ export default function Action({ translator, fab }) {
"C"
),
GM.registerMenuCommand(
"Open Menu (Alt+k)",
"Open Menu",
(event) => {
setShowPopup((pre) => !pre);
},
"K"
),
GM.registerMenuCommand(
"Open Setting (Alt+o)",
"Open Setting",
(event) => {
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
},

View File

@@ -112,7 +112,7 @@ function StyledSpan({ textStyle, textDiyStyle, bgColor, children }) {
}
}
export default function Content({ q, translator }) {
export default function Content({ q, keeps, translator }) {
const [rule, setRule] = useState(translator.rule);
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
const { textStyle, bgColor = "", textDiyStyle = "" } = rule;
@@ -145,18 +145,28 @@ export default function Content({ q, translator }) {
);
}
if (text && !sameLang) {
return (
<>
{q.length >= newlineLength ? <br /> : " "}
<StyledSpan
textStyle={textStyle}
textDiyStyle={textDiyStyle}
bgColor={bgColor}
>
{text}
</StyledSpan>
</>
);
if (!text || sameLang) {
return;
}
return (
<>
{q.length >= newlineLength ? <br /> : " "}
<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

@@ -5,11 +5,13 @@ import CircularProgress from "@mui/material/CircularProgress";
import {
OPT_TRANS_ALL,
OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPL,
OPT_TRANS_DEEPLFREE,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_OPENAI,
OPT_TRANS_GEMINI,
OPT_TRANS_CLOUDFLAREAI,
OPT_TRANS_CUSTOMIZE,
URL_KISS_PROXY,
} from "../../config";
@@ -96,6 +98,13 @@ function ApiFields({ translator }) {
OPT_TRANS_TENCENT,
];
const mulkeysTranslators = [
OPT_TRANS_DEEPL,
OPT_TRANS_OPENAI,
OPT_TRANS_GEMINI,
OPT_TRANS_CLOUDFLAREAI,
];
return (
<Stack spacing={3}>
{!buildinTranslators.includes(translator) && (
@@ -113,6 +122,10 @@ function ApiFields({ translator }) {
name="key"
value={key}
onChange={handleChange}
multiline={mulkeysTranslators.includes(translator)}
helperText={
mulkeysTranslators.includes(translator) ? i18n("mulkeys_help") : ""
}
/>
</>
)}

View File

@@ -35,7 +35,7 @@ function DictField({ word }) {
fromLang: "en",
toLang: "zh-CN",
});
setDictResult(dictRes[2].dict_result);
dictRes[2].type === 1 && setDictResult(JSON.parse(dictRes[2].result));
} catch (err) {
setError(err.message);
} finally {

View File

@@ -65,6 +65,8 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
const {
pattern,
selector,
keepSelector = "",
terms = "",
translator,
fromLang,
toLang,
@@ -179,6 +181,26 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
onFocus={handleFocus}
multiline
/>
<TextField
size="small"
label={i18n("keep_selector")}
helperText={i18n("keep_selector_helper")}
name="keepSelector"
value={keepSelector}
disabled={disabled}
onChange={handleChange}
multiline
/>
<TextField
size="small"
label={i18n("terms")}
helperText={i18n("terms_helper")}
name="terms"
value={terms}
disabled={disabled}
onChange={handleChange}
multiline
/>
<Box>
<Grid container spacing={2} columns={12}>

View File

@@ -97,6 +97,7 @@ export default function Settings() {
mouseKey = OPT_MOUSEKEY_DISABLE,
detectRemote = false,
contextMenus = true,
transTitle = false,
touchTranslate = 2,
blacklist = DEFAULT_BLACKLIST.join(",\n"),
disableLangs = [],
@@ -168,11 +169,11 @@ export default function Settings() {
/>
<FormControl size="small">
<InputLabel>{i18n("mouseover_translation")}</InputLabel>
<InputLabel>{i18n("translate_timing")}</InputLabel>
<Select
name="mouseKey"
value={mouseKey}
label={i18n("mouseover_translation")}
label={i18n("translate_timing")}
onChange={handleChange}
>
{OPT_MOUSEKEY_ALL.map((item) => (
@@ -183,6 +184,19 @@ export default function Settings() {
</Select>
</FormControl>
<FormControl size="small">
<InputLabel>{i18n("translate_page_title")}</InputLabel>
<Select
name="transTitle"
value={transTitle}
label={i18n("translate_page_title")}
onChange={handleChange}
>
<MenuItem value={false}>{i18n("disable")}</MenuItem>
<MenuItem value={true}>{i18n("enable")}</MenuItem>
</Select>
</FormControl>
<FormControl size="small">
<InputLabel>{i18n("touch_translate_shortcut")}</InputLabel>
<Select

View File

@@ -10,6 +10,7 @@ import Switch from "@mui/material/Switch";
import { useCallback } from "react";
import { limitNumber } from "../../libs/utils";
import { useTranbox } from "../../hooks/Tranbox";
import { isExt } from "../../libs/client";
export default function Tranbox() {
const i18n = useI18n();
@@ -159,11 +160,13 @@ export default function Tranbox() {
<MenuItem value={true}>{i18n("hide")}</MenuItem>
</TextField>
<ShortcutInput
value={tranboxShortcut}
onChange={handleShortcutInput}
label={i18n("trigger_tranbox_shortcut")}
/>
{!isExt && (
<ShortcutInput
value={tranboxShortcut}
onChange={handleShortcutInput}
label={i18n("trigger_tranbox_shortcut")}
/>
)}
</Stack>
</Box>
);

View File

@@ -18,11 +18,11 @@ import {
MSG_TRANS_PUTRULE,
MSG_OPEN_OPTIONS,
MSG_SAVE_RULE,
MSG_COMMAND_SHORTCUTS,
OPT_TRANS_ALL,
OPT_LANGS_FROM,
OPT_LANGS_TO,
OPT_STYLE_ALL,
OPT_STYLE_USE_COLOR,
} from "../../config";
import { sendIframeMsg } from "../../libs/iframe";
import { saveRule } from "../../libs/rules";
@@ -31,6 +31,7 @@ import { tryClearCaches } from "../../libs";
export default function Popup({ setShowPopup, translator: tran }) {
const i18n = useI18n();
const [rule, setRule] = useState(tran?.rule);
const [commands, setCommands] = useState({});
const handleOpenSetting = () => {
if (!tran) {
@@ -85,7 +86,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
const tab = await getTabInfo();
href = tab.url;
}
const newRule = { ...rule, pattern: href };
const newRule = { ...rule, pattern: href.split("/")[2] };
if (isExt && tran) {
sendBgMsg(MSG_SAVE_RULE, newRule);
} else {
@@ -112,6 +113,32 @@ export default function Popup({ setShowPopup, translator: tran }) {
})();
}, [tran]);
useEffect(() => {
(async () => {
try {
const commands = {};
if (isExt) {
const res = await sendBgMsg(MSG_COMMAND_SHORTCUTS);
if (!res.error) {
res.data.forEach(({ name, shortcut }) => {
commands[name] = shortcut;
});
}
} else {
const shortcuts = tran.setting.shortcuts;
if (shortcuts) {
Object.entries(shortcuts).forEach(([key, val]) => {
commands[key] = val.join("+");
});
}
}
setCommands(commands);
} catch (err) {
console.log("[query cmds]", err);
}
})();
}, [tran]);
if (!rule) {
return (
<Box minWidth={300}>
@@ -130,7 +157,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
);
}
const { transOpen, translator, fromLang, toLang, textStyle, bgColor } = rule;
const { transOpen, translator, fromLang, toLang, textStyle } = rule;
return (
<Box minWidth={300}>
@@ -154,13 +181,12 @@ export default function Popup({ setShowPopup, translator: tran }) {
onChange={handleTransToggle}
/>
}
label={i18n("translate_alt")}
label={
commands["toggleTranslate"]
? `${i18n("translate_alt")}(${commands["toggleTranslate"]})`
: i18n("translate_alt")
}
/>
{!isExt && (
<Button variant="text" onClick={handleClearCache}>
{i18n("clear_cache")}
</Button>
)}
</Stack>
<TextField
@@ -217,7 +243,11 @@ export default function Popup({ setShowPopup, translator: tran }) {
size="small"
value={textStyle}
name="textStyle"
label={i18n("text_style_alt")}
label={
commands["toggleStyle"]
? `${i18n("text_style_alt")}(${commands["toggleStyle"]})`
: i18n("text_style_alt")
}
onChange={handleChange}
>
{OPT_STYLE_ALL.map((item) => (
@@ -227,7 +257,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
))}
</TextField>
{OPT_STYLE_USE_COLOR.includes(textStyle) && (
{/* {OPT_STYLE_USE_COLOR.includes(textStyle) && (
<TextField
size="small"
name="bgColor"
@@ -235,7 +265,7 @@ export default function Popup({ setShowPopup, translator: tran }) {
label={i18n("bg_color")}
onChange={handleChange}
/>
)}
)} */}
<Stack
direction="row"
@@ -246,6 +276,11 @@ export default function Popup({ setShowPopup, translator: tran }) {
<Button variant="text" onClick={handleSaveRule}>
{i18n("save_rule")}
</Button>
{!isExt && (
<Button variant="text" onClick={handleClearCache}>
{i18n("clear_cache")}
</Button>
)}
<Button variant="text" onClick={handleOpenSetting}>
{i18n("setting")}
</Button>

View File

@@ -1,16 +1,11 @@
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import Stack from "@mui/material/Stack";
import FavBtn from "./FavBtn";
import Typography from "@mui/material/Typography";
const exchangeMap = {
word_third: "第三人称单数",
word_ing: "现在分词",
word_done: "过去式",
word_past: "过去分词",
word_pl: "复数",
word_proto: "词源",
const phonicMap = {
en_phonic: "",
us_phonic: "",
};
export default function DictCont({ dictResult }) {
@@ -26,40 +21,28 @@ export default function DictCont({ dictResult }) {
alignItems="flex-start"
>
<Typography variant="subtitle1" style={{ fontWeight: "bold" }}>
{dictResult.simple_means?.word_name}
{dictResult.src}
</Typography>
<FavBtn word={dictResult.simple_means?.word_name} />
<FavBtn word={dictResult.src} />
</Stack>
{dictResult.simple_means?.symbols?.map(({ ph_en, ph_am, parts }, idx) => (
<Typography key={idx} component="div">
{(ph_en || ph_am) && (
<Typography>{`英 /${ph_en || ""}/ 美 /${ph_am || ""}/`}</Typography>
)}
<ul style={{ margin: "0.5em 0" }}>
{parts.map(({ part, means }, idx) => (
<li key={idx}>
{part ? `[${part}] ${means.join("; ")}` : means.join("; ")}
</li>
))}
</ul>
<Typography component="div">
<Typography>
{dictResult.voice
.map(Object.entries)
.map((item) => item[0])
.map(([key, val]) => `${phonicMap[key] || key} ${val}`)
.join(" ")}
</Typography>
))}
<Typography>
{Object.entries(dictResult.simple_means?.exchange || {})
.map(([key, val]) => `${exchangeMap[key] || key}: ${val.join(", ")}`)
.join("; ")}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
{Object.values(dictResult.simple_means?.tags || {})
.flat()
.filter((item) => item)
.map((item) => (
<Chip label={item} size="small" />
<ul style={{ margin: "0.5em 0" }}>
{dictResult.content[0].mean.map(({ pre, cont }, idx) => (
<li key={idx}>
{pre && `[${pre}] `}
{Object.keys(cont).join("; ")}
</li>
))}
</Stack>
</ul>
</Typography>
</Box>
);
}

View File

@@ -0,0 +1,65 @@
import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip";
import Stack from "@mui/material/Stack";
import FavBtn from "./FavBtn";
import Typography from "@mui/material/Typography";
const exchangeMap = {
word_third: "第三人称单数",
word_ing: "现在分词",
word_done: "过去式",
word_past: "过去分词",
word_pl: "复数",
word_proto: "词源",
};
export default function DictCont({ dictResult }) {
if (!dictResult) {
return;
}
return (
<Box>
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-start"
>
<Typography variant="subtitle1" style={{ fontWeight: "bold" }}>
{dictResult.simple_means?.word_name}
</Typography>
<FavBtn word={dictResult.simple_means?.word_name} />
</Stack>
{dictResult.simple_means?.symbols?.map(({ ph_en, ph_am, parts }, idx) => (
<Typography key={idx} component="div">
{(ph_en || ph_am) && (
<Typography>{`英 /${ph_en || ""}/ 美 /${ph_am || ""}/`}</Typography>
)}
<ul style={{ margin: "0.5em 0" }}>
{parts.map(({ part, means }, idx) => (
<li key={idx}>
{part ? `[${part}] ${means.join("; ")}` : means.join("; ")}
</li>
))}
</ul>
</Typography>
))}
<Typography>
{Object.entries(dictResult.simple_means?.exchange || {})
.map(([key, val]) => `${exchangeMap[key] || key}: ${val.join(", ")}`)
.join("; ")}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
{Object.values(dictResult.simple_means?.tags || {})
.flat()
.filter((item) => item)
.map((item) => (
<Chip label={item} size="small" />
))}
</Stack>
</Box>
);
}

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box";
import { isMobile } from "../../libs/mobile";
function Pointer({
direction,
@@ -16,21 +17,23 @@ function Pointer({
const [origin, setOrigin] = useState(null);
function handlePointerDown(e) {
e.target.setPointerCapture(e.pointerId);
!isMobile && e.target.setPointerCapture(e.pointerId);
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
setOrigin({
x: position.x,
y: position.y,
w: size.w,
h: size.h,
clientX: e.clientX,
clientY: e.clientY,
clientX,
clientY,
});
}
function handlePointerMove(e) {
const { clientX, clientY } = isMobile ? e.targetTouches[0] : e;
if (origin) {
const dx = e.clientX - origin.clientX;
const dy = e.clientY - origin.clientY;
const dx = clientX - origin.clientX;
const dy = clientY - origin.clientY;
let x = position.x;
let y = position.y;
let w = size.w;
@@ -101,16 +104,24 @@ function Pointer({
}
function handlePointerUp(e) {
e.stopPropagation();
setOrigin(null);
}
const touchProps = isMobile
? {
onTouchStart: handlePointerDown,
onTouchMove: handlePointerMove,
onTouchEnd: handlePointerUp,
}
: {
onPointerDown: handlePointerDown,
onPointerMove: handlePointerMove,
onPointerUp: handlePointerUp,
};
return (
<div
{...props}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
>
<div {...props} {...touchProps}>
{children}
</div>
);
@@ -162,6 +173,7 @@ export default function DraggableResizable({
return (
<Box
style={{
touchAction: "none",
position: "fixed",
left: position.x,
top: position.y,

View File

@@ -1,17 +1,27 @@
import { isMobile } from "../../libs/mobile";
export default function TranBtn({ onClick, position, tranboxSetting }) {
const left = position.x + tranboxSetting.btnOffsetX;
const top = position.y + tranboxSetting.btnOffsetY;
const touchProps = isMobile
? {
onTouchEnd: onClick,
}
: {
onMouseUp: onClick,
};
return (
<div
style={{
cursor: "pointer",
position: "absolute",
left: position.x + tranboxSetting.btnOffsetX,
top: position.y + tranboxSetting.btnOffsetY,
left,
top,
zIndex: 2147483647,
}}
onClick={onClick}
onMouseUp={(e) => {
e.stopPropagation();
}}
{...touchProps}
>
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@@ -59,7 +59,8 @@ export default function TranCont({
// 词典
if (isValidWord(text) && toLang.startsWith("zh")) {
if (fromLang === "en" && translator === OPT_TRANS_BAIDU) {
setDictResult(tranRes[2].dict_result);
tranRes[2].type === 1 &&
setDictResult(JSON.parse(tranRes[2].result));
} else {
const dictRes = await apiTranslate({
text,
@@ -67,7 +68,8 @@ export default function TranCont({
fromLang: "en",
toLang: "zh-CN",
});
setDictResult(dictRes[2].dict_result);
dictRes[2].type === 1 &&
setDictResult(JSON.parse(dictRes[2].result));
}
}
} catch (err) {

View File

@@ -2,20 +2,27 @@ import { useState, useEffect, useCallback } from "react";
import TranBtn from "./TranBtn";
import TranBox from "./TranBox";
import { shortcutRegister } from "../../libs/shortcut";
import { sleep } from "../../libs/utils";
import { isGm } from "../../libs/client";
import { sleep, limitNumber } from "../../libs/utils";
import { isGm, isExt } from "../../libs/client";
import { MSG_OPEN_TRANBOX, DEFAULT_TRANBOX_SHORTCUT } from "../../config";
import { isMobile } from "../../libs/mobile";
export default function Slection({ contextMenus, tranboxSetting, transApis }) {
const boxWidth = limitNumber(window.innerWidth, 300, 600);
const boxHeight = limitNumber(window.innerHeight, 200, 400);
const [showBox, setShowBox] = useState(false);
const [showBtn, setShowBtn] = useState(false);
const [selectedText, setSelText] = useState("");
const [text, setText] = useState("");
const [position, setPosition] = useState({ x: 0, y: 0 });
const [boxSize, setBoxSize] = useState({ w: 600, h: 400 });
const [boxSize, setBoxSize] = useState({
w: boxWidth,
h: boxHeight,
});
const [boxPosition, setBoxPosition] = useState({
x: (window.innerWidth - 600) / 2,
y: (window.innerHeight - 400) / 2,
x: (window.innerWidth - boxWidth) / 2,
y: (window.innerHeight - boxHeight) / 2,
});
const handleClick = (e) => {
@@ -42,6 +49,7 @@ export default function Slection({ contextMenus, tranboxSetting, transApis }) {
useEffect(() => {
async function handleMouseup(e) {
e.stopPropagation();
await sleep(10);
const selectedText = window.getSelection()?.toString()?.trim() || "";
@@ -51,18 +59,28 @@ export default function Slection({ contextMenus, tranboxSetting, transApis }) {
return;
}
const { pageX, pageY } = isMobile ? e.changedTouches[0] : e;
!tranboxSetting.hideTranBtn && setShowBtn(true);
// setPosition({ x: e.clientX, y: e.clientY });
setPosition({ x: e.pageX, y: e.pageY });
setPosition({ x: pageX, y: pageY });
}
// todo: mobile support
window.addEventListener("mouseup", handleMouseup);
// window.addEventListener(isMobile ? "touchend" : "mouseup", handleMouseup);
return () => {
window.removeEventListener("mouseup", handleMouseup);
window.removeEventListener(
isMobile ? "touchend" : "mouseup",
handleMouseup
);
};
}, [tranboxSetting.hideTranBtn]);
useEffect(() => {
if (isExt) {
return;
}
const clearShortcut = shortcutRegister(
tranboxSetting.tranboxShortcut || DEFAULT_TRANBOX_SHORTCUT,
handleTranbox
@@ -91,7 +109,7 @@ export default function Slection({ contextMenus, tranboxSetting, transApis }) {
contextMenus &&
menuCommandIds.push(
GM.registerMenuCommand(
"Translate Selected Text (Alt+S)",
"Translate Selected Text",
(event) => {
handleTranbox();
},