Compare commits

...

99 Commits

Author SHA1 Message Date
Gabe
e1dfa35c6c v1.9.0 2025-07-23 00:38:33 +08:00
Gabe
73f80692d3 feat: add format script (prettier) 2025-07-03 19:08:33 +08:00
Gabe
42c7dae495 fix: log 2025-07-03 18:08:49 +08:00
Gabe
b2a1309caa feat: add gemini2 api 2025-07-02 21:54:18 +08:00
Gabe
94bf5f9580 fix: gemini api 2025-07-02 16:37:57 +08:00
Gabe
704ebdc9d7 fix: variable name 2025-07-02 13:38:30 +08:00
Gabe
165da4e559 fix: rule page: show more button 2025-07-01 23:34:16 +08:00
Gabe
d3e3b484bf fix: remove excess code 2025-07-01 22:47:04 +08:00
Gabe
192f8faa5b feat: support keypick for customize API 2025-07-01 22:42:57 +08:00
Gabe
579d5cb0a3 fix: update userscript 2025-07-01 17:55:57 +08:00
Gabe
07fca5b9af fix: #209 2025-07-01 17:03:52 +08:00
Gabe
866a63ab6c feat: move selected translation switch from setting to rule 2025-07-01 16:44:46 +08:00
Gabe
97b4935bc4 feat: api fetch timeout 2025-07-01 12:38:06 +08:00
Gabe
30129abef3 feat: custom API name 2025-07-01 10:54:30 +08:00
Gabe
24d904b32c feat: support volcengine api 2025-06-30 21:34:37 +08:00
Gabe
5f0ce57ead feat: qq transmart 2025-06-27 20:03:58 +08:00
Gabe
51f58d095a feat: add new google translate api (issue: 225, by: Bush2021) 2025-06-27 19:29:00 +08:00
Gabe
d22e3838c4 refactor: deepModels -> tinkIgnore 2025-06-27 16:33:30 +08:00
Gabe
adbb421b7b refactor: fetch timeout 2025-06-27 12:31:32 +08:00
Gabe
eaa47af269 fix: revert old google translate api 2025-06-26 11:13:51 +08:00
Gabe
a6cb5544f8 fix: browser.menus -> browser.contextMenus 2025-06-26 10:01:43 +08:00
Gabe
9e91faa660 Merge pull request #240 from unclemcz/dev
feat:ollama接口设置新增是否禁用深度思考参数
2025-06-25 20:42:31 +08:00
mcz
8636fadc72 接口设置ollama新增是否禁用深度思考参数 2025-06-03 23:07:10 +08:00
Gabe
0621957592 chore: thunderbird 2025-05-18 00:34:20 +08:00
Gabe
8ec06b0c84 chore: defined messenger in package.json 2025-05-18 00:29:06 +08:00
Gabe
d47f8d7ee9 Merge remote-tracking branch 'origin/dev' into dev 2025-05-17 23:22:09 +08:00
Gabe
24f8959525 fix: Ignore html comment elements 2025-05-17 23:19:38 +08:00
Gabe
983740578b Merge pull request #235 from unclemcz/dev
feat:基本设置增加请求超时参数&ollama接口配置增加<think>块忽略参数
2025-05-09 14:09:18 +08:00
Gabe
b5f79ed7cd Merge pull request #232 from Bush2021/fix-workflow
build: fix build errors caused by deprecated actions
2025-05-09 14:08:47 +08:00
Gabe
bbb0e79d4e Merge pull request #231 from Bush2021/fix-google-translate
fix: update API for Google Translate
2025-05-09 14:08:16 +08:00
mcz
471dc05897 ollama接口设置增加<think>块忽略参数 2025-05-01 23:41:08 +08:00
mcz
7a772d2459 在基本设置页面增加接口请求超时时间设置 2025-05-01 20:04:58 +08:00
mcz
1d92421960 Remove the <think></think> tags in qwen3 too. 2025-04-29 19:49:14 +08:00
Bush2021
aeaaf429d7 build: fix build errors caused by deprecated actions 2025-04-16 18:45:41 -04:00
Bush2021
84432e98ae fix: update API for Google Translate 2025-04-16 18:00:54 -04:00
Gabe
77c6102de7 Merge pull request #219 from unclemcz/dev
fix:(Ollama)Remove the <think></think> tags in deepseek-r1.
2025-03-12 17:18:26 +08:00
mcz
ab5dd82169 When using the deepseek-r1 model in Ollama, remove the content between the <think></think> tags. 2025-02-23 11:54:48 +08:00
Gabe
23e7b69dc5 Merge pull request #213 from htyxyt/master
采用更好的方式支持thunderbird
2025-02-19 18:09:01 +08:00
htyxyt
3dc8f393f2 Update package.json 2025-02-18 12:56:29 +08:00
htyxyt
8a2144f263 Update background.js 2025-02-18 12:44:06 +08:00
htyxyt
c1c59caa10 Update package.json 2025-02-18 12:29:43 +08:00
htyxyt
d27ebd90b6 Update package.json 2025-02-18 12:14:02 +08:00
htyxyt
467745c1e9 Update package.json 2025-02-18 11:58:39 +08:00
htyxyt
537378a038 Update background.js 2025-02-18 11:45:57 +08:00
htyxyt
4b5ed30e5b Delete src/background.thunderbird.js 2025-02-18 11:42:57 +08:00
htyxyt
52b7f6a225 Update background.js 2025-02-18 11:42:47 +08:00
htyxyt
f31675d8a2 Update index.js 2025-02-18 11:42:05 +08:00
htyxyt
dd46a8450c Update config-overrides.js 2025-02-18 11:41:28 +08:00
htyxyt
b0843f7d66 Update package.json 2025-02-18 11:39:58 +08:00
htyxyt
daadc0195c Update background.thunderbird.js 2025-02-18 10:25:12 +08:00
Gabe
298dec6957 Merge pull request #210 from htyxyt/master
添加对Thunderbird的支持
2025-02-17 23:48:40 +08:00
Gabe
bf39d85dfa Merge pull request #212 from qonmnop/patch-1
允许用户自定义 hooks 中重写signal 参数,自定义超时时间
2025-02-17 23:30:19 +08:00
htyxyt
30a9de25a8 Update config-overrides.js 2025-02-17 14:01:17 +08:00
htyxyt
af1ecf0bd4 Update package.json 2025-02-17 14:00:35 +08:00
qonmnop
fe55a2cd3c 允许用户自定义 hooks 中重写signal 参数,自定义超时时间 2025-02-17 12:14:10 +08:00
htyxyt
5a33d4e57e Add files via upload 2025-02-17 11:57:40 +08:00
htyxyt
7f46a9023c Delete public/background.thunderbird.js 2025-02-17 11:57:12 +08:00
htyxyt
dfd943b621 Rename manifest.thunderfird.json to manifest.thunderbird.json 2025-02-17 11:10:29 +08:00
htyxyt
7007d0d922 Update package.json 2025-02-17 10:39:08 +08:00
htyxyt
601678500d Update package.json 2025-02-14 15:23:03 +08:00
htyxyt
9bfb504381 Add files via upload 2025-02-14 15:13:40 +08:00
htyxyt
8a03b0cf15 Delete src/background.thunderfird.js 2025-02-14 15:12:57 +08:00
htyxyt
11ba89de0a Update manifest.thunderfird.json 2025-02-14 14:57:38 +08:00
htyxyt
bac7f62eea Update background.thunderfird.js 2025-02-14 14:56:58 +08:00
htyxyt
eef90ea02b Delete src/popup.thunderfird.js 2025-02-14 14:56:10 +08:00
htyxyt
0650df534a Delete src/content.thunderfird.js 2025-02-14 14:56:00 +08:00
htyxyt
9ef8c8b823 Delete src/options.thunderfird.js 2025-02-14 14:55:49 +08:00
htyxyt
d7e08da0b2 Add files via upload 2025-02-13 14:53:06 +08:00
htyxyt
ef361e0798 Add files via upload 2025-02-13 14:50:33 +08:00
Gabe
6855332092 fix: modify systemPrompt & userPrompt 2024-11-30 00:41:29 +08:00
Gabe
121d523e02 Merge remote-tracking branch 'origin/dev' into dev 2024-11-30 00:09:48 +08:00
Gabe
42a375c4c7 Merge pull request #188 from unclemcz/dev
给ollama增加system message
2024-11-30 00:07:14 +08:00
Gabe
a1dd705d97 fix: update pnpm-lock file 2024-11-29 21:14:30 +08:00
mcz
71f90b36ca 给ollama增加system message 2024-09-30 16:41:58 +08:00
Gabe
37facdc3c1 Merge pull request #186 from hoilc/dev
feat: enhance openai prompt
2024-09-26 23:38:34 +08:00
hoilc
66b4f547ff feat: enhance openai prompt 2024-09-25 14:03:12 +08:00
Gabe
d27b9c7f2d Merge pull request #185 from hoilc/dev
feat: support claude api
2024-09-24 23:30:00 +08:00
hoilc
278ff9c6bc feat: support claude api 2024-09-23 18:22:19 +08:00
Gabe Yuan
d6fe1ce9d7 fix: try detect language only when fromLang is auto 2024-05-30 21:05:05 +08:00
Gabe Yuan
0bfa5256b8 fix: simplify keepSelector logic 2024-05-30 17:18:39 +08:00
Gabe Yuan
72ccfc8aec v1.8.11 2024-05-23 20:06:48 +08:00
Gabe Yuan
d117c5dc10 feat: baidu dict can be disabled 2024-05-23 00:08:10 +08:00
Gabe Yuan
9312783f44 feat: lang detector can be selected 2024-05-22 23:33:30 +08:00
Gabe Yuan
e5b16ebfd3 Merge remote-tracking branch 'origin/master' into dev 2024-05-22 10:19:04 +08:00
Gabe Yuan
5d1d65c2d3 feat: the temperature and maxTokens of the openai can be configured 2024-05-21 23:15:46 +08:00
Gabe
9ca1309cec Merge pull request #126 from kebyn/master
fix: option translation
2024-05-21 20:02:53 +08:00
kebyn
a03afc05f5 fix: option translation 2024-05-21 08:56:09 +00:00
Gabe Yuan
0198963584 fix: deeplx: replace auto to blank string 2024-05-21 11:55:17 +08:00
Gabe Yuan
58e745d967 v1.8.10 2024-05-17 10:38:11 +08:00
Gabe Yuan
377e347d68 feat: support translate hooks 2024-05-15 11:07:13 +08:00
Gabe Yuan
bac0704d3d feat: download and upload settings 2024-05-12 20:24:40 +08:00
Gabe Yuan
d2ff46edf6 fix: show full gemini url 2024-05-12 16:25:20 +08:00
Gabe Yuan
f908372b4e feat: support hook for custom api 2024-05-12 16:10:11 +08:00
Gabe Yuan
5d44ff4913 v1.8.9 2024-04-28 22:23:53 +08:00
Gabe Yuan
4c9aa66048 feat: support ollama api 2024-04-28 21:45:20 +08:00
Gabe Yuan
b6a09b99ab feat: support ollama api 2024-04-28 21:43:20 +08:00
Gabe Yuan
3a0dcb1a52 feat: add more openai translator 2024-04-28 16:58:09 +08:00
Gabe Yuan
5015503b4c feat: hide transbox header when mouseleave 2024-04-28 14:56:49 +08:00
Gabe Yuan
16423feea4 fix: update readme 2024-04-21 22:21:29 +08:00
39 changed files with 9081 additions and 5232 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.8.8
REACT_APP_VERSION=1.9.0
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator

View File

@@ -7,28 +7,28 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 8.7.6
- uses: actions/setup-node@v3
version: latest
- uses: actions/setup-node@v4
with:
node-version: "18.17.0"
node-version: latest
cache: "pnpm"
- run: pnpm install
- run: pnpm build
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: build
deploy-web:
needs: build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build-artifacts
path: build
@@ -37,7 +37,8 @@ jobs:
with:
folder: build/web
create-release:
runs-on: ubuntu-22.04
needs: build
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create-release.outputs.upload_url }}
steps:
@@ -55,10 +56,10 @@ jobs:
strategy:
matrix:
client: ["chrome", "edge", "firefox", "userscript"]
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: build-artifacts
path: build

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
build
public
package.json

24
.prettierrc Normal file
View File

@@ -0,0 +1,24 @@
{
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"singleAttributePerLine": false,
"bracketSameLine": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"embeddedLanguageFormatting": "auto",
"vueIndentScriptAndStyle": false,
"experimentalTernaries": false,
"parser": "babel"
}

View File

@@ -1,5 +1,7 @@
# KISS Translator
English | [简体中文](README.md)
A simple, open source [bilingual translation extension & Greasemonkey script](https://github.com/fishjar/kiss-translator).
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)

View File

@@ -1,5 +1,7 @@
# 简约翻译
[English](README.en.md) | 简体中文
一个简约、开源的 [双语对照翻译扩展 & 油猴脚本](https://github.com/fishjar/kiss-translator)。
[kiss-translator.webm](https://github.com/fishjar/kiss-translator/assets/1157624/f7ba8a5c-e4a8-4d5a-823a-5c5c67a0a47f)

View File

@@ -92,6 +92,7 @@ const userscriptWebpack = (config, env) => {
// @grant GM.info
// @grant unsafeWindow
// @connect translate.googleapis.com
// @connect translate-pa.googleapis.com
// @connect api-edge.cognitive.microsofttranslator.com
// @connect edge.microsoft.com
// @connect api-free.deepl.com
@@ -108,10 +109,10 @@ const userscriptWebpack = (config, env) => {
// @connect dav.jianguoyun.com
// @connect fanyi.baidu.com
// @connect transmart.qq.com
// @connect localhost:3000
// @connect 127.0.0.1:3000
// @connect localhost:1188
// @connect 127.0.0.1:1188
// @connect niutrans.com
// @connect translate.volcengine.com
// @connect localhost
// @connect 127.0.0.1
// @run-at document-end
// ==/UserScript==

View File

@@ -1,7 +1,7 @@
{
"name": "kiss-translator",
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
"version": "1.8.8",
"version": "1.9.0",
"author": "Gabe<yugang2002@gmail.com>",
"private": true,
"dependencies": {
@@ -17,6 +17,7 @@
"react-markdown": "^8.0.7",
"react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
"sval": "^0.5.2",
"webdav": "^5.3.0",
"webextension-polyfill": "^0.10.0"
},
@@ -25,13 +26,15 @@
"start:userscript": "REACT_APP_CLIENT=userscript react-app-rewired start",
"build:chrome": "rm -rf build/chrome && BUILD_PATH=./build/chrome REACT_APP_CLIENT=chrome react-app-rewired build && rm build/chrome/content.html",
"build:edge": "rm -rf build/edge && cp -r build/chrome build/edge",
"build:thunderbird": "rm -rf build/thunderbird && BUILD_PATH=./build/thunderbird REACT_APP_CLIENT=thunderbird react-app-rewired build && rm build/thunderbird/content.html && cp ./build/thunderbird/manifest.thunderbird.json ./build/thunderbird/manifest.json && rm build/*/manifest.thunderbird.json",
"build:firefox": "rm -rf build/firefox && cp -r build/chrome build/firefox && cat ./build/firefox/manifest.firefox.json > ./build/firefox/manifest.json && rm build/*/manifest.firefox.json",
"build:web": "rm -rf build/web && BUILD_PATH=./build/web REACT_APP_CLIENT=userscript react-app-rewired build",
"build:userscript-ios": "file1=build/web/kiss-translator.user.js file2=build/web/kiss-translator-ios-safari.user.js && cp $file1 $file2 && sed -i 's|// @grant unsafeWindow|// @inject-into content|g' $file2",
"build:userscript": "rm -rf build/userscript && mkdir build/userscript && cp build/web/*.user.js build/userscript/",
"build:rules": "babel-node src/rules.js",
"build": "pnpm build:chrome && pnpm build:edge && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules",
"pack": "cd build && zip -r chrome.zip chrome && zip -r edge.zip edge && cd firefox && zip -r ../firefox.zip .",
"build": "pnpm format && pnpm build:chrome && pnpm build:edge && pnpm build:thunderbird && pnpm build:firefox && pnpm build:web && pnpm build:userscript-ios && pnpm build:userscript && pnpm build:rules",
"zip": "cd build && zip -r chrome.zip chrome && zip -r edge.zip edge && (cd firefox && zip -r ../firefox.zip .) && (cd thunderbird && zip -r ../thunderbird.zip .)",
"format": "prettier --write \"**/*.{js,json,html}\"",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
@@ -43,7 +46,8 @@
"globals": {
"GM": true,
"unsafeWindow": true,
"globalThis": true
"globalThis": true,
"messenger": true
}
},
"browserslist": {
@@ -63,6 +67,7 @@
"@babel/node": "^7.22.19",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.22.20",
"prettier": "3.6.2",
"react-app-rewired": "^2.2.1"
}
}

12040
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
@@ -16,7 +16,7 @@
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener('DOMContentLoaded', function () {
// (() => {
// var shadow = document.querySelector("#shadow1");
// var root = shadow.attachShadow({ mode: "open" });
@@ -54,8 +54,8 @@
// }, 1000);
setTimeout(function () {
var el = document.querySelector("h2>p>span");
el.innerText = "hello world";
var el = document.querySelector('h2>p>span');
el.innerText = 'hello world';
}, 1000);
});
</script>
@@ -105,28 +105,26 @@
<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
coming to Google Cloud with the C3A instances.
The <span>embargo</span> has just lifted to confirm that AmpereOne is coming to
Google Cloud with the C3A instances.
<br />
But these upcoming instances for now are only in private preview form.
<br />
<br />
Needless to say I also haven't had any AmpereOne access to check out the
performance and power efficiency of these new Arm server processors from
Ampere Computing.
performance and power efficiency of these new Arm server processors from Ampere
Computing.
<br />
</div>
<h2>
<p>
<span
>React is a JavaScript library for building user interfaces.</span
>
<span>React is a JavaScript library for building user interfaces.</span>
</p>
</h2>
<hr />
<input id="input1" style="width: 80%;" />
<input id="input1" style="width: 80%" />
<hr />
<textarea id="textarea1" style="width: 80%;">test</textarea>
<textarea id="textarea1" style="width: 80%">test</textarea>
<hr />
<div id="addtitle"></div>
<h2>Shadow 1</h2>
@@ -166,15 +164,47 @@
<br />
<br />
<h2>
React Server Components (or RSC) is a new application architecture
designed by the React team.
React Server Components (or RSC) is a new application architecture designed by the
React team.
</h2>
<iframe
id="iframe1"
width="800px"
height="600px"
src="http://localhost:3000/index.html"
></iframe>
src="http://localhost:3000/index.html"></iframe>
<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 />
<h2>Weve first shared our research on RSC in an introductory talk and an RFC.</h2>
<br />
<br />
<br />
@@ -208,52 +238,10 @@
<br />
<br />
<h2>
Weve first shared our research on RSC in an introductory talk and an
RFC.
To recap them, we are introducing a new kind of component—Server Components—that
run ahead of time and are excluded from your JavaScript bundle.
</h2>
<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 />
<h2>
To recap them, we are introducing a new kind of component—Server
Components—that run ahead of time and are excluded from your JavaScript
bundle.
</h2>
<iframe
id="iframe2"
width="800px"
height="600px"
src="https://react.dev/"
></iframe>
<iframe id="iframe2" width="800px" height="600px" src="https://react.dev/"></iframe>
<br />
<br />
<br />
@@ -288,15 +276,14 @@
<br />
<div class="cont cont1">
<h2>
Server Components can run during the build, letting you read from the
filesystem or fetch static content.
Server Components can run during the build, letting you read from the filesystem
or fetch static content.
</h2>
<ul>
<li>
They can also run on the server, letting you access your data layer
without having to build an API. You can pass data by props from
Server Components to the interactive Client Components in the
browser.
They can also run on the server, letting you access your data layer without
having to build an API. You can pass data by props from Server Components to
the interactive Client Components in the browser.
</li>
<li>以声明式编写 UI可以让你的代码更加可靠且方便调试。</li>
</ul>
@@ -315,14 +302,14 @@
<br />
<div class="cont cont2">
<h2>
Since our last update, we have merged the React Server Components RFC
to ratify the proposal.
Since our last update, we have merged the React Server Components RFC to ratify
the proposal.
</h2>
<ul>
<li>
RSC combines the simple “request/response” mental model of
server-centric Multi-Page Apps with the seamless interactivity of
client-centric Single-Page Apps, giving you the best of both worlds.
RSC combines the simple “request/response” mental model of server-centric
Multi-Page Apps with the seamless interactivity of client-centric Single-Page
Apps, giving you the best of both worlds.
</li>
<li>
React 使创建交互式 UI

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />

View File

@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.8.8",
"version": "1.9.0",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",

View File

@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.8.8",
"version": "1.9.0",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",
@@ -45,12 +45,7 @@
"description": "__MSG_open_options__"
}
},
"permissions": [
"storage",
"contextMenus",
"scripting",
"declarativeNetRequest"
],
"permissions": ["storage", "contextMenus", "scripting", "declarativeNetRequest"],
"host_permissions": ["<all_urls>"],
"icons": {
"16": "images/logo16.png",

View File

@@ -0,0 +1,78 @@
{
"manifest_version": 2,
"name": "__MSG_app_name__",
"description": "__MSG_app_description__",
"version": "1.9.0",
"default_locale": "en",
"author": "Gabe<yugang2002@gmail.com>",
"homepage_url": "https://github.com/fishjar/kiss-translator",
"browser_specific_settings": {
"gecko": {
"id": "yugang2002@gmail.com",
"strict_min_version": "78.0"
}
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"js": ["content.js"],
"matches": ["<all_urls>"],
"all_frames": true
}
],
"commands": {
"_execute_browser_action": {
"suggested_key": {
"default": "Alt+K"
}
},
"toggleTranslate": {
"suggested_key": {
"default": "Alt+Q"
},
"description": "__MSG_toggle_translate__"
},
"openTranbox": {
"suggested_key": {
"default": "Alt+S"
},
"description": "__MSG_open_tranbox__"
},
"toggleStyle": {
"suggested_key": {
"default": "Alt+C"
},
"description": "__MSG_toggle_style__"
},
"openOptions": {
"description": "__MSG_open_options__"
}
},
"permissions": [
"<all_urls>",
"storage",
"menus",
"messagesModify",
"scripting",
"declarativeNetRequest"
],
"icons": {
"16": "images/logo16.png",
"32": "images/logo32.png",
"48": "images/logo48.png",
"128": "images/logo128.png"
},
"browser_action": {
"default_icon": {
"128": "images/logo128.png"
},
"default_title": "__MSG_app_name__",
"default_popup": "popup.html"
},
"options_ui": {
"page": "options.html",
"open_in_tab": true
}
}

View File

@@ -2,6 +2,7 @@ import queryString from "query-string";
import { fetchData } from "../libs/fetch";
import {
OPT_TRANS_GOOGLE,
OPT_TRANS_GOOGLE_2,
OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPL,
OPT_TRANS_DEEPLFREE,
@@ -9,9 +10,17 @@ import {
OPT_TRANS_NIUTRANS,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_VOLCENGINE,
OPT_TRANS_OPENAI,
OPT_TRANS_OPENAI_2,
OPT_TRANS_OPENAI_3,
OPT_TRANS_GEMINI,
OPT_TRANS_GEMINI_2,
OPT_TRANS_CLAUDE,
OPT_TRANS_CLOUDFLAREAI,
OPT_TRANS_OLLAMA,
OPT_TRANS_OLLAMA_2,
OPT_TRANS_OLLAMA_3,
OPT_TRANS_CUSTOMIZE,
OPT_TRANS_CUSTOMIZE_2,
OPT_TRANS_CUSTOMIZE_3,
@@ -19,6 +28,8 @@ import {
OPT_TRANS_CUSTOMIZE_5,
URL_CACHE_TRAN,
KV_SALT_SYNC,
URL_GOOGLE_TRAN,
URL_MICROSOFT_LANGDETECT,
URL_BAIDU_LANGDETECT,
URL_BAIDU_SUGGEST,
URL_BAIDU_TTS,
@@ -26,8 +37,12 @@ import {
URL_TENCENT_TRANSMART,
OPT_LANGS_TENCENT,
OPT_LANGS_SPECIAL,
OPT_LANGS_MICROSOFT,
} from "../config";
import { sha256 } from "../libs/utils";
import interpreter from "../libs/interpreter";
import { msAuth } from "../libs/auth";
import { kissLog } from "../libs/log";
/**
* 同步数据
@@ -53,6 +68,52 @@ export const apiSyncData = async (url, key, data) =>
*/
export const apiFetch = (url) => fetchData(url);
/**
* Google语言识别
* @param {*} text
* @returns
*/
export const apiGoogleLangdetect = async (text) => {
const params = {
client: "gtx",
dt: "t",
dj: 1,
ie: "UTF-8",
sl: "auto",
tl: "zh-CN",
q: text,
};
const input = `${URL_GOOGLE_TRAN}?${queryString.stringify(params)}`;
const res = await fetchData(input, {
headers: {
"Content-type": "application/json",
},
useCache: true,
});
return res.src;
};
/**
* Microsoft语言识别
* @param {*} text
* @returns
*/
export const apiMicrosoftLangdetect = async (text) => {
const [token] = await msAuth();
const res = await fetchData(URL_MICROSOFT_LANGDETECT, {
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${token}`,
},
method: "POST",
body: JSON.stringify([{ Text: text }]),
useCache: true,
});
return OPT_LANGS_MICROSOFT.get(res[0].language) ?? res[0].language;
};
/**
* 百度语言识别
* @param {*} text
@@ -166,7 +227,7 @@ export const apiTranslate = async ({
OPT_LANGS_SPECIAL[translator].get("auto");
const to = OPT_LANGS_SPECIAL[translator].get(toLang);
if (!to) {
console.log(`[trans] target lang: ${toLang} not support`);
kissLog(`target lang: ${toLang} not support`, "translate");
return [trText, isSame];
}
@@ -202,6 +263,10 @@ export const apiTranslate = async ({
trText = res.sentences.map((item) => item.trans).join(" ");
isSame = to === res.src;
break;
case OPT_TRANS_GOOGLE_2:
trText = res?.[0]?.[0] || "";
isSame = to === res.src;
break;
case OPT_TRANS_MICROSOFT:
trText = res
.map((item) => item.translations.map((item) => item.text).join(" "))
@@ -240,10 +305,17 @@ export const apiTranslate = async ({
}
break;
case OPT_TRANS_TENCENT:
trText = res.auto_translation;
trText = res?.auto_translation?.[0];
isSame = text === trText;
break;
case OPT_TRANS_VOLCENGINE:
trText = res?.translation || "";
isSame = to === res?.detected_language;
break;
case OPT_TRANS_OPENAI:
case OPT_TRANS_OPENAI_2:
case OPT_TRANS_OPENAI_3:
case OPT_TRANS_GEMINI_2:
trText = res?.choices?.map((item) => item.message.content).join(" ");
isSame = text === trText;
break;
@@ -253,36 +325,39 @@ export const apiTranslate = async ({
.join(" ");
isSame = text === trText;
break;
case OPT_TRANS_CLAUDE:
trText = res?.content?.map((item) => item.text).join(" ");
isSame = text === trText;
break;
case OPT_TRANS_CLOUDFLAREAI:
trText = res?.result?.translated_text;
isSame = text === trText;
break;
case OPT_TRANS_OLLAMA:
case OPT_TRANS_OLLAMA_2:
case OPT_TRANS_OLLAMA_3:
const { thinkIgnore = "" } = apiSetting;
const deepModels = thinkIgnore.split(",").filter((model) => model.trim());
if (deepModels.some((model) => res?.model?.startsWith(model))) {
trText = res?.response.replace(/<think>[\s\S]*<\/think>/i, "");
} else {
trText = res?.response;
}
isSame = text === trText;
break;
case OPT_TRANS_CUSTOMIZE:
case OPT_TRANS_CUSTOMIZE_2:
case OPT_TRANS_CUSTOMIZE_3:
case OPT_TRANS_CUSTOMIZE_4:
case OPT_TRANS_CUSTOMIZE_5:
const { resHook } = apiSetting;
if (resHook?.trim()) {
interpreter.run(`exports.resHook = ${resHook}`);
[trText, isSame] = interpreter.exports.resHook(res, text, from, to);
} else {
trText = res.text;
isSame = to === res.from;
const { customOption } = apiSetting;
if (customOption?.trim()) {
try {
const opt = JSON.parse(customOption);
const textPattern = opt.resPattern?.text;
const fromPattern = opt.resPattern?.from;
if (textPattern) {
trText = textPattern.split(".").reduce((pre, cur) => pre[cur], res);
}
if (fromPattern) {
isSame =
to === fromPattern.split(".").reduce((pre, cur) => pre[cur], res);
}
} catch (err) {
throw new Error(`custom option parse err: ${err}`);
}
}
break;
default:
}

View File

@@ -1,6 +1,7 @@
import queryString from "query-string";
import {
OPT_TRANS_GOOGLE,
OPT_TRANS_GOOGLE_2,
OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPL,
OPT_TRANS_DEEPLFREE,
@@ -8,9 +9,17 @@ import {
OPT_TRANS_NIUTRANS,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_VOLCENGINE,
OPT_TRANS_OPENAI,
OPT_TRANS_OPENAI_2,
OPT_TRANS_OPENAI_3,
OPT_TRANS_GEMINI,
OPT_TRANS_GEMINI_2,
OPT_TRANS_CLAUDE,
OPT_TRANS_CLOUDFLAREAI,
OPT_TRANS_OLLAMA,
OPT_TRANS_OLLAMA_2,
OPT_TRANS_OLLAMA_3,
OPT_TRANS_CUSTOMIZE,
OPT_TRANS_CUSTOMIZE_2,
OPT_TRANS_CUSTOMIZE_3,
@@ -18,15 +27,18 @@ import {
OPT_TRANS_CUSTOMIZE_5,
URL_MICROSOFT_TRAN,
URL_TENCENT_TRANSMART,
URL_VOLCENGINE_TRAN,
INPUT_PLACE_URL,
INPUT_PLACE_FROM,
INPUT_PLACE_TO,
INPUT_PLACE_TEXT,
INPUT_PLACE_KEY,
INPUT_PLACE_MODEL,
} from "../config";
import { msAuth } from "../libs/auth";
import { genDeeplFree } from "./deepl";
import { genBaidu } from "./baidu";
import interpreter from "../libs/interpreter";
const keyMap = new Map();
const urlMap = new Map();
@@ -72,6 +84,20 @@ const genGoogle = ({ text, from, to, url, key }) => {
return [input, init];
};
const genGoogle2 = ({ text, from, to, url, key }) => {
const body = JSON.stringify([[[text], from, to], "wt_lib"]);
const init = {
method: "POST",
headers: {
"Content-Type": "application/json+protobuf",
"X-Goog-API-Key": key,
},
body,
};
return [url, init];
};
const genMicrosoft = async ({ text, from, to }) => {
const [token] = await msAuth();
const params = {
@@ -156,10 +182,14 @@ const genNiuTrans = ({ text, from, to, url, key, dictNo, memoryNo }) => {
const genTencent = ({ text, from, to }) => {
const data = {
header: {
fn: "auto_translation_block",
fn: "auto_translation",
client_key:
"browser-chrome-110.0.0-Mac OS-df4bd4c5-a65d-44b2-a40f-42f34f3535f2-1677486696487",
},
type: "plain",
model_category: "normal",
source: {
text_block: text,
text_list: [text],
lang: from,
},
target: {
@@ -170,6 +200,9 @@ const genTencent = ({ text, from, to }) => {
const init = {
headers: {
"Content-Type": "application/json",
"user-agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
referer: "https://transmart.qq.com/zh-CN/index",
},
method: "POST",
body: JSON.stringify(data),
@@ -178,25 +211,63 @@ const genTencent = ({ text, from, to }) => {
return [URL_TENCENT_TRANSMART, init];
};
const genOpenAI = ({ text, from, to, url, key, prompt, model }) => {
prompt = prompt
const genVolcengine = ({ text, from, to }) => {
const data = {
source_language: from,
target_language: to,
text: text,
};
const init = {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify(data),
};
return [URL_VOLCENGINE_TRAN, init];
};
const genOpenAI = ({
text,
from,
to,
url,
key,
systemPrompt,
userPrompt,
model,
temperature,
maxTokens,
}) => {
// 兼容历史上作为systemPrompt的prompt如果prompt中不包含带翻译文本则添加文本到prompt末尾
// if (!prompt.includes(INPUT_PLACE_TEXT)) {
// prompt += `\nSource Text: ${INPUT_PLACE_TEXT}`;
// }
systemPrompt = systemPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to);
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
userPrompt = userPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
const data = {
model,
messages: [
{
role: "system",
content: prompt,
content: systemPrompt,
},
{
role: "user",
content: text,
content: userPrompt,
},
],
temperature: 0,
max_tokens: 256,
temperature,
max_tokens: maxTokens,
};
const init = {
@@ -212,26 +283,50 @@ const genOpenAI = ({ text, from, to, url, key, prompt, model }) => {
return [url, init];
};
const genGemini = ({ text, from, to, url, key, prompt, model }) => {
prompt = prompt
const genGemini = ({
text,
from,
to,
url,
key,
systemPrompt,
userPrompt,
model,
temperature,
maxTokens,
}) => {
url = url
.replaceAll(INPUT_PLACE_MODEL, model)
.replaceAll(INPUT_PLACE_KEY, key);
systemPrompt = systemPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
userPrompt = userPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
const data = {
contents: [
{
// role: "user",
parts: [
{
text: prompt,
system_instruction: {
parts: {
text: systemPrompt,
},
],
},
],
contents: {
role: "user",
parts: {
text: userPrompt,
},
},
generationConfig: {
maxOutputTokens: maxTokens,
temperature,
// topP: 0.8,
// topK: 10,
},
};
const input = `${url}/${model}:generateContent?key=${key}`;
const init = {
headers: {
"Content-type": "application/json",
@@ -240,7 +335,145 @@ const genGemini = ({ text, from, to, url, key, prompt, model }) => {
body: JSON.stringify(data),
};
return [input, init];
return [url, init];
};
const genGemini2 = ({
text,
from,
to,
url,
key,
systemPrompt,
userPrompt,
model,
temperature,
reasoningEffort,
}) => {
systemPrompt = systemPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
userPrompt = userPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
const data = {
model,
reasoning_effort: reasoningEffort,
messages: [
{
role: "system",
content: systemPrompt,
},
{
role: "user",
content: userPrompt,
},
],
temperature,
};
const init = {
headers: {
"Content-type": "application/json",
Authorization: `Bearer ${key}`,
},
method: "POST",
body: JSON.stringify(data),
};
return [url, init];
};
const genClaude = ({
text,
from,
to,
url,
key,
systemPrompt,
userPrompt,
model,
temperature,
maxTokens,
}) => {
systemPrompt = systemPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
userPrompt = userPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
const data = {
model,
system: systemPrompt,
messages: [
{
role: "user",
content: userPrompt,
},
],
temperature,
max_tokens: maxTokens,
};
const init = {
headers: {
"Content-type": "application/json",
"anthropic-version": "2023-06-01",
"x-api-key": key,
},
method: "POST",
body: JSON.stringify(data),
};
return [url, init];
};
const genOllama = ({
text,
from,
to,
think,
url,
key,
systemPrompt,
userPrompt,
model,
}) => {
systemPrompt = systemPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
userPrompt = userPrompt
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text);
const data = {
model,
system: systemPrompt,
prompt: userPrompt,
think: think,
stream: false,
};
const init = {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify(data),
};
if (key) {
init.headers.Authorization = `Bearer ${key}`;
}
return [url, init];
};
const genCloudflareAI = ({ text, from, to, url, key }) => {
@@ -262,20 +495,27 @@ const genCloudflareAI = ({ text, from, to, url, key }) => {
return [url, init];
};
const genCustom = ({ text, from, to, url, key, customOption }) => {
const replaceInput = (str) =>
str
const genCustom = ({ text, from, to, url, key, reqHook }) => {
url = url
.replaceAll(INPUT_PLACE_URL, url)
.replaceAll(INPUT_PLACE_FROM, from)
.replaceAll(INPUT_PLACE_TO, to)
.replaceAll(INPUT_PLACE_TEXT, text.replaceAll(`"`, `\n`))
.replaceAll(INPUT_PLACE_TEXT, text)
.replaceAll(INPUT_PLACE_KEY, key);
let init = {};
if (reqHook?.trim()) {
interpreter.run(`exports.reqHook = ${reqHook}`);
[url, init] = interpreter.exports.reqHook(text, from, to, url, key);
return [url, init];
}
const data = {
text,
from,
to,
};
const init = {
init = {
headers: {
"Content-type": "application/json",
},
@@ -285,23 +525,6 @@ const genCustom = ({ text, from, to, url, key, customOption }) => {
if (key) {
init.headers.Authorization = `Bearer ${key}`;
}
url = replaceInput(url);
if (customOption?.trim()) {
try {
const opt = JSON.parse(replaceInput(customOption));
opt.url && (url = opt.url);
opt.headers && (init.headers = opt.headers);
opt.method && (init.method = opt.method);
if (init.method === "GET") {
delete init.body;
} else {
opt.body && (init.body = JSON.stringify(opt.body));
}
} catch (err) {
throw new Error(`custom option parse err: ${err}`);
}
}
return [url, init];
};
@@ -317,9 +540,21 @@ export const genTransReq = ({ translator, text, from, to }, apiSetting) => {
switch (translator) {
case OPT_TRANS_DEEPL:
case OPT_TRANS_OPENAI:
case OPT_TRANS_OPENAI_2:
case OPT_TRANS_OPENAI_3:
case OPT_TRANS_GEMINI:
case OPT_TRANS_GEMINI_2:
case OPT_TRANS_CLAUDE:
case OPT_TRANS_CLOUDFLAREAI:
case OPT_TRANS_OLLAMA:
case OPT_TRANS_OLLAMA_2:
case OPT_TRANS_OLLAMA_3:
case OPT_TRANS_NIUTRANS:
case OPT_TRANS_CUSTOMIZE:
case OPT_TRANS_CUSTOMIZE_2:
case OPT_TRANS_CUSTOMIZE_3:
case OPT_TRANS_CUSTOMIZE_4:
case OPT_TRANS_CUSTOMIZE_5:
args.key = keyPick(translator, args.key, keyMap);
break;
case OPT_TRANS_DEEPLX:
@@ -331,6 +566,8 @@ export const genTransReq = ({ translator, text, from, to }, apiSetting) => {
switch (translator) {
case OPT_TRANS_GOOGLE:
return genGoogle(args);
case OPT_TRANS_GOOGLE_2:
return genGoogle2(args);
case OPT_TRANS_MICROSOFT:
return genMicrosoft(args);
case OPT_TRANS_DEEPL:
@@ -345,12 +582,24 @@ export const genTransReq = ({ translator, text, from, to }, apiSetting) => {
return genBaidu(args);
case OPT_TRANS_TENCENT:
return genTencent(args);
case OPT_TRANS_VOLCENGINE:
return genVolcengine(args);
case OPT_TRANS_OPENAI:
case OPT_TRANS_OPENAI_2:
case OPT_TRANS_OPENAI_3:
return genOpenAI(args);
case OPT_TRANS_GEMINI:
return genGemini(args);
case OPT_TRANS_GEMINI_2:
return genGemini2(args);
case OPT_TRANS_CLAUDE:
return genClaude(args);
case OPT_TRANS_CLOUDFLAREAI:
return genCloudflareAI(args);
case OPT_TRANS_OLLAMA:
case OPT_TRANS_OLLAMA_2:
case OPT_TRANS_OLLAMA_3:
return genOllama(args);
case OPT_TRANS_CUSTOMIZE:
case OPT_TRANS_CUSTOMIZE_2:
case OPT_TRANS_CUSTOMIZE_3:

View File

@@ -17,6 +17,7 @@ import {
CMD_TOGGLE_STYLE,
CMD_OPEN_OPTIONS,
CMD_OPEN_TRANBOX,
CLIENT_THUNDERBIRD,
} from "./config";
import { getSettingWithDefault, tryInitDefaultData } from "./libs/storage";
import { trySyncSettingAndRules } from "./libs/sync";
@@ -44,7 +45,7 @@ const REMOVE_HEADERS = [
async function addContextMenus(contextMenuType = 1) {
// 添加前先删除,避免重复ID的错误
try {
await browser.contextMenus.removeAll();
await browser.menus.removeAll();
} catch (err) {
//
}
@@ -123,12 +124,26 @@ async function updateCspRules(csplist = DEFAULT_CSPLIST.join(",\n")) {
}
}
/**
* 注册邮件显示脚本
*/
async function registerMsgDisplayScript() {
await messenger.messageDisplayScripts.register({
js: [{ file: "/content.js" }],
});
}
/**
* 插件安装
*/
browser.runtime.onInstalled.addListener(() => {
tryInitDefaultData();
//在thunderbird中注册脚本
if (process.env.REACT_APP_CLIENT === CLIENT_THUNDERBIRD) {
registerMsgDisplayScript();
}
// 右键菜单
addContextMenus();
@@ -151,6 +166,11 @@ browser.runtime.onStartup.addListener(async () => {
tryClearCaches();
}
//在thunderbird中注册脚本
if (process.env.REACT_APP_CLIENT === CLIENT_THUNDERBIRD) {
registerMsgDisplayScript();
}
// 右键菜单
// firefox重启后菜单会消失,故重复添加
addContextMenus(contextMenuType);

View File

@@ -72,7 +72,7 @@ function runtimeListener(translator) {
default:
return { error: `message action is unavailable: ${action}` };
}
return { data: translator.rule };
return { rule: translator.rule, setting: translator.setting };
});
}
@@ -135,14 +135,18 @@ async function showFab(translator) {
* @param {*} param0
* @returns
*/
function showTransbox({
function showTransbox(
{
contextMenuType,
tranboxSetting = DEFAULT_TRANBOX_SETTING,
transApis,
darkMode,
uiLang,
}) {
if (!tranboxSetting?.transOpen) {
langDetector,
},
{ transSelected }
) {
if (transSelected === "false") {
return;
}
@@ -172,6 +176,7 @@ function showTransbox({
tranboxSetting={tranboxSetting}
transApis={transApis}
uiLang={uiLang}
langDetector={langDetector}
/>
</CacheProvider>
</React.StrictMode>
@@ -249,7 +254,7 @@ export async function run(isUserscript = false) {
inputTranslate(setting);
// 划词翻译
showTransbox(setting);
showTransbox(setting, rule);
// 浮球按钮
await showFab(translator);

View File

@@ -42,7 +42,23 @@ const customApiLangs = `["en", "English - English"],
["vi", "Vietnamese - Tiếng Việt"],
`;
const customDefaultOption = `{
const hookExample = `// URL
https://translate.googleapis.com/translate_a/single?client=gtx&dj=1&dt=t&ie=UTF-8&q={{text}}&sl=en&tl=zh-CN
// Request Hook
(text, from, to, url, key) => [url, {
headers: {
"Content-type": "application/json",
},
method: "GET",
body: null,
}]
// Response Hook
(res, text, from, to) => [res.sentences.map((item) => item.trans).join(" "), to === res.src]`;
const customApiHelpZH = `// 请求数据默认格式
{
"url": "{{url}}",
"method": "POST",
"headers": {
@@ -50,18 +66,12 @@ const customDefaultOption = `{
"Authorization": "Bearer {{key}}"
},
"body": {
"text": "{{text}}",
"from": "{{from}}",
"to": "{{to}}"
"text": "{{text}}", // 待翻译文字
"from": "{{from}}", // 文字的语言(可能为空)
"to": "{{to}}", // 目标语言
},
"resPattern": {
"text": "text",
"from": "from"
}
}`;
}
const customApiHelpZH = `// 自定义选项范例
${customDefaultOption}
// 返回数据默认格式
{
@@ -70,20 +80,43 @@ ${customDefaultOption}
to: "", // 目标语言(可选)
}
// Hook 范例
${hookExample}
// 支持的语言代码如下
${customApiLangs}
`;
const customApiHelpEN = `// Example of custom options
${customDefaultOption}
const customApiHelpEN = `// Default request
{
"url": "{{url}}",
"method": "POST",
"headers": {
"Content-type": "application/json",
"Authorization": "Bearer {{key}}"
},
"body": {
"text": "{{text}}", // Text to be translated
"from": "{{from}}", // The language of the text (may be empty)
"to": "{{to}}", // Target language
},
}
// Return data default format
// Default response
{
text: "", // translated text
from: "", // Recognized source language
to: "", // Target language (optional)
}
/// Hook Example
${hookExample}
// The supported language codes are as follows
${customApiLangs}
`;
@@ -153,6 +186,22 @@ export const I18N = {
zh: `最大并发请求数量 (1-100)`,
en: `Maximum Number Of Concurrent Requests (1-100)`,
},
if_think: {
zh: `启用或禁用模型的深度思考能力`,
en: `Enable or disable the models thinking behavior `,
},
think: {
zh: `启用深度思考`,
en: `enable thinking`,
},
nothink: {
zh: `禁用深度思考`,
en: `disable thinking`,
},
think_ignore: {
zh: `忽略以下模型的<think>输出,逗号(,)分割,当模型支持思考但ollama不支持时需要填写本参数`,
en: `Ignore the <think> block for the following models, comma (,) separated`,
},
fetch_interval: {
zh: `每次请求间隔时间 (0-5000ms)`,
en: `Time Between Requests (0-5000ms)`,
@@ -161,6 +210,10 @@ export const I18N = {
zh: `重新翻译间隔时间 (100-5000ms)`,
en: `Retranslation Interval (100-5000ms)`,
},
http_timeout: {
zh: `请求超时时间 (5000-30000ms)`,
en: `Request Timeout Time (5000-30000ms)`,
},
min_translate_length: {
zh: `最小翻译字符数 (1-100)`,
en: `Minimum number Of Translated Characters (1-100)`,
@@ -394,8 +447,8 @@ export const I18N = {
en: `Keep unchanged selector`,
},
keep_selector_helper: {
zh: `1、遵循CSS选择器语法。2、子元素选择器用“>>>”隔开。`,
en: `1. Follow CSS selector syntax. 2. Sub-element selectors are separated by ">>>".`,
zh: `1、遵循CSS选择器语法。`,
en: `1. Follow CSS selector syntax.`,
},
terms: {
zh: `专业术语`,
@@ -623,7 +676,7 @@ export const I18N = {
},
use_simple_style: {
zh: `使用简洁界面`,
en: `Click outside to close the pop-up window`,
en: `Use a simple interface`,
},
show: {
zh: `显示`,
@@ -821,6 +874,10 @@ export const I18N = {
zh: `更多`,
en: `More`,
},
less: {
zh: `更少`,
en: `Less`,
},
fixer_selector: {
zh: `网页修复选择器`,
en: `Fixer Selector`,
@@ -873,4 +930,44 @@ export const I18N = {
zh: `翻译框跟随选中文本`,
en: `Transbox Follow Selection`,
},
translate_start_hook: {
zh: `翻译开始钩子函数`,
en: `Translate Start Hook`,
},
translate_start_hook_helper: {
zh: `翻译开始时运行,入参为: 翻译节点,原文文本。`,
en: `Run when translation starts, the input parameters are: translation node, original text.`,
},
translate_end_hook: {
zh: `翻译完成钩子函数`,
en: `Translate End Hook`,
},
translate_end_hook_helper: {
zh: `翻译完成时运行,入参为: 翻译节点,原文文本,译文文本,保留元素。`,
en: `Run when the translation is completed, the input parameters are: translation node, original text, translation text, retained elements.`,
},
translate_remove_hook: {
zh: `翻译移除钩子函数`,
en: `Translate Removed Hook`,
},
translate_remove_hook_helper: {
zh: `翻译移除时运行,入参为: 翻译节点。`,
en: `Run when translation is removed, the input parameters are: translation node.`,
},
english_dict: {
zh: `英文词典`,
en: `English Dictionary`,
},
api_name: {
zh: `接口名称`,
en: `API Name`,
},
is_disabled: {
zh: `是否禁用`,
en: `Is Disabled`,
},
translate_selected: {
zh: `是否启用划词翻译`,
en: `If translate selected`,
},
};

View File

@@ -39,7 +39,13 @@ export const CLIENT_CHROME = "chrome";
export const CLIENT_EDGE = "edge";
export const CLIENT_FIREFOX = "firefox";
export const CLIENT_USERSCRIPT = "userscript";
export const CLIENT_EXTS = [CLIENT_CHROME, CLIENT_EDGE, CLIENT_FIREFOX];
export const CLIENT_THUNDERBIRD = "thunderbird";
export const CLIENT_EXTS = [
CLIENT_CHROME,
CLIENT_EDGE,
CLIENT_FIREFOX,
CLIENT_THUNDERBIRD,
];
export const KV_RULES_KEY = "kiss-rules.json";
export const KV_WORDS_KEY = "kiss-words.json";
@@ -78,9 +84,20 @@ export const URL_RAW_PREFIX =
"https://raw.githubusercontent.com/fishjar/kiss-translator/master";
export const URL_CACHE_TRAN = `https://${APP_LCNAME}/translate`;
// api.cognitive.microsofttranslator.com
export const URL_MICROSOFT_TRAN =
"https://api-edge.cognitive.microsofttranslator.com/translate";
export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth";
export const URL_MICROSOFT_LANGDETECT =
"https://api-edge.cognitive.microsofttranslator.com/detect?api-version=3.0";
export const URL_GOOGLE_TRAN =
"https://translate.googleapis.com/translate_a/single";
export const URL_GOOGLE_TRAN2 =
"https://translate-pa.googleapis.com/v1/translateHtml";
export const DEFAULT_GOOGLE_API_KEY = "AIzaSyATBXajvzQLTDHEQbcpq0Ihe0vWDHmO520";
export const URL_BAIDU_LANGDETECT = "https://fanyi.baidu.com/langdetect";
export const URL_BAIDU_SUGGEST = "https://fanyi.baidu.com/sug";
export const URL_BAIDU_TTS = "https://fanyi.baidu.com/gettts";
@@ -89,13 +106,18 @@ 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";
export const URL_VOLCENGINE_TRAN =
"https://translate.volcengine.com/crx/translate/v1";
export const URL_NIUTRANS_REG =
"https://niutrans.com/login?active=3&userSource=kiss-translator";
export const DEFAULT_USER_AGENT =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
export const OPT_DICT_BAIDU = "Baidu";
export const OPT_TRANS_GOOGLE = "Google";
export const OPT_TRANS_GOOGLE_2 = "Google2";
export const OPT_TRANS_MICROSOFT = "Microsoft";
export const OPT_TRANS_DEEPL = "DeepL";
export const OPT_TRANS_DEEPLX = "DeepLX";
@@ -103,9 +125,17 @@ export const OPT_TRANS_DEEPLFREE = "DeepLFree";
export const OPT_TRANS_NIUTRANS = "NiuTrans";
export const OPT_TRANS_BAIDU = "Baidu";
export const OPT_TRANS_TENCENT = "Tencent";
export const OPT_TRANS_VOLCENGINE = "Volcengine";
export const OPT_TRANS_OPENAI = "OpenAI";
export const OPT_TRANS_OPENAI_2 = "OpenAI2";
export const OPT_TRANS_OPENAI_3 = "OpenAI3";
export const OPT_TRANS_GEMINI = "Gemini";
export const OPT_TRANS_GEMINI_2 = "Gemini2";
export const OPT_TRANS_CLAUDE = "Claude";
export const OPT_TRANS_CLOUDFLAREAI = "CloudflareAI";
export const OPT_TRANS_OLLAMA = "Ollama";
export const OPT_TRANS_OLLAMA_2 = "Ollama2";
export const OPT_TRANS_OLLAMA_3 = "Ollama3";
export const OPT_TRANS_CUSTOMIZE = "Custom";
export const OPT_TRANS_CUSTOMIZE_2 = "Custom2";
export const OPT_TRANS_CUSTOMIZE_3 = "Custom3";
@@ -113,16 +143,25 @@ export const OPT_TRANS_CUSTOMIZE_4 = "Custom4";
export const OPT_TRANS_CUSTOMIZE_5 = "Custom5";
export const OPT_TRANS_ALL = [
OPT_TRANS_GOOGLE,
OPT_TRANS_GOOGLE_2,
OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_VOLCENGINE,
OPT_TRANS_DEEPL,
OPT_TRANS_DEEPLFREE,
OPT_TRANS_DEEPLX,
OPT_TRANS_NIUTRANS,
OPT_TRANS_OPENAI,
OPT_TRANS_OPENAI_2,
OPT_TRANS_OPENAI_3,
OPT_TRANS_GEMINI,
OPT_TRANS_GEMINI_2,
OPT_TRANS_CLAUDE,
OPT_TRANS_CLOUDFLAREAI,
OPT_TRANS_OLLAMA,
OPT_TRANS_OLLAMA_2,
OPT_TRANS_OLLAMA_3,
OPT_TRANS_CUSTOMIZE,
OPT_TRANS_CUSTOMIZE_2,
OPT_TRANS_CUSTOMIZE_3,
@@ -130,6 +169,13 @@ export const OPT_TRANS_ALL = [
OPT_TRANS_CUSTOMIZE_5,
];
export const OPT_LANGDETECTOR_ALL = [
OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
];
export const OPT_LANGS_TO = [
["en", "English - English"],
["zh-CN", "Simplified Chinese - 简体中文"],
@@ -172,6 +218,7 @@ export const OPT_LANGS_TO = [
export const OPT_LANGS_FROM = [["auto", "Auto-detect"], ...OPT_LANGS_TO];
export const OPT_LANGS_SPECIAL = {
[OPT_TRANS_GOOGLE]: new Map(OPT_LANGS_FROM.map(([key]) => [key, key])),
[OPT_TRANS_GOOGLE_2]: new Map(OPT_LANGS_FROM.map(([key]) => [key, key])),
[OPT_TRANS_MICROSOFT]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]),
["auto", ""],
@@ -192,7 +239,7 @@ export const OPT_LANGS_SPECIAL = {
]),
[OPT_TRANS_DEEPLX]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key.toUpperCase()]),
["auto", ""],
["auto", "auto"],
["zh-CN", "ZH"],
["zh-TW", "ZH"],
]),
@@ -202,6 +249,12 @@ export const OPT_LANGS_SPECIAL = {
["zh-CN", "zh"],
["zh-TW", "cht"],
]),
[OPT_TRANS_VOLCENGINE]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]),
["auto", "auto"],
["zh-CN", "zh"],
["zh-TW", "zh-Hant"],
]),
[OPT_TRANS_BAIDU]: new Map([
...OPT_LANGS_FROM.map(([key]) => [key, key]),
["zh-CN", "zh"],
@@ -254,9 +307,30 @@ export const OPT_LANGS_SPECIAL = {
[OPT_TRANS_OPENAI]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OPENAI_2]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OPENAI_3]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_GEMINI]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_GEMINI_2]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_CLAUDE]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OLLAMA]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OLLAMA_2]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_OLLAMA_3]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
[OPT_TRANS_CLOUDFLAREAI]: new Map([
["auto", ""],
["zh-CN", "chinese"],
@@ -293,6 +367,12 @@ export const OPT_LANGS_SPECIAL = {
]),
};
export const OPT_LANGS_LIST = OPT_LANGS_TO.map(([lang]) => lang);
export const OPT_LANGS_MICROSOFT = new Map(
Array.from(OPT_LANGS_SPECIAL[OPT_TRANS_MICROSOFT].entries()).map(([k, v]) => [
v,
k,
])
);
export const OPT_LANGS_BAIDU = new Map(
Array.from(OPT_LANGS_SPECIAL[OPT_TRANS_BAIDU].entries()).map(([k, v]) => [
v,
@@ -359,6 +439,7 @@ export const INPUT_PLACE_FROM = "{{from}}"; // 占位符
export const INPUT_PLACE_TO = "{{to}}"; // 占位符
export const INPUT_PLACE_TEXT = "{{text}}"; // 占位符
export const INPUT_PLACE_KEY = "{{key}}"; // 占位符
export const INPUT_PLACE_MODEL = "{{model}}"; // 占位符
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
@@ -387,10 +468,14 @@ export const GLOBLA_RULE = {
transTiming: OPT_TIMING_PAGESCROLL, // 翻译时机/鼠标悬停翻译
transTag: DEFAULT_TRANS_TAG, // 译文元素标签
transTitle: "false", // 是否同时翻译页面标题
transSelected: "true", // 是否启用划词翻译
detectRemote: "false", // 是否使用远程语言检测
skipLangs: [], // 不翻译的语言
fixerSelector: "", // 修复函数选择器
fixerFunc: "-", // 修复函数
transStartHook: "", // 钩子函数
transEndHook: "", // 钩子函数
transRemoveHook: "", // 钩子函数
};
// 输入框翻译
@@ -422,7 +507,7 @@ export const OPT_TRANBOX_TRIGGER_ALL = [
];
export const DEFAULT_TRANBOX_SHORTCUT = ["AltLeft", "KeyS"];
export const DEFAULT_TRANBOX_SETTING = {
transOpen: true,
// transOpen: true, // 是否启用划词翻译作废移至rule
translator: OPT_TRANS_MICROSOFT,
fromLang: "auto",
toLang: "zh-CN",
@@ -438,6 +523,7 @@ export const DEFAULT_TRANBOX_SETTING = {
followSelection: false, // 翻译框是否跟随选中文本
triggerMode: OPT_TRANBOX_TRIGGER_CLICK, // 触发翻译方式
extStyles: "", // 附加样式
enDict: OPT_DICT_BAIDU, // 英文词典
};
// 订阅列表
@@ -456,48 +542,120 @@ export const DEFAULT_SUBRULES_LIST = [
},
];
export const DEFAULT_HTTP_TIMEOUT = 5000; // 调用超时时间
// 翻译接口
const defaultCustomApi = {
url: "",
key: "",
customOption: "",
customOption: "", // (作废)
reqHook: "", // request 钩子函数
resHook: "", // response 钩子函数
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: "",
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
};
const defaultOpenaiApi = {
url: "https://api.openai.com/v1/chat/completions",
key: "",
model: "gpt-4",
systemPrompt: `You are a professional, authentic machine translation engine.`,
userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`,
temperature: 0,
maxTokens: 256,
fetchLimit: 1,
fetchInterval: 500,
apiName: "",
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT * 2,
};
const defaultOllamaApi = {
url: "http://localhost:11434/api/generate",
key: "",
model: "llama3.1",
systemPrompt: `You are a professional, authentic machine translation engine.`,
userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`,
think: false,
thinkIgnore: `qwen3,deepseek-r1`,
fetchLimit: 1,
fetchInterval: 500,
apiName: "",
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT * 2,
};
export const DEFAULT_TRANS_APIS = {
[OPT_TRANS_GOOGLE]: {
url: "https://translate.googleapis.com/translate_a/single",
url: URL_GOOGLE_TRAN,
key: "",
fetchLimit: DEFAULT_FETCH_LIMIT, // 最大任务数量
fetchInterval: DEFAULT_FETCH_INTERVAL, // 任务间隔时间
apiName: OPT_TRANS_GOOGLE, // 接口自定义名称
isDisabled: false, // 是否禁用
httpTimeout: DEFAULT_HTTP_TIMEOUT, // 超时时间
},
[OPT_TRANS_GOOGLE_2]: {
url: URL_GOOGLE_TRAN2,
key: DEFAULT_GOOGLE_API_KEY,
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: OPT_TRANS_GOOGLE_2,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_MICROSOFT]: {
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: OPT_TRANS_MICROSOFT,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_BAIDU]: {
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: OPT_TRANS_BAIDU,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_TENCENT]: {
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: OPT_TRANS_TENCENT,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_VOLCENGINE]: {
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: OPT_TRANS_VOLCENGINE,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_DEEPL]: {
url: "https://api-free.deepl.com/v2/translate",
key: "",
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_DEEPL,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_DEEPLFREE]: {
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_DEEPLFREE,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_DEEPLX]: {
url: "http://localhost:1188/translate",
key: "",
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_DEEPLX,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_NIUTRANS]: {
url: "https://api.niutrans.com/NiuTransServer/translation",
@@ -506,29 +664,67 @@ export const DEFAULT_TRANS_APIS = {
memoryNo: "",
fetchLimit: DEFAULT_FETCH_LIMIT,
fetchInterval: DEFAULT_FETCH_INTERVAL,
apiName: OPT_TRANS_NIUTRANS,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
},
[OPT_TRANS_OPENAI]: {
url: "https://api.openai.com/v1/chat/completions",
key: "",
model: "gpt-4",
prompt: `You will be provided with a sentence in ${INPUT_PLACE_FROM}, and your task is to translate it into ${INPUT_PLACE_TO}.`,
fetchLimit: 1,
fetchInterval: 500,
},
[OPT_TRANS_OPENAI]: defaultOpenaiApi,
[OPT_TRANS_OPENAI_2]: defaultOpenaiApi,
[OPT_TRANS_OPENAI_3]: defaultOpenaiApi,
[OPT_TRANS_GEMINI]: {
url: "https://generativelanguage.googleapis.com/v1/models",
url: `https://generativelanguage.googleapis.com/v1/models/${INPUT_PLACE_MODEL}:generateContent?key=${INPUT_PLACE_KEY}`,
key: "",
model: "gemini-pro",
prompt: `Translate the following text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}:\n\n${INPUT_PLACE_TEXT}`,
model: "gemini-2.5-flash",
systemPrompt: `You are a professional, authentic machine translation engine.`,
userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`,
temperature: 0,
maxTokens: 2048,
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_GEMINI,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT * 2,
},
[OPT_TRANS_GEMINI_2]: {
url: `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions`,
key: "",
model: "gemini-2.0-flash",
systemPrompt: `You are a professional, authentic machine translation engine.`,
userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`,
temperature: 0,
reasoningEffort: "low",
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_GEMINI_2,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT * 2,
},
[OPT_TRANS_CLAUDE]: {
url: "https://api.anthropic.com/v1/messages",
key: "",
model: "claude-3-haiku-20240307",
systemPrompt: `You are a professional, authentic machine translation engine.`,
userPrompt: `Translate the following source text from ${INPUT_PLACE_FROM} to ${INPUT_PLACE_TO}. Output translation directly without any additional text.\n\nSource Text: ${INPUT_PLACE_TEXT}\n\nTranslated Text:`,
temperature: 0,
maxTokens: 1024,
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_CLAUDE,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT * 2,
},
[OPT_TRANS_CLOUDFLAREAI]: {
url: "https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/run/@cf/meta/m2m100-1.2b",
url: "https://api.cloudflare.com/client/v4/accounts/{{ACCOUNT_ID}}/ai/run/@cf/meta/m2m100-1.2b",
key: "",
fetchLimit: 1,
fetchInterval: 500,
apiName: OPT_TRANS_CLOUDFLAREAI,
isDisabled: false,
httpTimeout: DEFAULT_HTTP_TIMEOUT * 2,
},
[OPT_TRANS_OLLAMA]: defaultOllamaApi,
[OPT_TRANS_OLLAMA_2]: defaultOllamaApi,
[OPT_TRANS_OLLAMA_3]: defaultOllamaApi,
[OPT_TRANS_CUSTOMIZE]: defaultCustomApi,
[OPT_TRANS_CUSTOMIZE_2]: defaultCustomApi,
[OPT_TRANS_CUSTOMIZE_3]: defaultCustomApi,
@@ -568,6 +764,7 @@ export const DEFAULT_SETTING = {
minLength: TRANS_MIN_LENGTH,
maxLength: TRANS_MAX_LENGTH,
newlineLength: TRANS_NEWLINE_LENGTH,
httpTimeout: DEFAULT_HTTP_TIMEOUT,
clearCache: false, // 是否在浏览器下次启动时清除缓存
injectRules: true, // 是否注入订阅规则
// injectWebfix: true, // 是否注入修复补丁(作废)
@@ -589,6 +786,7 @@ export const DEFAULT_SETTING = {
csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单
// disableLangs: [], // 不翻译的语言(移至rule作废)
transInterval: 500, // 翻译间隔时间
langDetector: OPT_TRANS_MICROSOFT, // 远程语言识别服务
};
export const DEFAULT_RULES = [GLOBLA_RULE];

View File

@@ -26,10 +26,14 @@ export const DEFAULT_RULE = {
transTiming: GLOBAL_KEY, // 翻译时机/鼠标悬停翻译
transTag: GLOBAL_KEY, // 译文元素标签
transTitle: GLOBAL_KEY, // 是否同时翻译页面标题
transSelected: GLOBAL_KEY, // 是否启用划词翻译
detectRemote: GLOBAL_KEY, // 是否使用远程语言检测
skipLangs: [], // 不翻译的语言
fixerSelector: "", // 修复函数选择器
fixerFunc: GLOBAL_KEY, // 修复函数
transStartHook: "", // 钩子函数
transEndHook: "", // 钩子函数
transRemoveHook: "", // 钩子函数
};
const DEFAULT_DIY_STYLE = `color: #666;

View File

@@ -30,7 +30,14 @@ export function useTranslate(q, rule, setting) {
return;
}
const deLang = await tryDetectLang(q, detectRemote === "true");
let deLang = "";
if (fromLang === "auto") {
deLang = await tryDetectLang(
q,
detectRemote === "true",
setting.langDetector
);
}
if (deLang && (toLang.includes(deLang) || skipLangs.includes(deLang))) {
setSamelang(true);
} else {

View File

@@ -1,20 +1,21 @@
import { isExt, isGm } from "./client";
import { sendBgMsg } from "./msg";
import { taskPool } from "./pool";
import { getSettingWithDefault } from "./storage";
import {
MSG_FETCH,
MSG_GET_HTTPCACHE,
CACHE_NAME,
DEFAULT_FETCH_INTERVAL,
DEFAULT_FETCH_LIMIT,
DEFAULT_HTTP_TIMEOUT,
} from "../config";
import { isBg } from "./browser";
import { genTransReq } from "../apis/trans";
import { kissLog } from "./log";
import { blobToBase64 } from "./utils";
const TIMEOUT = 5000;
/**
* 构造缓存 request
* @param {*} input
@@ -39,7 +40,10 @@ const newCacheReq = async (input, init) => {
* @param {*} init
* @returns
*/
export const fetchGM = async (input, { method = "GET", headers, body } = {}) =>
export const fetchGM = async (
input,
{ method = "GET", headers, body, timeout } = {}
) =>
new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method,
@@ -47,7 +51,7 @@ export const fetchGM = async (input, { method = "GET", headers, body } = {}) =>
headers,
data: body,
// withCredentials: true,
timeout: TIMEOUT,
timeout,
onload: ({ response, responseHeaders, status, statusText }) => {
const headers = {};
responseHeaders.split("\n").forEach((line) => {
@@ -81,21 +85,47 @@ export const fetchPatcher = async (input, init, transOpts, apiSetting) => {
throw new Error("url is empty");
}
if (isGm) {
let info;
if (window.KISS_GM) {
info = await window.KISS_GM.getInfo();
} else {
info = GM.info;
let timeout = apiSetting?.httpTimeout || DEFAULT_HTTP_TIMEOUT;
if (!apiSetting) {
try {
timeout = (await getSettingWithDefault()).httpTimeout;
} catch (err) {
//
}
}
if (isGm) {
// let info;
// if (window.KISS_GM) {
// info = await window.KISS_GM.getInfo();
// } else {
// info = GM.info;
// }
// Tampermonkey --> .connects
// Violentmonkey --> .connect
const connects = info?.script?.connects || info?.script?.connect || [];
const url = new URL(input);
const isSafe = connects.find((item) => url.hostname.endsWith(item));
// const connects = info?.script?.connects || info?.script?.connect || [];
// const url = new URL(input);
// const isSafe = connects.find((item) => url.hostname.endsWith(item));
// if (isSafe) {
// // todo: 自定义接口 init 可能包含了 signal
// Object.assign(init, { timeout });
// const { body, headers, status, statusText } = window.KISS_GM
// ? await window.KISS_GM.fetch(input, init)
// : await fetchGM(input, init);
// return new Response(body, {
// headers: new Headers(headers),
// status,
// statusText,
// });
// }
// todo: 自定义接口 init 可能包含了 signal
Object.assign(init, { timeout });
if (isSafe) {
const { body, headers, status, statusText } = window.KISS_GM
? await window.KISS_GM.fetch(input, init)
: await fetchGM(input, init);
@@ -106,10 +136,9 @@ export const fetchPatcher = async (input, init, transOpts, apiSetting) => {
statusText,
});
}
}
if (AbortSignal?.timeout) {
Object.assign(init, { signal: AbortSignal.timeout(TIMEOUT) });
if (AbortSignal?.timeout && !init.signal) {
Object.assign(init, { signal: AbortSignal.timeout(timeout) });
}
return fetch(input, init);

View File

@@ -1,8 +1,26 @@
import { CACHE_NAME } from "../config";
import {
CACHE_NAME,
OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
} from "../config";
import { browser } from "./browser";
import { apiBaiduLangdetect } from "../apis";
import {
apiGoogleLangdetect,
apiMicrosoftLangdetect,
apiBaiduLangdetect,
apiTencentLangdetect,
} from "../apis";
import { kissLog } from "./log";
const langdetectMap = {
[OPT_TRANS_GOOGLE]: apiGoogleLangdetect,
[OPT_TRANS_MICROSOFT]: apiMicrosoftLangdetect,
[OPT_TRANS_BAIDU]: apiBaiduLangdetect,
[OPT_TRANS_TENCENT]: apiTencentLangdetect,
};
/**
* 清除缓存数据
*/
@@ -19,12 +37,16 @@ export const tryClearCaches = async () => {
* @param {*} q
* @returns
*/
export const tryDetectLang = async (q, useRemote = false) => {
export const tryDetectLang = async (
q,
useRemote = false,
langDetector = OPT_TRANS_MICROSOFT
) => {
let lang = "";
if (useRemote) {
try {
lang = await apiBaiduLangdetect(q);
lang = await langdetectMap[langDetector](q);
} catch (err) {
kissLog(err, "detect lang remote");
}

16
src/libs/interpreter.js Normal file
View File

@@ -0,0 +1,16 @@
import Sval from "sval";
const interpreter = new Sval({
// ECMA Version of the code
// 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
// or 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024
// or "latest"
ecmaVer: "latest",
// Code source type
// "script" or "module"
sourceType: "script",
// Whether the code runs in a sandbox
sandBox: true,
});
export default interpreter;

View File

@@ -74,6 +74,9 @@ export const matchRule = async (
"injectJs",
"injectCss",
"fixerSelector",
"transStartHook",
"transEndHook",
"transRemoveHook",
].forEach((key) => {
if (!rule[key]?.trim()) {
rule[key] = globalRule[key];
@@ -89,6 +92,7 @@ export const matchRule = async (
"transTiming",
"transTag",
"transTitle",
"transSelected",
"detectRemote",
"fixerFunc",
].forEach((key) => {
@@ -158,10 +162,14 @@ export const checkRules = (rules) => {
transTiming,
transTag,
transTitle,
transSelected,
detectRemote,
skipLangs,
fixerSelector,
fixerFunc,
transStartHook,
transEndHook,
transRemoveHook,
}) => ({
pattern: pattern.trim(),
selector: type(selector) === "string" ? selector : "",
@@ -182,9 +190,14 @@ export const checkRules = (rules) => {
transTiming: matchValue([GLOBAL_KEY, ...OPT_TIMING_ALL], transTiming),
transTag: matchValue([GLOBAL_KEY, "span", "font"], transTag),
transTitle: matchValue([GLOBAL_KEY, "true", "false"], transTitle),
transSelected: matchValue([GLOBAL_KEY, "true", "false"], transSelected),
detectRemote: matchValue([GLOBAL_KEY, "true", "false"], detectRemote),
skipLangs: type(skipLangs) === "array" ? skipLangs : [],
fixerSelector: type(fixerSelector) === "string" ? fixerSelector : "",
transStartHook: type(transStartHook) === "string" ? transStartHook : "",
transEndHook: type(transEndHook) === "string" ? transEndHook : "",
transRemoveHook:
type(transRemoveHook) === "string" ? transRemoveHook : "",
fixerFunc: matchValue([GLOBAL_KEY, ...FIXER_ALL], fixerFunc),
})
);

View File

@@ -25,6 +25,7 @@ import { sendBgMsg } from "./msg";
import { isExt } from "./client";
import { injectInlineJs, injectInternalCss } from "./injector";
import { kissLog } from "./log";
import interpreter from "./interpreter";
/**
* 翻译类
@@ -52,7 +53,7 @@ export class Translator {
];
_eventName = genEventName();
_mouseoverNode = null;
_keepSelector = [null, null];
_keepSelector = "";
_terms = [];
_docTitle = "";
@@ -124,9 +125,7 @@ export class Translator {
this._setting = setting;
this._rule = rule;
this._keepSelector = (rule.keepSelector || "")
.split(SHADOW_KEY)
.map((item) => item.trim());
this._keepSelector = rule.keepSelector || "";
this._terms = (rule.terms || "")
.split(/\n|;/)
.map((item) => item.split(",").map((item) => item.trim()))
@@ -405,6 +404,7 @@ export class Translator {
// 移除键盘监听
window.removeEventListener("keydown", this._handleKeydown);
const { transRemoveHook } = this._rule;
this._tranNodes.forEach((innerHTML, node) => {
if (
!this._rule.transTiming ||
@@ -420,11 +420,18 @@ export class Translator {
}
// 移除/恢复元素
if (innerHTML && this._rule.transOnly === "true") {
if (innerHTML) {
if (this._rule.transOnly === "true") {
node.innerHTML = innerHTML;
} else {
node.querySelector(APP_LCNAME)?.remove();
}
// 钩子函数
if (transRemoveHook?.trim()) {
interpreter.run(`exports.transRemoveHook = ${transRemoveHook}`);
interpreter.exports.transRemoveHook(node);
}
}
});
// 移除用户JS/CSS
@@ -490,23 +497,26 @@ export class Translator {
}
const keeps = [];
// 翻译开始钩子函数
const { transStartHook } = this._rule;
if (transStartHook?.trim()) {
interpreter.run(`exports.transStartHook = ${transStartHook}`);
interpreter.exports.transStartHook(el, q);
}
// 保留元素
const [matchSelector, subSelector] = this._keepSelector;
if (matchSelector || subSelector) {
const keepSelector = this._keepSelector.trim();
if (keepSelector) {
let text = "";
el.childNodes.forEach((child) => {
if (
child.nodeType === 1 &&
((matchSelector && child.matches(matchSelector)) ||
(subSelector && child.querySelector(subSelector)))
) {
if (child.nodeType === 1 && child.matches(keepSelector)) {
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 {
} else if (child.nodeType === 1 || child.nodeType === 3) {
text += child.textContent;
}
});
@@ -538,18 +548,22 @@ export class Translator {
}
}
traEl = document.createElement(APP_LCNAME);
traEl.style.visibility = "visible";
// if (this._rule.transOnly === "true") {
// el.innerHTML = "";
// }
// 附加样式
const { selectStyle, parentStyle } = this._rule;
el.appendChild(traEl);
el.style.cssText += selectStyle;
if (el.parentElement) {
el.parentElement.style.cssText += parentStyle;
}
// 插入译文节点
traEl = document.createElement(APP_LCNAME);
traEl.style.visibility = "visible";
// if (this._rule.transOnly === "true") {
// el.innerHTML = "";
// }
el.appendChild(traEl);
// 渲染译文节点
const root = createRoot(traEl);
root.render(<Content q={q} keeps={keeps} translator={this} $el={el} />);
};

View File

@@ -15,6 +15,16 @@ export const limitNumber = (num, min = 0, max = 100) => {
return number;
};
export const limitFloat = (num, min = 0, max = 100) => {
const number = parseFloat(num);
if (Number.isNaN(number) || number < min) {
return min;
} else if (number > max) {
return max;
}
return number;
};
/**
* 匹配是否为数组中的值
* @param {*} arr

View File

@@ -15,6 +15,7 @@ import {
import { useTranslate } from "../../hooks/Translate";
import { styled, css } from "@mui/material/styles";
import { APP_LCNAME } from "../../config";
import interpreter from "../../libs/interpreter";
const LINE_STYLES = {
[OPT_STYLE_LINE]: "solid",
@@ -85,8 +86,15 @@ const StyledSpan = styled("span")`
export default function Content({ q, keeps, translator, $el }) {
const [rule, setRule] = useState(translator.rule);
const { text, sameLang, loading } = useTranslate(q, rule, translator.setting);
const { transOpen, textStyle, bgColor, textDiyStyle, transOnly, transTag } =
rule;
const {
transOpen,
textStyle,
bgColor,
textDiyStyle,
transOnly,
transTag,
transEndHook,
} = rule;
const { newlineLength } = translator.setting;
@@ -107,6 +115,14 @@ export default function Content({ q, keeps, translator, $el }) {
};
}, [translator.eventName]);
useEffect(() => {
// 运行钩子函数
if (text && transEndHook?.trim()) {
interpreter.run(`exports.transEndHook = ${transEndHook}`);
interpreter.exports.transEndHook($el, q, text, keeps);
}
}, [$el, q, text, keeps, transEndHook]);
const gap = useMemo(() => {
if (transOnly === "true") {
return "";

View File

@@ -2,6 +2,9 @@ import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import LoadingButton from "@mui/lab/LoadingButton";
import MenuItem from "@mui/material/MenuItem";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import {
OPT_TRANS_ALL,
OPT_TRANS_MICROSOFT,
@@ -10,15 +13,28 @@ import {
OPT_TRANS_DEEPLFREE,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_VOLCENGINE,
OPT_TRANS_OPENAI,
OPT_TRANS_OPENAI_2,
OPT_TRANS_OPENAI_3,
OPT_TRANS_GEMINI,
OPT_TRANS_GEMINI_2,
OPT_TRANS_CLAUDE,
OPT_TRANS_CLOUDFLAREAI,
OPT_TRANS_OLLAMA,
OPT_TRANS_OLLAMA_2,
OPT_TRANS_OLLAMA_3,
OPT_TRANS_CUSTOMIZE,
OPT_TRANS_CUSTOMIZE_2,
OPT_TRANS_CUSTOMIZE_3,
OPT_TRANS_CUSTOMIZE_4,
OPT_TRANS_CUSTOMIZE_5,
OPT_TRANS_NIUTRANS,
URL_KISS_PROXY,
URL_NIUTRANS_REG,
DEFAULT_FETCH_LIMIT,
DEFAULT_FETCH_INTERVAL,
DEFAULT_HTTP_TIMEOUT,
} from "../../config";
import { useState } from "react";
import { useI18n } from "../../hooks/I18n";
@@ -33,7 +49,7 @@ import { useApi } from "../../hooks/Api";
import { apiTranslate } from "../../apis";
import Box from "@mui/material/Box";
import Link from "@mui/material/Link";
import { limitNumber } from "../../libs/utils";
import { limitNumber, limitFloat } from "../../libs/utils";
function TestButton({ translator, api }) {
const i18n = useI18n();
@@ -51,7 +67,7 @@ function TestButton({ translator, api }) {
useCache: false,
});
if (!text) {
throw new Error("empty reault");
throw new Error("empty result");
}
alert.success(i18n("test_success"));
} catch (err) {
@@ -109,12 +125,22 @@ function ApiFields({ translator }) {
url = "",
key = "",
model = "",
prompt = "",
systemPrompt = "",
userPrompt = "",
think = false,
thinkIgnore = "",
fetchLimit = DEFAULT_FETCH_LIMIT,
fetchInterval = DEFAULT_FETCH_INTERVAL,
httpTimeout = DEFAULT_HTTP_TIMEOUT,
dictNo = "",
memoryNo = "",
customOption = "",
reqHook = "",
resHook = "",
temperature = 0,
maxTokens = 256,
apiName = "",
isDisabled = false,
reasoningEffort = "low",
} = api;
const handleChange = (e) => {
@@ -126,6 +152,15 @@ function ApiFields({ translator }) {
case "fetchInterval":
value = limitNumber(value, 0, 5000);
break;
case "httpTimeout":
value = limitNumber(value, 5000, 30000);
break;
case "temperature":
value = limitFloat(value, 0, 2);
break;
case "maxTokens":
value = limitNumber(value, 0, 2 ** 15);
break;
default:
}
updateApi({
@@ -133,19 +168,32 @@ function ApiFields({ translator }) {
});
};
const buildinTranslators = [
const builtinTranslators = [
OPT_TRANS_MICROSOFT,
OPT_TRANS_DEEPLFREE,
OPT_TRANS_BAIDU,
OPT_TRANS_TENCENT,
OPT_TRANS_VOLCENGINE,
];
const mulkeysTranslators = [
OPT_TRANS_DEEPL,
OPT_TRANS_OPENAI,
OPT_TRANS_OPENAI_2,
OPT_TRANS_OPENAI_3,
OPT_TRANS_GEMINI,
OPT_TRANS_GEMINI_2,
OPT_TRANS_CLAUDE,
OPT_TRANS_CLOUDFLAREAI,
OPT_TRANS_OLLAMA,
OPT_TRANS_OLLAMA_2,
OPT_TRANS_OLLAMA_3,
OPT_TRANS_NIUTRANS,
OPT_TRANS_CUSTOMIZE,
OPT_TRANS_CUSTOMIZE_2,
OPT_TRANS_CUSTOMIZE_3,
OPT_TRANS_CUSTOMIZE_4,
OPT_TRANS_CUSTOMIZE_5,
];
const keyHelper =
@@ -164,7 +212,15 @@ function ApiFields({ translator }) {
return (
<Stack spacing={3}>
{!buildinTranslators.includes(translator) && (
<TextField
size="small"
label={i18n("api_name")}
name="apiName"
value={apiName}
onChange={handleChange}
/>
{!builtinTranslators.includes(translator) && (
<>
<TextField
size="small"
@@ -191,7 +247,10 @@ function ApiFields({ translator }) {
</>
)}
{(translator === OPT_TRANS_OPENAI || translator === OPT_TRANS_GEMINI) && (
{(translator.startsWith(OPT_TRANS_OPENAI) ||
translator.startsWith(OPT_TRANS_OLLAMA) ||
translator === OPT_TRANS_CLAUDE ||
translator.startsWith(OPT_TRANS_GEMINI)) && (
<>
<TextField
size="small"
@@ -202,13 +261,94 @@ function ApiFields({ translator }) {
/>
<TextField
size="small"
label={"PROMPT"}
name="prompt"
value={prompt}
label={"SYSTEM PROMPT"}
name="systemPrompt"
value={systemPrompt}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={"USER PROMPT"}
name="userPrompt"
value={userPrompt}
onChange={handleChange}
multiline
maxRows={10}
/>
</>
)}
{translator.startsWith(OPT_TRANS_OLLAMA) && (
<>
<TextField
select
size="small"
name="think"
value={think}
label={i18n("if_think")}
onChange={handleChange}
>
<MenuItem value={false}>{i18n("nothink")}</MenuItem>
<MenuItem value={true}>{i18n("think")}</MenuItem>
</TextField>
<TextField
size="small"
label={i18n("think_ignore")}
name="thinkIgnore"
value={thinkIgnore}
onChange={handleChange}
/>
</>
)}
{(translator.startsWith(OPT_TRANS_OPENAI) ||
translator === OPT_TRANS_CLAUDE ||
translator === OPT_TRANS_GEMINI) && (
<>
<TextField
size="small"
label={"Temperature"}
type="number"
name="temperature"
value={temperature}
onChange={handleChange}
/>
<TextField
size="small"
label={"Max Tokens"}
type="number"
name="maxTokens"
value={maxTokens}
onChange={handleChange}
/>
</>
)}
{translator === OPT_TRANS_GEMINI_2 && (
<>
<TextField
size="small"
label="Temperature"
type="number"
name="temperature"
value={temperature}
onChange={handleChange}
/>
<TextField
select
size="small"
name="reasoningEffort"
value={reasoningEffort}
label="Reasoning Effort"
onChange={handleChange}
>
<MenuItem value={"none"}>none</MenuItem>
<MenuItem value={"low"}>low</MenuItem>
<MenuItem value={"medium"}>medium</MenuItem>
<MenuItem value={"high"}>high</MenuItem>
</TextField>
</>
)}
@@ -232,15 +372,26 @@ function ApiFields({ translator }) {
)}
{translator.startsWith(OPT_TRANS_CUSTOMIZE) && (
<>
<TextField
size="small"
label={i18n("custom_option")}
name="customOption"
value={customOption}
label={"Request Hook"}
name="reqHook"
value={reqHook}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={"Response Hook"}
name="resHook"
value={resHook}
onChange={handleChange}
multiline
maxRows={10}
/>
</>
)}
<TextField
@@ -261,6 +412,29 @@ function ApiFields({ translator }) {
onChange={handleChange}
/>
<TextField
size="small"
label={i18n("http_timeout")}
type="number"
name="httpTimeout"
defaultValue={httpTimeout}
onChange={handleChange}
/>
<FormControlLabel
control={
<Switch
size="small"
name="isDisabled"
checked={isDisabled}
onChange={() => {
updateApi({ isDisabled: !isDisabled });
}}
/>
}
label={i18n("is_disabled")}
/>
<Stack direction="row" spacing={2}>
<TestButton translator={translator} api={api} />
<Button

View File

@@ -96,9 +96,7 @@ export default function FavWords() {
.join(" "),
dictResult.content[0].mean
.map(({ pre, cont }) => {
return ` - ${pre ? `[${pre}] ` : ""}${Object.keys(cont).join(
"; "
)}`;
return ` - ${pre ? `[${pre}] ` : ""}${Object.keys(cont).join("; ")}`;
})
.join("\n"),
].join("\n\n")

View File

@@ -27,6 +27,7 @@ import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import AccordionDetails from "@mui/material/AccordionDetails";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { useRules } from "../../hooks/Rules";
import MenuItem from "@mui/material/MenuItem";
import Grid from "@mui/material/Grid";
@@ -93,10 +94,14 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
transTiming = OPT_TIMING_PAGESCROLL,
transTag = DEFAULT_TRANS_TAG,
transTitle = "false",
transSelected = "true",
detectRemote = "false",
skipLangs = [],
fixerSelector = "",
fixerFunc = "-",
transStartHook = "",
transEndHook = "",
transRemoveHook = "",
} = formValues;
const hasSamePattern = (str) => {
@@ -177,6 +182,30 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
</MenuItem>
);
const ShowMoreButton = showMore ? (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(false);
}}
startIcon={<ExpandLessIcon />}
>
{i18n("less")}
</Button>
) : (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
startIcon={<ExpandMoreIcon />}
>
{i18n("more")}
</Button>
);
return (
<form onSubmit={handleSubmit}>
<Stack spacing={2}>
@@ -339,8 +368,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
/>
)}
{showMore && (
<>
<Box>
<Grid container spacing={2} columns={12}>
<Grid item xs={12} sm={6} md={3} lg={2}>
@@ -410,6 +437,22 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
size="small"
fullWidth
name="transSelected"
value={transSelected}
label={i18n("translate_selected")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
<MenuItem value={"false"}>{i18n("disable")}</MenuItem>
<MenuItem value={"true"}>{i18n("enable")}</MenuItem>
</TextField>
</Grid>
<Grid item xs={12} sm={6} md={3} lg={2}>
<TextField
select
@@ -429,6 +472,36 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
</Grid>
</Box>
{showMore && (
<>
<TextField
size="small"
label={i18n("fixer_selector")}
name="fixerSelector"
value={fixerSelector}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
select
size="small"
name="fixerFunc"
value={fixerFunc}
label={i18n("fixer_function")}
helperText={i18n("fixer_function_helper")}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{FIXER_ALL.map((item) => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</TextField>
<TextField
select
size="small"
@@ -458,34 +531,42 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("fixer_selector")}
name="fixerSelector"
value={fixerSelector}
label={i18n("translate_start_hook")}
helperText={i18n("translate_start_hook_helper")}
name="transStartHook"
value={transStartHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
select
size="small"
name="fixerFunc"
value={fixerFunc}
label={i18n("fixer_function")}
helperText={i18n("fixer_function_helper")}
label={i18n("translate_end_hook")}
helperText={i18n("translate_end_hook_helper")}
name="transEndHook"
value={transEndHook}
disabled={disabled}
onChange={handleChange}
>
{GlobalItem}
{FIXER_ALL.map((item) => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</TextField>
multiline
maxRows={10}
/>
<TextField
size="small"
label={i18n("translate_remove_hook")}
helperText={i18n("translate_remove_hook_helper")}
name="transRemoveHook"
value={transRemoveHook}
disabled={disabled}
onChange={handleChange}
multiline
maxRows={10}
/>
<TextField
size="small"
@@ -564,18 +645,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
{i18n("delete")}
</Button>
)}
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
startIcon={<ExpandMoreIcon />}
>
{i18n("more")}
</Button>
)}
{ShowMoreButton}
</>
) : (
<>
@@ -595,18 +665,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
>
{i18n("cancel")}
</Button>
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
startIcon={<ExpandMoreIcon />}
>
{i18n("more")}
</Button>
)}
{ShowMoreButton}
</>
)}
</Stack>
@@ -629,17 +688,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
>
{i18n("cancel")}
</Button>
{!showMore && (
<Button
size="small"
variant="text"
onClick={() => {
setShowMore(true);
}}
>
{i18n("more")}
</Button>
)}
{ShowMoreButton}
</Stack>
))}
</Stack>

View File

@@ -17,6 +17,8 @@ import {
UI_LANGS,
TRANS_NEWLINE_LENGTH,
CACHE_NAME,
OPT_TRANS_MICROSOFT,
OPT_LANGDETECTOR_ALL,
OPT_SHORTCUT_TRANSLATE,
OPT_SHORTCUT_STYLE,
OPT_SHORTCUT_POPUP,
@@ -25,12 +27,15 @@ import {
DEFAULT_CSPLIST,
MSG_CONTEXT_MENUS,
MSG_UPDATE_CSP,
DEFAULT_HTTP_TIMEOUT,
} from "../../config";
import { useShortcut } from "../../hooks/Shortcut";
import ShortcutInput from "./ShortcutInput";
import { useFab } from "../../hooks/Fab";
import { sendBgMsg } from "../../libs/msg";
import { kissLog } from "../../libs/log";
import UploadButton from "./UploadButton";
import DownloadButton from "./DownloadButton";
function ShortcutItem({ action, label }) {
const { shortcut, setShortcut } = useShortcut(action);
@@ -67,6 +72,9 @@ export default function Settings() {
case "newlineLength":
value = limitNumber(value, 1, 1000);
break;
case "httpTimeout":
value = limitNumber(value, 5000, 30000);
break;
case "touchTranslate":
value = limitNumber(value, 0, 4);
break;
@@ -92,23 +100,48 @@ export default function Settings() {
}
};
const handleImport = async (data) => {
try {
await updateSetting(JSON.parse(data));
} catch (err) {
kissLog(err, "import setting");
}
};
const {
uiLang,
minLength,
maxLength,
clearCache,
newlineLength = TRANS_NEWLINE_LENGTH,
httpTimeout = DEFAULT_HTTP_TIMEOUT,
contextMenuType = 1,
touchTranslate = 2,
blacklist = DEFAULT_BLACKLIST.join(",\n"),
csplist = DEFAULT_CSPLIST.join(",\n"),
transInterval = 500,
langDetector = OPT_TRANS_MICROSOFT,
} = setting;
const { isHide = false } = fab || {};
return (
<Box>
<Stack spacing={3}>
<Stack
direction="row"
alignItems="center"
spacing={2}
useFlexGap
flexWrap="wrap"
>
<UploadButton text={i18n("import")} handleImport={handleImport} />
<DownloadButton
handleData={() => JSON.stringify(setting, null, 2)}
text={i18n("export")}
fileName={`kiss-setting_${Date.now()}.json`}
/>
</Stack>
<FormControl size="small">
<InputLabel>{i18n("ui_lang")}</InputLabel>
<Select
@@ -160,7 +193,14 @@ export default function Settings() {
defaultValue={transInterval}
onChange={handleChange}
/>
<TextField
size="small"
label={i18n("http_timeout")}
type="number"
name="httpTimeout"
defaultValue={httpTimeout}
onChange={handleChange}
/>
<FormControl size="small">
<InputLabel>{i18n("touch_translate_shortcut")}</InputLabel>
<Select
@@ -206,6 +246,22 @@ export default function Settings() {
</Select>
</FormControl>
<FormControl size="small">
<InputLabel>{i18n("detect_lang_remote")}</InputLabel>
<Select
name="langDetector"
value={langDetector}
label={i18n("detect_lang_remote")}
onChange={handleChange}
>
{OPT_LANGDETECTOR_ALL.map((item) => (
<MenuItem value={item} key={item}>
{item}
</MenuItem>
))}
</Select>
</FormControl>
{isExt ? (
<>
<FormControl size="small">

View File

@@ -9,10 +9,9 @@ import {
OPT_LANGS_TO,
OPT_TRANBOX_TRIGGER_CLICK,
OPT_TRANBOX_TRIGGER_ALL,
OPT_DICT_BAIDU,
} from "../../config";
import ShortcutInput from "./ShortcutInput";
import FormControlLabel from "@mui/material/FormControlLabel";
import Switch from "@mui/material/Switch";
import { useCallback } from "react";
import { limitNumber } from "../../libs/utils";
import { useTranbox } from "../../hooks/Tranbox";
@@ -47,7 +46,6 @@ export default function Tranbox() {
);
const {
transOpen,
translator,
fromLang,
toLang,
@@ -63,25 +61,12 @@ export default function Tranbox() {
followSelection = false,
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
extStyles = "",
enDict = OPT_DICT_BAIDU,
} = tranboxSetting;
return (
<Box>
<Stack spacing={3}>
<FormControlLabel
control={
<Switch
size="small"
name="transOpen"
checked={transOpen}
onChange={() => {
updateTranbox({ transOpen: !transOpen });
}}
/>
}
label={i18n("toggle_selection_translate")}
/>
<TextField
select
size="small"
@@ -143,6 +128,18 @@ export default function Tranbox() {
))}
</TextField>
<TextField
select
size="small"
name="enDict"
value={enDict}
label={i18n("english_dict")}
onChange={handleChange}
>
<MenuItem value={"-"}>{i18n("disable")}</MenuItem>
<MenuItem value={OPT_DICT_BAIDU}>{OPT_DICT_BAIDU}</MenuItem>
</TextField>
<TextField
size="small"
label={i18n("tranbtn_offset_x")}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import MenuItem from "@mui/material/MenuItem";
@@ -23,6 +23,7 @@ import {
OPT_LANGS_FROM,
OPT_LANGS_TO,
OPT_STYLE_ALL,
DEFAULT_TRANS_APIS,
} from "../../config";
import { sendIframeMsg } from "../../libs/iframe";
import { saveRule } from "../../libs/rules";
@@ -32,6 +33,7 @@ import { kissLog } from "../../libs/log";
export default function Popup({ setShowPopup, translator: tran }) {
const i18n = useI18n();
const [rule, setRule] = useState(tran?.rule);
const [transApis, setTransApis] = useState(tran?.setting?.transApis || []);
const [commands, setCommands] = useState({});
const handleOpenSetting = () => {
@@ -106,7 +108,8 @@ export default function Popup({ setShowPopup, translator: tran }) {
try {
const res = await sendTabMsg(MSG_TRANS_GETRULE);
if (!res.error) {
setRule(res.data);
setRule(res.rule);
setTransApis(res.setting.transApis);
}
} catch (err) {
kissLog(err, "query rule");
@@ -138,6 +141,20 @@ export default function Popup({ setShowPopup, translator: tran }) {
})();
}, [tran]);
const optApis = useMemo(
() =>
OPT_TRANS_ALL.map((key) => ({
...(transApis[key] || DEFAULT_TRANS_APIS[key]),
apiKey: key,
}))
.filter((item) => !item.isDisabled)
.map(({ apiKey, apiName }) => ({
key: apiKey,
name: apiName?.trim() || apiKey,
})),
[transApis]
);
if (!rule) {
return (
<Box minWidth={300}>
@@ -197,9 +214,9 @@ export default function Popup({ setShowPopup, translator: tran }) {
label={i18n("translate_service")}
onChange={handleChange}
>
{OPT_TRANS_ALL.map((item) => (
<MenuItem key={item} value={item}>
{item}
{optApis.map(({ key, name }) => (
<MenuItem key={key} value={key}>
{name}
</MenuItem>
))}
</TextField>

View File

@@ -93,9 +93,7 @@ export default function DictCont({ text }) {
key={key}
style={{ display: "inline-block" }}
>
<Typography component="span">{`${
PHONIC_MAP[key]?.[0] || key
} ${val}`}</Typography>
<Typography component="span">{`${PHONIC_MAP[key]?.[0] || key} ${val}`}</Typography>
<AudioBtn text={dictResult.src} lan={PHONIC_MAP[key]?.[1]} />
</Typography>
))}

View File

@@ -18,13 +18,19 @@ import LockIcon from "@mui/icons-material/Lock";
import LockOpenIcon from "@mui/icons-material/LockOpen";
import CloseIcon from "@mui/icons-material/Close";
import { useI18n } from "../../hooks/I18n";
import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config";
import { useState, useRef } from "react";
import {
OPT_TRANS_ALL,
OPT_LANGS_FROM,
OPT_LANGS_TO,
DEFAULT_TRANS_APIS,
} from "../../config";
import { useState, useRef, useMemo } from "react";
import TranCont from "./TranCont";
import DictCont from "./DictCont";
import SugCont from "./SugCont";
import CopyBtn from "./CopyBtn";
import { isValidWord } from "../../libs/utils";
import { isMobile } from "../../libs/mobile";
function Header({
setShowPopup,
@@ -34,7 +40,12 @@ function Header({
setHideClickAway,
followSelection,
setFollowSelection,
mouseHover,
}) {
if (!isMobile && simpleStyle && !mouseHover) {
return;
}
return (
<Box
className="KT-transbox-header"
@@ -95,7 +106,15 @@ function Header({
);
}
function TranForm({ text, setText, tranboxSetting, transApis, simpleStyle }) {
function TranForm({
text,
setText,
tranboxSetting,
transApis,
simpleStyle,
langDetector,
enDict,
}) {
const i18n = useI18n();
const [editMode, setEditMode] = useState(false);
@@ -105,6 +124,20 @@ function TranForm({ text, setText, tranboxSetting, transApis, simpleStyle }) {
const [toLang, setToLang] = useState(tranboxSetting.toLang);
const inputRef = useRef(null);
const optApis = useMemo(
() =>
OPT_TRANS_ALL.map((key) => ({
...(transApis[key] || DEFAULT_TRANS_APIS[key]),
apiKey: key,
}))
.filter((item) => !item.isDisabled)
.map(({ apiKey, apiName }) => ({
key: apiKey,
name: apiName?.trim() || apiKey,
})),
[transApis]
);
return (
<Stack
className="KT-transbox-container"
@@ -168,9 +201,9 @@ function TranForm({ text, setText, tranboxSetting, transApis, simpleStyle }) {
setTranslator(e.target.value);
}}
>
{OPT_TRANS_ALL.map((item) => (
<MenuItem key={item} value={item}>
{item}
{optApis.map(({ key, name }) => (
<MenuItem key={key} value={key}>
{name}
</MenuItem>
))}
</TextField>
@@ -227,7 +260,10 @@ function TranForm({ text, setText, tranboxSetting, transApis, simpleStyle }) {
</>
)}
{(!simpleStyle || !isValidWord(text) || !toLang.startsWith("zh")) && (
{(!simpleStyle ||
!isValidWord(text) ||
!toLang.startsWith("zh") ||
enDict === "-") && (
<TranCont
text={text}
translator={translator}
@@ -236,11 +272,16 @@ function TranForm({ text, setText, tranboxSetting, transApis, simpleStyle }) {
toLang2={tranboxSetting.toLang2}
transApis={transApis}
simpleStyle={simpleStyle}
langDetector={langDetector}
/>
)}
{enDict !== "-" && (
<>
<DictCont text={text} />
<SugCont text={text} />
</>
)}
</Stack>
);
}
@@ -262,7 +303,10 @@ export default function TranBox({
followSelection,
setFollowSelection,
extStyles,
langDetector,
enDict,
}) {
const [mouseHover, setMouseHover] = useState(false);
return (
<SettingProvider>
<ThemeProvider styles={extStyles}>
@@ -280,9 +324,12 @@ export default function TranBox({
setHideClickAway={setHideClickAway}
followSelection={followSelection}
setFollowSelection={setFollowSelection}
mouseHover={mouseHover}
/>
}
onClick={(e) => e.stopPropagation()}
onMouseEnter={() => setMouseHover(true)}
onMouseLeave={() => setMouseHover(false)}
>
<TranForm
text={text}
@@ -290,6 +337,8 @@ export default function TranBox({
tranboxSetting={tranboxSetting}
transApis={transApis}
simpleStyle={simpleStyle}
langDetector={langDetector}
enDict={enDict}
/>
</DraggableResizable>
</ThemeProvider>

View File

@@ -5,10 +5,11 @@ import Stack from "@mui/material/Stack";
import { useI18n } from "../../hooks/I18n";
import { DEFAULT_TRANS_APIS } from "../../config";
import { useEffect, useState } from "react";
import { apiTranslate, apiBaiduLangdetect } from "../../apis";
import { apiTranslate } from "../../apis";
import CopyBtn from "./CopyBtn";
import Typography from "@mui/material/Typography";
import Alert from "@mui/material/Alert";
import { tryDetectLang } from "../../libs";
export default function TranCont({
text,
@@ -18,6 +19,7 @@ export default function TranCont({
toLang2 = "en",
transApis,
simpleStyle,
langDetector,
}) {
const i18n = useI18n();
const [trText, setTrText] = useState("");
@@ -32,8 +34,8 @@ export default function TranCont({
setError("");
let to = toLang;
if (toLang !== toLang2 && toLang2 !== "none") {
const detectLang = await apiBaiduLangdetect(text);
if (fromLang === "auto" && toLang !== toLang2 && toLang2 !== "none") {
const detectLang = await tryDetectLang(text, true, langDetector);
if (detectLang === toLang) {
to = toLang2;
}
@@ -55,7 +57,7 @@ export default function TranCont({
setLoading(false);
}
})();
}, [text, translator, fromLang, toLang, toLang2, transApis]);
}, [text, translator, fromLang, toLang, toLang2, transApis, langDetector]);
if (simpleStyle) {
return (

View File

@@ -10,6 +10,7 @@ import {
OPT_TRANBOX_TRIGGER_CLICK,
OPT_TRANBOX_TRIGGER_HOVER,
OPT_TRANBOX_TRIGGER_SELECT,
OPT_DICT_BAIDU,
} from "../../config";
import { isMobile } from "../../libs/mobile";
import { kissLog } from "../../libs/log";
@@ -20,6 +21,7 @@ export default function Slection({
tranboxSetting,
transApis,
uiLang,
langDetector,
}) {
const {
hideTranBtn = false,
@@ -33,6 +35,7 @@ export default function Slection({
btnOffsetY,
boxOffsetX = 0,
boxOffsetY = 10,
enDict = OPT_DICT_BAIDU,
} = tranboxSetting;
const boxWidth =
@@ -234,6 +237,8 @@ export default function Slection({
followSelection={followSelection}
setFollowSelection={setFollowSelection}
extStyles={extStyles}
langDetector={langDetector}
enDict={enDict}
/>
)}