Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39b3b00117 | ||
|
|
763019f0c5 | ||
|
|
d743271be8 | ||
|
|
992dad26aa | ||
|
|
9bd0e67474 | ||
|
|
5767a4afb2 | ||
|
|
9e4c510684 | ||
|
|
16607fb069 | ||
|
|
e047a06432 | ||
|
|
9e09fd898a | ||
|
|
799c32a871 | ||
|
|
483f33b5c9 | ||
|
|
d444fd4fba | ||
|
|
0aae93ba2e | ||
|
|
9608bea3bf | ||
|
|
a038a1ecdc | ||
|
|
c82cdd7f8f | ||
|
|
0c6d5c3c61 | ||
|
|
900426f359 | ||
|
|
1d760fc93a | ||
|
|
61571e0f61 | ||
|
|
c9eb423c89 | ||
|
|
45b294a121 | ||
|
|
3a3f1fabe1 | ||
|
|
03177a09b3 | ||
|
|
cae391f62b |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
||||
|
||||
REACT_APP_NAME=KISS Translator
|
||||
REACT_APP_NAME_CN=简约翻译
|
||||
REACT_APP_VERSION=1.9.1
|
||||
REACT_APP_VERSION=1.9.2
|
||||
|
||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||
|
||||
|
||||
118
README.en.md
118
README.en.md
@@ -11,10 +11,18 @@ A simple, open source [bilingual translation extension & Greasemonkey script](ht
|
||||
- [x] Keep it simple, smart
|
||||
- [x] Open source
|
||||
- [x] Adapt to common browsers
|
||||
- [x] Chrome/Edge/Firefox/Kiwi/Orion
|
||||
- [x] Chrome/Edge
|
||||
- [x] Firefox
|
||||
- [x] Kiwi (Android)
|
||||
- [x] Orion (iOS)
|
||||
- [ ] Safari
|
||||
- [x] Safari (Mac)
|
||||
- [x] Thunderbird
|
||||
- [x] Supports multiple translation services
|
||||
- [x] Google/Microsoft/DeepL/NiuTrans/OpenAI/Gemini/CloudflareAI/Baidu/Tencent
|
||||
- [x] Google/Microsoft
|
||||
- [x] Baidu/Tencent/Volcengine
|
||||
- [x] OpenAI/Gemini/Claude/Ollama/DeepSeek/CloudflareAI
|
||||
- [x] DeepL/DeepLX/NiuTrans
|
||||
- [x] Custom translation interface
|
||||
- [x] Covers common translation scenarios
|
||||
- [x] Web bilingual translation
|
||||
@@ -52,6 +60,8 @@ A simple, open source [bilingual translation extension & Greasemonkey script](ht
|
||||
- [x] Edge [Installation address](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
|
||||
- [x] Firefox [Installation address](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
||||
- [ ] Safari
|
||||
- [x] Safari (Mac) Compiled by a third party, not verified, obtained by yourself: https://www.nodeloc.com/t/topic/54245
|
||||
- [x] Thunderbird [Download address](https://github.com/fishjar/kiss-translator/releases)
|
||||
- [x] GreaseMonkey Script
|
||||
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [Installation link](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
||||
- [Greasy Fork](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
||||
@@ -69,16 +79,112 @@ A simple, open source [bilingual translation extension & Greasemonkey script](ht
|
||||
- Translation interface agent: [https://github.com/fishjar/kiss-proxy](https://github.com/fishjar/kiss-proxy)
|
||||
- If you encounter network problems when accessing a certain translation interface, this proxy service may help you.
|
||||
- Deploy and manage by yourself.
|
||||
- Minimalistic Dictionary Plugin: [https://github.com/fishjar/kiss-dictionary](https://github.com/fishjar/kiss-dictionary)
|
||||
- A word-marking translation plug-in used with this project.
|
||||
- Supports query of English words, sentences and Chinese characters.
|
||||
- Supports history records and word collections.
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How to Turn Off Automatic Translation
|
||||
|
||||
You can achieve this through `Rules Setting` with the following methods:
|
||||
|
||||
- Personal Rules: RULES-> Global Rule -> Translate Switch -> Disaabled
|
||||
- Subscription Rules: SUBSCRIBE -> Select the third option `kiss-rules-off.json`
|
||||
- Override Subscription Rules: OVERWRITE -> Translate Switch -> Disaabled
|
||||
- Add a Personal Rule for a Specific Website: Translate Switch -> Disaabled
|
||||
|
||||
### How to Set Keyboard Shortcuts
|
||||
|
||||
Set this in the extension management page, for example:
|
||||
|
||||
- chrome [chrome://extensions/shortcuts](chrome://extensions/shortcuts)
|
||||
- firefox [about:addons](about:addons)
|
||||
|
||||
### How to Turn Off Selection Translation
|
||||
|
||||
Set this in the `Rules Setting`: RULES -> Global Rule -> If translate selected -> Disable
|
||||
|
||||
### How to Set it to Show Only the Translation
|
||||
|
||||
Set this in the `Rules Setting`: RULES -> Global Rule -> Show Only Translations -> Enable
|
||||
|
||||
### How to Set Mouse Hover Translation
|
||||
|
||||
Set this in the `Rules Setting`: RULES -> Global Rule -> TTrigger Mode
|
||||
|
||||
### Why are some web pages not fully translated?
|
||||
|
||||
This extension's webpage translation is based on CSS selectors. Generic rules cannot adapt to all websites, and sometimes you need to manually add site-specific rules. If you don't know how to write rules, you can seek help here:
|
||||
https://github.com/fishjar/kiss-rules/issues
|
||||
|
||||
### What is the priority order of rule settings?
|
||||
|
||||
Personal Rules > Override Subscription Rules > Subscription Rules > Global Rules
|
||||
|
||||
Among these, Global Rules have the lowest priority but are very important as they serve as the default rules.
|
||||
|
||||
### Why are YouTube subtitles translated in broken sentences?
|
||||
|
||||
This extension has no special development for video content. Support for YouTube is also treated as regular webpage translation. Auto-generated subtitles are streamed and output progressively, resulting in poorer support.
|
||||
|
||||
To disable this extension's subtitle translation, add a rule. Reference: https://github.com/fishjar/kiss-translator/issues/62
|
||||
|
||||
### Local Ollama interface cannot be used
|
||||
|
||||
If encountering a 403 error, refer to: https://github.com/fishjar/kiss-translator/issues/174
|
||||
|
||||
### Custom API doesn't work in Tampermonkey scripts
|
||||
|
||||
Tampermonkey scripts require adding domains to the whitelist; otherwise, requests cannot be sent.
|
||||
|
||||
### How to Set Up Hook Functions for Custom Interfaces
|
||||
|
||||
The custom interface feature is highly flexible and can theoretically integrate with any translation interface.
|
||||
|
||||
Example of a Request Hook function:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Request Hook
|
||||
* @param {string} text Text to be translated
|
||||
* @param {string} from Source language
|
||||
* @param {string} to Target language
|
||||
* @param {string} url Translation interface URL
|
||||
* @param {string} key Translation interface API key
|
||||
* @returns {Array[string, object]} [Interface URL, request object]
|
||||
*/
|
||||
(text, from, to, url, key) => [url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
"Authorization": `Bearer ${key}`
|
||||
},
|
||||
method: "POST",
|
||||
body: { text, to },
|
||||
}]
|
||||
```
|
||||
|
||||
Example of a Response Hook function:
|
||||
|
||||
```js
|
||||
* Response Hook
|
||||
* @param {string} res JSON data returned by the interface
|
||||
* @param {string} text Text to be translated
|
||||
* @param {string} from Source language
|
||||
* @param {string} to Target language
|
||||
* @returns {Array[string, boolean]} [Translated text, whether target language is same as source]
|
||||
* Note: If the second return value is true (target language same as source),
|
||||
* the translation will not be displayed on the page,
|
||||
* If the parameters are incomplete, it is recommended to return false directly
|
||||
*/
|
||||
(res, text, from, to) => [res.text, to === res.src]
|
||||
```
|
||||
|
||||
For more custom interface examples, refer to: [custom-api.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api.md)
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
```sh
|
||||
git clone https://github.com/fishjar/kiss-translator.git
|
||||
cd kiss-translator
|
||||
git checkout dev # Submit a PR suggestion to push to the dev branch
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
117
README.md
117
README.md
@@ -11,10 +11,18 @@
|
||||
- [x] 保持简约
|
||||
- [x] 开放源代码
|
||||
- [x] 适配常见浏览器
|
||||
- [x] Chrome/Edge/Firefox/Kiwi/Orion
|
||||
- [x] Chrome/Edge
|
||||
- [x] Firefox
|
||||
- [x] Kiwi (Android)
|
||||
- [x] Orion (iOS)
|
||||
- [ ] Safari
|
||||
- [x] Safari (Mac)
|
||||
- [x] Thunderbird
|
||||
- [x] 支持多种翻译服务
|
||||
- [x] Google/Microsoft/DeepL/NiuTrans/OpenAI/Gemini/CloudflareAI/Baidu/Tencent
|
||||
- [x] Google/Microsoft
|
||||
- [x] Baidu/Tencent/Volcengine
|
||||
- [x] OpenAI/Gemini/Claude/Ollama/DeepSeek/CloudflareAI
|
||||
- [x] DeepL/DeepLX/NiuTrans
|
||||
- [x] 自定义翻译接口
|
||||
- [x] 覆盖常见翻译场景
|
||||
- [x] 网页双语对照翻译
|
||||
@@ -52,6 +60,8 @@
|
||||
- [x] Edge [安装地址](https://microsoftedge.microsoft.com/addons/detail/%E7%AE%80%E7%BA%A6%E7%BF%BB%E8%AF%91/jemckldkclkinpjighnoilpbldbdmmlh?hl=zh-CN)
|
||||
- [x] Firefox [安装地址](https://addons.mozilla.org/zh-CN/firefox/addon/kiss-translator/)
|
||||
- [ ] Safari
|
||||
- [x] Safari (Mac) 第三方编译,未作验证,自行获取: https://www.nodeloc.com/t/topic/54245
|
||||
- [x] Thunderbird [下载地址](https://github.com/fishjar/kiss-translator/releases)
|
||||
- [x] 油猴脚本
|
||||
- [x] Chrome/Edge/Firefox ([Tampermonkey](https://www.tampermonkey.net/)/[Violentmonkey](https://violentmonkey.github.io/)) [安装链接](https://fishjar.github.io/kiss-translator/kiss-translator.user.js)
|
||||
- [Greasy Fork](https://greasyfork.org/zh-CN/scripts/472840-kiss-translator)
|
||||
@@ -69,16 +79,111 @@
|
||||
- 翻译接口代理: [https://github.com/fishjar/kiss-proxy](https://github.com/fishjar/kiss-proxy)
|
||||
- 如果访问某个翻译接口遇到网络问题,这个代理服务也许可以帮到你。
|
||||
- 自己部署,自己管理。
|
||||
- 简约词典插件: [https://github.com/fishjar/kiss-dictionary](https://github.com/fishjar/kiss-dictionary)
|
||||
- 搭配本项目一起使用的划词翻译插件。
|
||||
- 支持英文单词、句子、汉字的查询。
|
||||
- 支持历史记录、单词收藏。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 如何关闭自动翻译
|
||||
|
||||
通过规则设置,以下方法均可实现:
|
||||
|
||||
- 个人规则:全局规则 -> 开启翻译 -> 默认关闭
|
||||
- 订阅规则:选择第三个 `kiss-rules-off.json`
|
||||
- 覆写订阅规则:开启翻译 -> 默认关闭
|
||||
- 添加一条针对某个网站的个人规则:开启翻译 -> 默认关闭
|
||||
|
||||
### 如何设置快捷键
|
||||
|
||||
在插件管理那里设置,例如:
|
||||
|
||||
- chrome [chrome://extensions/shortcuts](chrome://extensions/shortcuts)
|
||||
- firefox [about:addons](about:addons)
|
||||
|
||||
### 如何关闭划词翻译
|
||||
|
||||
通过规则设置:个人规则 -> 全局规则 -> 是否启用划词翻译 -> 禁用
|
||||
|
||||
### 如何设置仅显示译文
|
||||
|
||||
通过规则设置:个人规则 -> 全局规则 -> 仅显示译文 -> 启用
|
||||
|
||||
### 如何设置鼠标悬停翻译
|
||||
|
||||
通过规则设置:个人规则 -> 全局规则 -> 触发方式
|
||||
|
||||
### 为什么有些网页翻译不全
|
||||
|
||||
本插件的网页翻译是基于CSS选择器的,通用规则不能适配所有网页,有时需要自行添加相应网站的单独规则。如果不会写规则,可以到这里求助: https://github.com/fishjar/kiss-rules/issues
|
||||
|
||||
### 规则设置的优先级是如何的
|
||||
|
||||
个人规则 > 覆写订阅规则 > 订阅规则 > 全局规则
|
||||
|
||||
其中全局规则优先级最低,但非常重要,相当于默认规则。
|
||||
|
||||
### 为什么油管字幕一句话会断开翻译
|
||||
|
||||
本插件目前没有针对视频做特殊开发,对油管的支持也是当做网页翻译看待,自动生成字幕是流式生成并输出的,所以支持较差。
|
||||
|
||||
如果需要关闭本插件的字幕翻译,增加一条规则即可,参考:https://github.com/fishjar/kiss-translator/issues/62
|
||||
|
||||
### 本地的Ollama接口不能使用
|
||||
|
||||
如果出现403的情况,参考:https://github.com/fishjar/kiss-translator/issues/174
|
||||
|
||||
### 填写的接口在油猴脚本不能使用
|
||||
|
||||
油猴脚本需要增加域名白名单,否则不能发出请求。
|
||||
|
||||
### 如何设置自定义接口的hook函数
|
||||
|
||||
自定义接口功能非常灵活,理论可以接入任何翻译接口。
|
||||
|
||||
Request Hook 函数示例如下:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Request Hook
|
||||
* @param {string} text 需要翻译的原文
|
||||
* @param {string} from 原文语言
|
||||
* @param {string} to 译文语言
|
||||
* @param {string} url 翻译接口地址
|
||||
* @param {string} key 翻译接口密钥
|
||||
* @returns {Array[string, object]} [接口地址, 请求参数对象]
|
||||
*/
|
||||
(text, from, to, url, key) => [url, {
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
"Authorization": `Bearer ${key}`
|
||||
},
|
||||
method: "POST",
|
||||
body: { text, to },
|
||||
}]
|
||||
```
|
||||
|
||||
Response Hook 函数示例如下:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Request Hook
|
||||
* @param {string} res 接口返回的json数据
|
||||
* @param {string} text 需要翻译的原文
|
||||
* @param {string} from 原文语言
|
||||
* @param {string} to 译文语言
|
||||
* @returns {Array[string, boolean]} [译文, 译文语言与原文语言是否相同]
|
||||
* 注:如果返回值第二个值为true(译文语言与原文语言相同)则译文不会在页面显示,
|
||||
* 参数不全的情况建议直接返回false
|
||||
*/
|
||||
(res, text, from, to) => [res.text, to === res.src]
|
||||
```
|
||||
|
||||
更多的自定义接口示例,请参考: [custom-api.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api.md)
|
||||
|
||||
## 开发指引
|
||||
|
||||
```sh
|
||||
git clone https://github.com/fishjar/kiss-translator.git
|
||||
cd kiss-translator
|
||||
git checkout dev # 提交PR建议推送到dev分支
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
346
custom-api.md
Normal file
346
custom-api.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# 自定义接口示例
|
||||
|
||||
以下示例为网友提供,仅供学习参考。
|
||||
|
||||
## 本地运行 Seed-X-PPO-7B 量化模型
|
||||
|
||||
> 由网友 emptyghost6 提供,来源:https://linux.do/t/topic/828257
|
||||
|
||||
URL
|
||||
|
||||
```sh
|
||||
http://localhost:8000/v1/completions
|
||||
```
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => {
|
||||
// 模型支持的语言代码到完整名称的映射
|
||||
const langFullNameMap = {
|
||||
ar: 'Arabic', fr: 'French', ms: 'Malay', ru: 'Russian',
|
||||
cs: 'Czech', hr: 'Croatian', nb: 'Norwegian Bokmal', sv: 'Swedish',
|
||||
da: 'Danish', hu: 'Hungarian', nl: 'Dutch', th: 'Thai',
|
||||
de: 'German', id: 'Indonesian', no: 'Norwegian', tr: 'Turkish',
|
||||
en: 'English', it: 'Italian', pl: 'Polish', uk: 'Ukrainian',
|
||||
es: 'Spanish', ja: 'Japanese', pt: 'Portuguese', vi: 'Vietnamese',
|
||||
fi: 'Finnish', ko: 'Korean', ro: 'Romanian', zh: 'Chinese'
|
||||
};
|
||||
|
||||
// 将 Hook 系统的语言代码转换为模型 API 支持的代码
|
||||
const getModelLangCode = (lang) => {
|
||||
if (lang === 'zh-CN' || lang === 'zh-TW') return 'zh';
|
||||
return lang;
|
||||
};
|
||||
|
||||
const sourceLangCode = getModelLangCode(from);
|
||||
const targetLangCode = getModelLangCode(to);
|
||||
|
||||
const sourceLangName = langFullNameMap[sourceLangCode] || from;
|
||||
const targetLangName = langFullNameMap[targetLangCode] || to;
|
||||
|
||||
const prompt = `Translate it to ${targetLangName}:\n${text} <${targetLangCode}>`;
|
||||
|
||||
// 构建请求体对象
|
||||
const bodyObject = {
|
||||
model: "./ByteDance-Seed/Seed-X-PPO-7B-AWQ-Int4",
|
||||
prompt: prompt,
|
||||
max_tokens: 2048,
|
||||
temperature: 0.0,
|
||||
};
|
||||
|
||||
// 返回最终的请求配置
|
||||
return [url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
// 关键改动:将 JavaScript 对象转换为 JSON 字符串
|
||||
body: JSON.stringify(bodyObject),
|
||||
}];
|
||||
}
|
||||
```
|
||||
|
||||
Response Hook
|
||||
|
||||
```js
|
||||
(res, text, from, to) => {
|
||||
// 检查返回是否有效
|
||||
if (res && res.choices && res.choices.length > 0 && res.choices[0].text) {
|
||||
|
||||
// 提取译文并去除可能存在的前后空格
|
||||
const translatedText = res.choices[0].text.trim();
|
||||
|
||||
// 比较原文与译文,相同为 true,否则为 false。
|
||||
const areTextsIdentical = text.trim() === translatedText;
|
||||
|
||||
// 返回数组:[翻译后的文本, 是否与原文相同]
|
||||
return [translatedText, areTextsIdentical];
|
||||
}
|
||||
// 如果响应格式不正确或没有结果,则抛出错误
|
||||
throw new Error("Invalid API response format or no translation found.");
|
||||
}
|
||||
```
|
||||
|
||||
## 接入 openrouter
|
||||
|
||||
> 由网友 Rick Sanchez 提供
|
||||
|
||||
URL
|
||||
|
||||
```sh
|
||||
https://openrouter.ai/api/v1/chat/completions
|
||||
```
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => [url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${key}`,
|
||||
"Content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"model": "deepseek/deepseek-chat-v3-0324:free", //可自定义你的模型
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": //可自定义你的提示词
|
||||
`You are a professional ${to} native translator. Your task is to produce a fluent, natural, and culturally appropriate translation of the following text from ${from} to ${to}, fully conveying the meaning, tone, and nuance of the original.
|
||||
|
||||
## Translation Rules
|
||||
1. Output only the final polished translation — no explanations, intermediate drafts, or notes.
|
||||
2. Translate in a way that reads naturally to a native ${to} audience, adapting idioms, cultural references, and tone when necessary.
|
||||
3. Preserve proper nouns, technical terms, brand names, and URLs exactly as in the original text unless a widely accepted ${to} equivalent exists.
|
||||
4. Keep any formatting (Markdown, HTML tags, bullet points, numbering) intact and positioned naturally within the translation.
|
||||
5. Adapt humor, metaphors, and figurative language to culturally relevant forms in ${to} while keeping the original intent.
|
||||
6. Maintain the same level of formality or informality as the original.
|
||||
|
||||
Source Text: ${text}
|
||||
|
||||
Translated Text:`
|
||||
}
|
||||
]
|
||||
})
|
||||
}]
|
||||
```
|
||||
|
||||
Response Hook
|
||||
|
||||
```js
|
||||
(res, text, from, to) => [
|
||||
res.choices?.[0]?.message?.content ?? "",
|
||||
false
|
||||
]
|
||||
```
|
||||
|
||||
## 接入 gemini-2.5-flash, 关闭思考模式, 去审查
|
||||
|
||||
> 由网友 Rick Sanchez 提供
|
||||
|
||||
URL
|
||||
|
||||
```sh
|
||||
https://generativelanguage.googleapis.com/v1beta/models
|
||||
```
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => [`${url}/gemini-2.5-flash:generateContent?key=${key}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"generationConfig": {
|
||||
"temperature": 0.8,
|
||||
"thinkingConfig": {
|
||||
"thinkingBudget": 0, //gemini-2.5-flash设为0关闭思考模式
|
||||
},
|
||||
},
|
||||
"safetySettings": [
|
||||
{
|
||||
"category": "HARM_CATEGORY_HARASSMENT",
|
||||
"threshold": "BLOCK_NONE",
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_HATE_SPEECH",
|
||||
"threshold": "BLOCK_NONE",
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
||||
"threshold": "BLOCK_NONE",
|
||||
},
|
||||
{
|
||||
"category": "HARM_CATEGORY_DANGEROUS_CONTENT",
|
||||
"threshold": "BLOCK_NONE",
|
||||
}
|
||||
],
|
||||
"contents": [{
|
||||
"parts": [{
|
||||
"text": `自定义提示词`
|
||||
}]
|
||||
}],
|
||||
}),
|
||||
}]
|
||||
```
|
||||
|
||||
Response Hook
|
||||
|
||||
```js
|
||||
(res, text, from, to) => [
|
||||
res.candidates?.[0]?.content?.parts?.[0]?.text ?? "",
|
||||
false
|
||||
]
|
||||
```
|
||||
|
||||
## 接入 Qwen-MT
|
||||
|
||||
> 由网友 atom 提供
|
||||
|
||||
URL
|
||||
|
||||
```sh
|
||||
https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
|
||||
```
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => {
|
||||
const mapLanguageCode = (lang) => ({
|
||||
'zh-CN': 'zh',
|
||||
'zh-TW': 'zh_tw',
|
||||
})[lang] || lang;
|
||||
|
||||
const targetLang = mapLanguageCode(to);
|
||||
|
||||
return [
|
||||
url,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${key}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"model": "qwen-mt-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": text
|
||||
}
|
||||
],
|
||||
"translation_options": {
|
||||
"source_lang": "auto",
|
||||
"target_lang": targetLang
|
||||
}
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
Response Hook
|
||||
|
||||
```js
|
||||
(res, text, from, to) => [res.choices?.[0]?.message?.content ?? "", false]
|
||||
```
|
||||
|
||||
|
||||
## 接入 deepl 接口
|
||||
|
||||
> 来源: https://github.com/fishjar/kiss-translator/issues/101#issuecomment-2123786236
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => [
|
||||
url,
|
||||
{
|
||||
headers: {
|
||||
"Content-type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
text,
|
||||
target_lang: "ZH",
|
||||
source_lang: "auto",
|
||||
}),
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
Response Hook
|
||||
|
||||
```js
|
||||
(res, text, from, to) => [res.data, "ZH" === res.source_lang]
|
||||
```
|
||||
|
||||
## 接入智谱AI大模型
|
||||
|
||||
> 来源: https://github.com/fishjar/kiss-translator/issues/205#issuecomment-2642422679
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => [url, {
|
||||
"method": "POST",
|
||||
"headers": {
|
||||
"Content-type": "application/json",
|
||||
"Authorization": key
|
||||
},
|
||||
"body": JSON.stringify({
|
||||
"model": "glm-4-flash",
|
||||
"messages": [
|
||||
{
|
||||
"role":"system",
|
||||
"content": "You are a professional, authentic machine translation engine. You only return the translated text, without any explanations."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": `Translate the following text into ${to}. If translation is unnecessary (e.g. proper nouns, codes, etc.), return the original text. NO explanations. NO notes:\n\n ${text} `
|
||||
}
|
||||
]
|
||||
})
|
||||
}]
|
||||
```
|
||||
|
||||
## 接入谷歌新接口
|
||||
|
||||
> 由网友 Bush2021 提供,来源:https://github.com/fishjar/kiss-translator/issues/225#issuecomment-2810950717
|
||||
|
||||
URL
|
||||
|
||||
```sh
|
||||
https://translate-pa.googleapis.com/v1/translateHtml
|
||||
```
|
||||
|
||||
KEY
|
||||
|
||||
```sh
|
||||
AIzaSyATBXajvzQLTDHEQbcpq0Ihe0vWDHmO520
|
||||
```
|
||||
|
||||
Request Hook
|
||||
|
||||
```js
|
||||
(text, from, to, url, key) => [url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json+protobuf",
|
||||
"X-Goog-API-Key": key
|
||||
},
|
||||
body: JSON.stringify([[[text], from || "auto", to], "wt_lib"])
|
||||
}]
|
||||
```
|
||||
|
||||
Response Hook
|
||||
|
||||
```js
|
||||
(res, text, from, to) => [res?.[0]?.join(" ") || "Translation unavailable", to === res?.[1]?.[0]]
|
||||
```
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kiss-translator",
|
||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.2",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@@ -33,7 +33,7 @@
|
||||
"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 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 && rm -f *.zip && zip -r chrome.zip chrome && zip -r edge.zip edge && (cd firefox && zip -r ../firefox.zip .) && (cd thunderbird && zip -r ../thunderbird.zip .)",
|
||||
"zip": "cd build && rm -f *.zip && zip -r chrome.zip chrome && zip -r edge.zip edge && zip -r userscript.zip userscript && (cd firefox && zip -r ../firefox.zip .) && (cd thunderbird && zip -r ../thunderbird.zip .)",
|
||||
"build+zip": "pnpm build && pnpm zip",
|
||||
"format": "prettier --write \"**/*.{js,json,html}\"",
|
||||
"test": "react-app-rewired test",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.2",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.2",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 2,
|
||||
"name": "__MSG_app_name__",
|
||||
"description": "__MSG_app_description__",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.2",
|
||||
"default_locale": "en",
|
||||
"author": "Gabe<yugang2002@gmail.com>",
|
||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||
|
||||
@@ -267,7 +267,7 @@ const genOpenAI = ({
|
||||
},
|
||||
],
|
||||
temperature,
|
||||
max_tokens: maxTokens,
|
||||
max_completion_tokens: maxTokens,
|
||||
};
|
||||
|
||||
const init = {
|
||||
|
||||
@@ -45,9 +45,9 @@ const REMOVE_HEADERS = [
|
||||
async function addContextMenus(contextMenuType = 1) {
|
||||
// 添加前先删除,避免重复ID的错误
|
||||
try {
|
||||
await browser.menus.removeAll();
|
||||
await browser.contextMenus.removeAll();
|
||||
} catch (err) {
|
||||
//
|
||||
kissLog(err, "remove contextMenus");
|
||||
}
|
||||
|
||||
switch (contextMenuType) {
|
||||
|
||||
@@ -42,21 +42,6 @@ const customApiLangs = `["en", "English - English"],
|
||||
["vi", "Vietnamese - Tiếng Việt"],
|
||||
`;
|
||||
|
||||
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}}",
|
||||
@@ -82,7 +67,21 @@ const customApiHelpZH = `// 请求数据默认格式
|
||||
|
||||
|
||||
// Hook 范例
|
||||
${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]
|
||||
|
||||
|
||||
// 支持的语言代码如下
|
||||
@@ -114,7 +113,22 @@ const customApiHelpEN = `// Default request
|
||||
|
||||
|
||||
/// Hook Example
|
||||
${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
|
||||
// In the returned array, the first value is the translated string, while the second value is a boolean
|
||||
// that indicates whether the source language is the same as the target language.
|
||||
(res, text, from, to) => [res.sentences.map((item) => item.trans).join(" "), to === res.src]
|
||||
|
||||
|
||||
// The supported language codes are as follows
|
||||
@@ -359,12 +373,20 @@ export const I18N = {
|
||||
en: `3. Regarding filling in the rules: Leave the input box blank or select "*" in the drop-down box to use global rule.`,
|
||||
},
|
||||
sync_warn: {
|
||||
zh: `涉及隐私数据的同步请谨慎选择第三方同步服务,建议自行搭建 kiss-worker 或 WebDAV 服务。`,
|
||||
en: `When synchronizing data that involves privacy, please be cautious about choosing third-party sync services. It is recommended to set up your own sync service using kiss-worker or WebDAV.`,
|
||||
},
|
||||
sync_warn_2: {
|
||||
zh: `如果服务器存在其他客户端同步的数据,第一次同步将直接覆盖本地配置,后面则根据修改时间,新的覆盖旧的。`,
|
||||
en: `If the server has data synchronized by other clients, the first synchronization will directly overwrite the local configuration, and later, according to the modification time, the new one will overwrite the old one.`,
|
||||
},
|
||||
about_sync_api: {
|
||||
zh: `查看关于数据同步接口部署`,
|
||||
en: `View About Data Synchronization Interface Deployment`,
|
||||
zh: `自建kiss-wroker数据同步服务`,
|
||||
en: `Self-hosting a Kiss-worker data sync service`,
|
||||
},
|
||||
about_api: {
|
||||
zh: `暂未列出的接口,理论上都可以通过自定义接口的形式支持。`,
|
||||
en: `Interfaces that have not yet been launched can theoretically be supported through custom interfaces.`,
|
||||
},
|
||||
about_api_proxy: {
|
||||
zh: `查看自建一个翻译接口代理`,
|
||||
|
||||
@@ -443,7 +443,7 @@ export const INPUT_PLACE_MODEL = "{{model}}"; // 占位符
|
||||
|
||||
export const DEFAULT_COLOR = "#209CEE"; // 默认高亮背景色/线条颜色
|
||||
|
||||
export const DEFAULT_TRANS_TAG = "span";
|
||||
export const DEFAULT_TRANS_TAG = "font";
|
||||
export const DEFAULT_SELECT_STYLE =
|
||||
"-webkit-line-clamp: unset; max-height: none; height: auto;";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const loadingSvg = `
|
||||
<svg viewBox="0 0 100 100" style="display:inline-block; width:100%; height: 100%;">
|
||||
<svg viewBox="0 0 100 100" style="display:inline-block; width:100%; height: 100%; max-width: 24; max-height: 24;">
|
||||
<circle fill="#209CEE" stroke="none" cx="6" cy="50" r="6">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from "../config";
|
||||
import Content from "../views/Content";
|
||||
import { updateFetchPool, clearFetchPool } from "./fetch";
|
||||
import { debounce, genEventName } from "./utils";
|
||||
import { debounce, genEventName, getHtmlText } from "./utils";
|
||||
import { runFixer } from "./webfix";
|
||||
import { apiTranslate } from "../apis";
|
||||
import { sendBgMsg } from "./msg";
|
||||
@@ -475,26 +475,19 @@ export class Translator {
|
||||
return;
|
||||
}
|
||||
|
||||
const preText = this._tranNodes.get(el);
|
||||
const curText = el.innerText.trim();
|
||||
// const traText = traEl.innerText.trim();
|
||||
|
||||
// todo
|
||||
// 1. traText when loading
|
||||
// 2. replace startsWith
|
||||
if (curText.startsWith(preText)) {
|
||||
const preText = getHtmlText(this._tranNodes.get(el));
|
||||
const curText = getHtmlText(el.innerHTML, APP_LCNAME);
|
||||
if (preText === curText) {
|
||||
return;
|
||||
}
|
||||
|
||||
traEl.remove();
|
||||
}
|
||||
|
||||
// 缓存已翻译元素
|
||||
this._tranNodes.set(el, el.innerHTML);
|
||||
|
||||
let q = el.innerText.trim();
|
||||
if (this._rule.transOnly === "true") {
|
||||
this._tranNodes.set(el, el.innerHTML);
|
||||
} else {
|
||||
this._tranNodes.set(el, q);
|
||||
}
|
||||
const keeps = [];
|
||||
|
||||
// 翻译开始钩子函数
|
||||
|
||||
@@ -250,3 +250,20 @@ export const blobToBase64 = (blob) => {
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取html内的文本
|
||||
* @param {*} htmlStr
|
||||
* @param {*} skipTag
|
||||
* @returns
|
||||
*/
|
||||
export const getHtmlText = (htmlStr, skipTag = "") => {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlStr, "text/html");
|
||||
|
||||
if (skipTag) {
|
||||
doc.querySelectorAll(skipTag).forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
return doc.body.innerText.trim();
|
||||
};
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
OPT_TRANS_CUSTOMIZE_4,
|
||||
OPT_TRANS_CUSTOMIZE_5,
|
||||
OPT_TRANS_NIUTRANS,
|
||||
URL_KISS_PROXY,
|
||||
URL_NIUTRANS_REG,
|
||||
DEFAULT_FETCH_LIMIT,
|
||||
DEFAULT_FETCH_INTERVAL,
|
||||
@@ -118,9 +117,8 @@ function TestButton({ translator, api }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ApiFields({ translator }) {
|
||||
function ApiFields({ translator, api, updateApi, resetApi }) {
|
||||
const i18n = useI18n();
|
||||
const { api, updateApi, resetApi } = useApi(translator);
|
||||
const {
|
||||
url = "",
|
||||
key = "",
|
||||
@@ -431,6 +429,7 @@ function ApiFields({ translator }) {
|
||||
|
||||
function ApiAccordion({ translator }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { api, updateApi, resetApi } = useApi(translator);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setExpanded((pre) => !pre);
|
||||
@@ -439,10 +438,19 @@ function ApiAccordion({ translator }) {
|
||||
return (
|
||||
<Accordion expanded={expanded} onChange={handleChange}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography>{translator}</Typography>
|
||||
<Typography>
|
||||
{api.apiName ? `${translator} (${api.apiName})` : translator}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{expanded && <ApiFields translator={translator} />}
|
||||
{expanded && (
|
||||
<ApiFields
|
||||
translator={translator}
|
||||
api={api}
|
||||
updateApi={updateApi}
|
||||
resetApi={resetApi}
|
||||
/>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
@@ -453,11 +461,7 @@ export default function Apis() {
|
||||
return (
|
||||
<Box>
|
||||
<Stack spacing={3}>
|
||||
<Alert severity="info">
|
||||
<Link href={URL_KISS_PROXY} target="_blank">
|
||||
{i18n("about_api_proxy")}
|
||||
</Link>
|
||||
</Alert>
|
||||
<Alert severity="info">{i18n("about_api")}</Alert>
|
||||
|
||||
<Box>
|
||||
{OPT_TRANS_ALL.map((translator) => (
|
||||
|
||||
@@ -65,6 +65,7 @@ export default function SyncSetting() {
|
||||
<Box>
|
||||
<Stack spacing={3}>
|
||||
<Alert severity="warning">{i18n("sync_warn")}</Alert>
|
||||
<Alert severity="warning">{i18n("sync_warn_2")}</Alert>
|
||||
|
||||
<TextField
|
||||
select
|
||||
|
||||
Reference in New Issue
Block a user