Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9693436bb | ||
|
|
6b18d8f934 | ||
|
|
65c325de9a | ||
|
|
8da5aaf259 | ||
|
|
00e8fdd3e6 | ||
|
|
b2eea5d0d7 | ||
|
|
9a8e24f590 | ||
|
|
32c6d45cb0 | ||
|
|
74ce6f2f1f | ||
|
|
573865cf10 | ||
|
|
56d4733e2a | ||
|
|
a8965a01e3 | ||
|
|
beef51ef38 |
2
.env
2
.env
@@ -2,7 +2,7 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_NAME_CN=简约翻译
|
REACT_APP_NAME_CN=简约翻译
|
||||||
REACT_APP_VERSION=2.0.1
|
REACT_APP_VERSION=2.0.2
|
||||||
|
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,12 @@ If encountering a 403 error, refer to: https://github.com/fishjar/kiss-translato
|
|||||||
|
|
||||||
Tampermonkey scripts require adding domains to the whitelist; otherwise, requests cannot be sent.
|
Tampermonkey scripts require adding domains to the whitelist; otherwise, requests cannot be sent.
|
||||||
|
|
||||||
|
### How to set up a hook function for a custom API
|
||||||
|
|
||||||
|
Custom APIs are very powerful and flexible, and can theoretically connect to any translation API.
|
||||||
|
|
||||||
|
Example reference: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||||
|
|
||||||
## Future Plans
|
## Future Plans
|
||||||
|
|
||||||
This is a side project with no strict timeline. Community contributions are welcome. The following are preliminary feature directions:
|
This is a side project with no strict timeline. Community contributions are welcome. The following are preliminary feature directions:
|
||||||
|
|||||||
@@ -143,6 +143,12 @@
|
|||||||
|
|
||||||
油猴脚本需要增加域名白名单,否则不能发出请求。
|
油猴脚本需要增加域名白名单,否则不能发出请求。
|
||||||
|
|
||||||
|
### 如何设置自定义接口的hook函数
|
||||||
|
|
||||||
|
自定义接口功能非常强大、灵活,理论可以接入任何翻译接口。
|
||||||
|
|
||||||
|
示例参考: [custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||||
|
|
||||||
## 未来规划
|
## 未来规划
|
||||||
|
|
||||||
本项目为业余开发,无严格时间表,欢迎社区共建。以下为初步设想的功能方向:
|
本项目为业余开发,无严格时间表,欢迎社区共建。以下为初步设想的功能方向:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# 自定义接口示例(本文档已过期,新版不再适用)
|
# 自定义接口示例(本文档已过期,新版不再适用)
|
||||||
|
|
||||||
|
V2版的示例请查看这里:[custom-api_v2.md](https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md)
|
||||||
|
|
||||||
以下示例为网友提供,仅供学习参考。
|
以下示例为网友提供,仅供学习参考。
|
||||||
|
|
||||||
## 本地运行 Seed-X-PPO-7B 量化模型
|
## 本地运行 Seed-X-PPO-7B 量化模型
|
||||||
|
|||||||
206
custom-api_v2.md
Normal file
206
custom-api_v2.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# 自定义接口示例
|
||||||
|
|
||||||
|
## 谷歌翻译接口
|
||||||
|
|
||||||
|
> 此接口不支持聚合
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
```js
|
||||||
|
async (args) => {
|
||||||
|
const url = args.url.replace("{{text}}", args.texts[0]);
|
||||||
|
const method = "GET";
|
||||||
|
return { url, method };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Response Hook
|
||||||
|
|
||||||
|
```js
|
||||||
|
async ({ res }) => {
|
||||||
|
return { translations: [[res?.sentences?.[0]?.trans || "", res?.src]] };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Ollama
|
||||||
|
|
||||||
|
> 此示例为支持聚合的模型类(要支持上下文,需进一步改动)
|
||||||
|
|
||||||
|
* 注意 ollama 启动参数需要添加环境变量 `OLLAMA_ORIGINS=*`
|
||||||
|
* 检查环境变量生效命令:`systemctl show ollama | grep OLLAMA_ORIGINS`
|
||||||
|
|
||||||
|
URL
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:11434/v1/chat/completions
|
||||||
|
```
|
||||||
|
|
||||||
|
Request Hook
|
||||||
|
|
||||||
|
```js
|
||||||
|
async (args) => {
|
||||||
|
const url = args.url;
|
||||||
|
const method = "POST";
|
||||||
|
const headers = { "Content-type": "application/json" };
|
||||||
|
const body = {
|
||||||
|
model: "gemma3",
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
'Act as a translation API. Output a single raw JSON object only. No extra text or fences.\n\nInput:\n{"targetLanguage":"<lang>","title":"<context>","description":"<context>","segments":[{"id":1,"text":"..."}],"glossary":{"sourceTerm":"targetTerm"},"tone":"<formal|casual>"}\n\nOutput:\n{"translations":[{"id":1,"text":"...","sourceLanguage":"<detected>"}]}\n\nRules:\n1. Use title/description for context only; do not output them.\n2. Keep id, order, and count of segments.\n3. Preserve whitespace, HTML entities, and all HTML-like tags (e.g., <i1>, <a1>). Translate inner text only.\n4. Highest priority: Follow \'glossary\'. Use value for translation; if value is "", keep the key.\n5. Do not translate: content in <code>, <pre>, text enclosed in backticks, or placeholders like {1}, {{1}}, [1], [[1]].\n6. Apply the specified tone to the translation.\n7. Detect sourceLanguage for each segment.\n8. Return empty or unchanged inputs as is.\n\nExample:\nInput: {"targetLanguage":"zh-CN","segments":[{"id":1,"text":"A <b>React</b> component."}],"glossary":{"component":"组件","React":""}}\nOutput: {"translations":[{"id":1,"text":"一个<b>React</b>组件","sourceLanguage":"en"}]}\n\nFail-safe: On any error, return {"translations":[]}.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: JSON.stringify({
|
||||||
|
targetLanguage: args.to,
|
||||||
|
segments: args.texts.map((text, id) => ({ id, text })),
|
||||||
|
glossary: {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
temperature: 0,
|
||||||
|
max_tokens: 20480,
|
||||||
|
think: false,
|
||||||
|
stream: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { url, body, headers, method };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
v2.0.2 Request Hook 可以简化为:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async (args) => {
|
||||||
|
const url = args.url;
|
||||||
|
const method = "POST";
|
||||||
|
const headers = { "Content-type": "application/json" };
|
||||||
|
const body = {
|
||||||
|
model: "gemma3", // v2.0.2 版后此处可填 args.model
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: args.defaultSystemPrompt, // 或者 args.systemPrompt
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: JSON.stringify({
|
||||||
|
targetLanguage: args.to,
|
||||||
|
segments: args.texts.map((text, id) => ({ id, text })),
|
||||||
|
glossary: {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
temperature: 0,
|
||||||
|
max_tokens: 20480,
|
||||||
|
think: false,
|
||||||
|
stream: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { url, body, headers, method };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Response Hook
|
||||||
|
|
||||||
|
```js
|
||||||
|
async ({ res }) => {
|
||||||
|
const extractJson = (raw) => {
|
||||||
|
const jsonRegex = /({.*}|\[.*\])/s;
|
||||||
|
const match = raw.match(jsonRegex);
|
||||||
|
return match ? match[0] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseAIRes = (raw) => {
|
||||||
|
if (!raw) return [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonString = extractJson(raw);
|
||||||
|
if (!jsonString) return [];
|
||||||
|
|
||||||
|
const data = JSON.parse(jsonString);
|
||||||
|
if (Array.isArray(data.translations)) {
|
||||||
|
return data.translations.map((item) => [
|
||||||
|
item?.text ?? "",
|
||||||
|
item?.sourceLanguage ?? "",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("parseAIRes", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const translations = parseAIRes(res?.choices?.[0]?.message?.content);
|
||||||
|
|
||||||
|
return { translations };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
v2.0.2 版后内置`parseAIRes`函数,Response Hook 可以简化为:
|
||||||
|
|
||||||
|
```js
|
||||||
|
async ({ res, parseAIRes, }) => {
|
||||||
|
const translations = parseAIRes(res?.choices?.[0]?.message?.content);
|
||||||
|
return { translations };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 硅基流动
|
||||||
|
|
||||||
|
> 此示例为不支持聚合模型类,支持聚合的模型类参考上面 Ollama 的写法
|
||||||
|
|
||||||
|
URL
|
||||||
|
|
||||||
|
```
|
||||||
|
https://api.siliconflow.cn/v1/chat/completions
|
||||||
|
```
|
||||||
|
|
||||||
|
Request Hook
|
||||||
|
|
||||||
|
```js
|
||||||
|
async (args) => {
|
||||||
|
const url = args.url;
|
||||||
|
const method = "POST";
|
||||||
|
const headers = {
|
||||||
|
"Content-type": "application/json",
|
||||||
|
Authorization: `Bearer ${args.key}`,
|
||||||
|
};
|
||||||
|
const body = {
|
||||||
|
model: "tencent/Hunyuan-MT-7B", // v2.0.2 版后此处可填 args.model
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content:
|
||||||
|
"You are a professional, authentic machine translation engine.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: `Translate the following source text from to ${args.to}. Output translation directly without any additional text.\n\nSource Text: ${args.texts[0]}\n\nTranslated Text:`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
temperature: 0,
|
||||||
|
max_tokens: 20480,
|
||||||
|
};
|
||||||
|
|
||||||
|
return { url, body, headers, method };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Response Hook
|
||||||
|
|
||||||
|
```js
|
||||||
|
async ({ res }) => {
|
||||||
|
return { translations: [[res?.choices?.[0]?.message?.content || ""]] };
|
||||||
|
};
|
||||||
|
```
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kiss-translator",
|
"name": "kiss-translator",
|
||||||
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
"description": "A minimalist bilingual translation Extension & Greasemonkey Script",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "__MSG_app_name__",
|
"name": "__MSG_app_name__",
|
||||||
"description": "__MSG_app_description__",
|
"description": "__MSG_app_description__",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"author": "Gabe<yugang2002@gmail.com>",
|
"author": "Gabe<yugang2002@gmail.com>",
|
||||||
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
"homepage_url": "https://github.com/fishjar/kiss-translator",
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export const apiMicrosoftLangdetect = async (text) => {
|
|||||||
|
|
||||||
const key = `${URL_CACHE_DELANG}_${OPT_TRANS_MICROSOFT}`;
|
const key = `${URL_CACHE_DELANG}_${OPT_TRANS_MICROSOFT}`;
|
||||||
const queue = getBatchQueue(key, handleMicrosoftLangdetect, {
|
const queue = getBatchQueue(key, handleMicrosoftLangdetect, {
|
||||||
batchInterval: 500,
|
batchInterval: 200,
|
||||||
batchSize: 20,
|
batchSize: 20,
|
||||||
batchLength: 100000,
|
batchLength: 100000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import {
|
|||||||
INPUT_PLACE_KEY,
|
INPUT_PLACE_KEY,
|
||||||
INPUT_PLACE_MODEL,
|
INPUT_PLACE_MODEL,
|
||||||
DEFAULT_USER_AGENT,
|
DEFAULT_USER_AGENT,
|
||||||
|
defaultSystemPrompt,
|
||||||
|
defaultSubtitlePrompt,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { msAuth } from "../libs/auth";
|
import { msAuth } from "../libs/auth";
|
||||||
import { genDeeplFree } from "./deepl";
|
import { genDeeplFree } from "./deepl";
|
||||||
@@ -98,8 +100,9 @@ const parseAIRes = (raw) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const jsonString = extractJson(raw);
|
const jsonString = extractJson(raw);
|
||||||
const data = JSON.parse(jsonString);
|
if (!jsonString) return [];
|
||||||
|
|
||||||
|
const data = JSON.parse(jsonString);
|
||||||
if (Array.isArray(data.translations)) {
|
if (Array.isArray(data.translations)) {
|
||||||
// todo: 考虑序号id可能会打乱
|
// todo: 考虑序号id可能会打乱
|
||||||
return data.translations.map((item) => [
|
return data.translations.map((item) => [
|
||||||
@@ -677,13 +680,16 @@ export const genTransReq = async ({ reqHook, ...args }) => {
|
|||||||
if (reqHook?.trim() && !events) {
|
if (reqHook?.trim() && !events) {
|
||||||
try {
|
try {
|
||||||
interpreter.run(`exports.reqHook = ${reqHook}`);
|
interpreter.run(`exports.reqHook = ${reqHook}`);
|
||||||
const hookResult = await interpreter.exports.reqHook(args, {
|
const hookResult = await interpreter.exports.reqHook(
|
||||||
url,
|
{ ...args, defaultSystemPrompt, defaultSubtitlePrompt },
|
||||||
body,
|
{
|
||||||
headers,
|
url,
|
||||||
userMsg,
|
body,
|
||||||
method,
|
headers,
|
||||||
});
|
userMsg,
|
||||||
|
method,
|
||||||
|
}
|
||||||
|
);
|
||||||
if (hookResult && hookResult.url) {
|
if (hookResult && hookResult.url) {
|
||||||
return genInit(hookResult);
|
return genInit(hookResult);
|
||||||
}
|
}
|
||||||
@@ -731,6 +737,8 @@ export const parseTransRes = async (
|
|||||||
fromLang,
|
fromLang,
|
||||||
toLang,
|
toLang,
|
||||||
langMap,
|
langMap,
|
||||||
|
extractJson,
|
||||||
|
parseAIRes,
|
||||||
});
|
});
|
||||||
if (hookResult && Array.isArray(hookResult.translations)) {
|
if (hookResult && Array.isArray(hookResult.translations)) {
|
||||||
if (history && userMsg && hookResult.modelMsg) {
|
if (history && userMsg && hookResult.modelMsg) {
|
||||||
@@ -925,7 +933,7 @@ export const handleTranslate = async (
|
|||||||
userMsg,
|
userMsg,
|
||||||
...apiSetting,
|
...apiSetting,
|
||||||
});
|
});
|
||||||
if (!Array.isArray(result)) {
|
if (!result?.length) {
|
||||||
throw new Error("tranlate got an unexpected result");
|
throw new Error("tranlate got an unexpected result");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const DEFAULT_HTTP_TIMEOUT = 10000; // 调用超时时间
|
export const DEFAULT_HTTP_TIMEOUT = 10000; // 调用超时时间
|
||||||
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
|
export const DEFAULT_FETCH_LIMIT = 10; // 默认最大任务数量
|
||||||
export const DEFAULT_FETCH_INTERVAL = 100; // 默认任务间隔时间
|
export const DEFAULT_FETCH_INTERVAL = 100; // 默认任务间隔时间
|
||||||
export const DEFAULT_BATCH_INTERVAL = 1000; // 批处理请求间隔时间
|
export const DEFAULT_BATCH_INTERVAL = 400; // 批处理请求间隔时间
|
||||||
export const DEFAULT_BATCH_SIZE = 10; // 每次最多发送段落数量
|
export const DEFAULT_BATCH_SIZE = 10; // 每次最多发送段落数量
|
||||||
export const DEFAULT_BATCH_LENGTH = 10000; // 每次发送最大文字数量
|
export const DEFAULT_BATCH_LENGTH = 10000; // 每次发送最大文字数量
|
||||||
export const DEFAULT_CONTEXT_SIZE = 3; // 上下文会话数量
|
export const DEFAULT_CONTEXT_SIZE = 3; // 上下文会话数量
|
||||||
@@ -340,7 +340,7 @@ Object.entries(OPT_LANGS_TO_SPEC).forEach(([t, m]) => {
|
|||||||
OPT_LANGS_TO_CODE[t] = specToCode(m);
|
OPT_LANGS_TO_CODE[t] = specToCode(m);
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultSystemPrompt = `Act as a translation API. Output a single raw JSON object only. No extra text or fences.
|
export const defaultSystemPrompt = `Act as a translation API. Output a single raw JSON object only. No extra text or fences.
|
||||||
|
|
||||||
Input:
|
Input:
|
||||||
{"targetLanguage":"<lang>","title":"<context>","description":"<context>","segments":[{"id":1,"text":"..."}],"glossary":{"sourceTerm":"targetTerm"},"tone":"<formal|casual>"}
|
{"targetLanguage":"<lang>","title":"<context>","description":"<context>","segments":[{"id":1,"text":"..."}],"glossary":{"sourceTerm":"targetTerm"},"tone":"<formal|casual>"}
|
||||||
@@ -381,7 +381,7 @@ Fail-safe: On any error, return {"translations":[]}.`;
|
|||||||
// 4. **Special Cases**: '[Music]' (and similar cues) are standalone entries. Translate appropriately (e.g., '[音乐]', '[Musique]').
|
// 4. **Special Cases**: '[Music]' (and similar cues) are standalone entries. Translate appropriately (e.g., '[音乐]', '[Musique]').
|
||||||
// `;
|
// `;
|
||||||
|
|
||||||
const defaultSubtitlePrompt = `You are an expert AI for subtitle generation. Convert a JSON array of word-level timestamps into a bilingual VTT file.
|
export const defaultSubtitlePrompt = `You are an expert AI for subtitle generation. Convert a JSON array of word-level timestamps into a bilingual VTT file.
|
||||||
|
|
||||||
**Workflow:**
|
**Workflow:**
|
||||||
1. Merge \`text\` fields into complete sentences; ignore empty text.
|
1. Merge \`text\` fields into complete sentences; ignore empty text.
|
||||||
@@ -409,16 +409,16 @@ Good morning.
|
|||||||
\`\`\``;
|
\`\`\``;
|
||||||
|
|
||||||
const defaultRequestHook = `async (args, { url, body, headers, userMsg, method } = {}) => {
|
const defaultRequestHook = `async (args, { url, body, headers, userMsg, method } = {}) => {
|
||||||
console.log("request hook args:", args);
|
console.log("request hook args:", { args, url, body, headers, userMsg, method });
|
||||||
// return { url, body, headers, userMsg, method };
|
// return { url, body, headers, userMsg, method };
|
||||||
}`;
|
};`;
|
||||||
|
|
||||||
const defaultResponseHook = `async ({ res, ...args }) => {
|
const defaultResponseHook = `async ({ res, ...args }) => {
|
||||||
console.log("reaponse hook args:", res, args);
|
console.log("reaponse hook args:", { res, args });
|
||||||
// const translations = [["你好", "zh"]];
|
// const translations = [["你好", "zh"]];
|
||||||
// const modelMsg = "";
|
// const modelMsg = "";
|
||||||
// return { translations, modelMsg };
|
// return { translations, modelMsg };
|
||||||
}`;
|
};`;
|
||||||
|
|
||||||
// 翻译接口默认参数
|
// 翻译接口默认参数
|
||||||
const defaultApi = {
|
const defaultApi = {
|
||||||
|
|||||||
@@ -137,46 +137,42 @@ ${customApiLangs}
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const requestHookHelperZH = `1、第一个参数包含如下字段:'texts', 'from', 'to', 'url', 'key', 'model', 'systemPrompt', ...
|
const requestHookHelperZH = `1、第一个参数包含如下字段:'texts', 'from', 'to', 'url', 'key', 'model', 'systemPrompt', ...
|
||||||
2、返回值必须是包含以下字段的对象: 'url', 'body', 'headers', 'userMsg', 'method'
|
2、返回值必须是包含以下字段的对象: 'url', 'body', 'headers', 'method'
|
||||||
3、如返回空值,则hook函数不会产生任何效果。
|
3、如返回空值,则hook函数不会产生任何效果。
|
||||||
|
|
||||||
// 示例
|
// 示例
|
||||||
async (args, { url, body, headers, userMsg, method } = {}) => {
|
async (args, { url, body, headers, userMsg, method } = {}) => {
|
||||||
console.log("request hook args:", args);
|
|
||||||
return { url, body, headers, userMsg, method };
|
return { url, body, headers, userMsg, method };
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const requestHookHelperEN = `1. The first parameter contains the following fields: 'texts', 'from', 'to', 'url', 'key', 'model', 'systemPrompt', ...
|
const requestHookHelperEN = `1. The first parameter contains the following fields: 'texts', 'from', 'to', 'url', 'key', 'model', 'systemPrompt', ...
|
||||||
2. The return value must be an object containing the following fields: 'url', 'body', 'headers', 'userMsg', 'method'
|
2. The return value must be an object containing the following fields: 'url', 'body', 'headers', 'method'
|
||||||
3. If a null value is returned, the hook function will have no effect.
|
3. If a null value is returned, the hook function will have no effect.
|
||||||
|
|
||||||
// Example
|
// Example
|
||||||
async (args, { url, body, headers, userMsg, method } = {}) => {
|
async (args, { url, body, headers, userMsg, method } = {}) => {
|
||||||
console.log("request hook args:", args);
|
|
||||||
return { url, body, headers, userMsg, method };
|
return { url, body, headers, userMsg, method };
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const responsetHookHelperZH = `1、第一个参数包含如下字段:'res', ...
|
const responsetHookHelperZH = `1、第一个参数包含如下字段:'res', ...
|
||||||
2、返回值必须是包含以下字段的对象: 'translations', 'modelMsg'
|
2、返回值必须是包含以下字段的对象: 'translations'
|
||||||
('translations' 应为一个二维数组:[[译文, 源语言]])
|
('translations' 应为一个二维数组:[[译文, 源语言]])
|
||||||
3、如返回空值,则hook函数不会产生任何效果。
|
3、如返回空值,则hook函数不会产生任何效果。
|
||||||
|
|
||||||
// 示例
|
// 示例
|
||||||
async ({ res, ...args }) => {
|
async ({ res, ...args }) => {
|
||||||
console.log("reaponse hook args:", res, args);
|
|
||||||
const translations = [["你好", "zh"]];
|
const translations = [["你好", "zh"]];
|
||||||
const modelMsg = "";
|
const modelMsg = "";
|
||||||
return { translations, modelMsg };
|
return { translations, modelMsg };
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const responsetHookHelperEN = `1. The first parameter contains the following fields: 'res', ...
|
const responsetHookHelperEN = `1. The first parameter contains the following fields: 'res', ...
|
||||||
2. The return value must be an object containing the following fields: 'translations', 'modelMsg'
|
2. The return value must be an object containing the following fields: 'translations'
|
||||||
('translations' should be a two-dimensional array: [[translation, source language]]).
|
('translations' should be a two-dimensional array: [[translation, source language]]).
|
||||||
3. If a null value is returned, the hook function will have no effect.
|
3. If a null value is returned, the hook function will have no effect.
|
||||||
|
|
||||||
// Example
|
// Example
|
||||||
async ({ res, ...args }) => {
|
async ({ res, ...args }) => {
|
||||||
console.log("reaponse hook args:", res, args);
|
|
||||||
const translations = [["你好", "zh"]];
|
const translations = [["你好", "zh"]];
|
||||||
const modelMsg = "";
|
const modelMsg = "";
|
||||||
return { translations, modelMsg };
|
return { translations, modelMsg };
|
||||||
@@ -718,6 +714,11 @@ export const I18N = {
|
|||||||
en: `Selector Style`,
|
en: `Selector Style`,
|
||||||
zh_TW: `選擇器節點樣式`,
|
zh_TW: `選擇器節點樣式`,
|
||||||
},
|
},
|
||||||
|
terms_style: {
|
||||||
|
zh: `专业术语样式`,
|
||||||
|
en: `Terms Style`,
|
||||||
|
zh_TW: `專業術語樣式`,
|
||||||
|
},
|
||||||
selector_style_helper: {
|
selector_style_helper: {
|
||||||
zh: `开启翻译时注入。`,
|
zh: `开启翻译时注入。`,
|
||||||
en: `It is injected when translation is turned on.`,
|
en: `It is injected when translation is turned on.`,
|
||||||
@@ -1663,6 +1664,11 @@ export const I18N = {
|
|||||||
en: `Log Level`,
|
en: `Log Level`,
|
||||||
zh_TW: `日誌等級`,
|
zh_TW: `日誌等級`,
|
||||||
},
|
},
|
||||||
|
goto_custom_api_example: {
|
||||||
|
zh: `点击查看【自定义接口示例】`,
|
||||||
|
en: `Click to view [Custom Interface Example]`,
|
||||||
|
zh_TW: `點選查看【自訂介面範例】`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const i18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
export const i18n = (lang) => (key) => I18N[key]?.[lang] || "";
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ background: linear-gradient(
|
|||||||
|
|
||||||
export const DEFAULT_SELECTOR =
|
export const DEFAULT_SELECTOR =
|
||||||
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
|
"h1, h2, h3, h4, h5, h6, li, p, dd, blockquote, figcaption, label, legend";
|
||||||
export const DEFAULT_IGNORE_SELECTOR =
|
export const DEFAULT_IGNORE_SELECTOR = "button, footer, pre, mark, nav";
|
||||||
"aside, button, footer, form, pre, mark, nav";
|
|
||||||
export const DEFAULT_KEEP_SELECTOR = `a:has(code)`;
|
export const DEFAULT_KEEP_SELECTOR = `a:has(code)`;
|
||||||
export const DEFAULT_RULE = {
|
export const DEFAULT_RULE = {
|
||||||
pattern: "", // 匹配网址
|
pattern: "", // 匹配网址
|
||||||
@@ -94,6 +93,7 @@ export const DEFAULT_RULE = {
|
|||||||
transOpen: GLOBAL_KEY, // 开启翻译
|
transOpen: GLOBAL_KEY, // 开启翻译
|
||||||
bgColor: "", // 译文颜色
|
bgColor: "", // 译文颜色
|
||||||
textDiyStyle: "", // 自定义译文样式
|
textDiyStyle: "", // 自定义译文样式
|
||||||
|
termsStyle: "", // 专业术语样式
|
||||||
selectStyle: "", // 选择器节点样式
|
selectStyle: "", // 选择器节点样式
|
||||||
parentStyle: "", // 选择器父节点样式
|
parentStyle: "", // 选择器父节点样式
|
||||||
grandStyle: "", // 选择器父节点样式
|
grandStyle: "", // 选择器父节点样式
|
||||||
@@ -132,6 +132,7 @@ export const GLOBLA_RULE = {
|
|||||||
transOpen: "false", // 开启翻译
|
transOpen: "false", // 开启翻译
|
||||||
bgColor: "", // 译文颜色
|
bgColor: "", // 译文颜色
|
||||||
textDiyStyle: DEFAULT_DIY_STYLE, // 自定义译文样式
|
textDiyStyle: DEFAULT_DIY_STYLE, // 自定义译文样式
|
||||||
|
termsStyle: "font-weight: bold;", // 专业术语样式
|
||||||
selectStyle: DEFAULT_SELECT_STYLE, // 选择器节点样式
|
selectStyle: DEFAULT_SELECT_STYLE, // 选择器节点样式
|
||||||
parentStyle: DEFAULT_SELECT_STYLE, // 选择器父节点样式
|
parentStyle: DEFAULT_SELECT_STYLE, // 选择器父节点样式
|
||||||
grandStyle: DEFAULT_SELECT_STYLE, // 选择器祖节点样式
|
grandStyle: DEFAULT_SELECT_STYLE, // 选择器祖节点样式
|
||||||
@@ -192,6 +193,11 @@ const RULES_MAP = {
|
|||||||
rootsSelector: `ytd-page-manager`,
|
rootsSelector: `ytd-page-manager`,
|
||||||
ignoreSelector: `aside, button, footer, form, header, pre, mark, nav, #player, #container, .caption-window, .ytp-settings-menu`,
|
ignoreSelector: `aside, button, footer, form, header, pre, mark, nav, #player, #container, .caption-window, .ytp-settings-menu`,
|
||||||
},
|
},
|
||||||
|
"www.youtube.com/live_chat": {
|
||||||
|
rootsSelector: `div#items`,
|
||||||
|
selector: `span.yt-live-chat-text-message-renderer`,
|
||||||
|
autoScan: `false`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BUILTIN_RULES = Object.entries(RULES_MAP)
|
export const BUILTIN_RULES = Object.entries(RULES_MAP)
|
||||||
|
|||||||
@@ -134,9 +134,9 @@ export const DEFAULT_SUBRULES_LIST = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_MOUSEHOVER_KEY = ["KeyQ"];
|
export const DEFAULT_MOUSEHOVER_KEY = ["ControlLeft"];
|
||||||
export const DEFAULT_MOUSE_HOVER_SETTING = {
|
export const DEFAULT_MOUSE_HOVER_SETTING = {
|
||||||
useMouseHover: true, // 是否启用鼠标悬停翻译
|
useMouseHover: false, // 是否启用鼠标悬停翻译
|
||||||
mouseHoverKey: DEFAULT_MOUSEHOVER_KEY, // 鼠标悬停翻译组合键
|
mouseHoverKey: DEFAULT_MOUSEHOVER_KEY, // 鼠标悬停翻译组合键
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
import { STOKEY_WORDS, KV_WORDS_KEY } from "../config";
|
import { STOKEY_WORDS, KV_WORDS_KEY } from "../config";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useStorage } from "./Storage";
|
import { useStorage } from "./Storage";
|
||||||
|
import { debounceSyncMeta } from "../libs/storage";
|
||||||
|
|
||||||
const DEFAULT_FAVWORDS = {};
|
const DEFAULT_FAVWORDS = {};
|
||||||
|
|
||||||
export function useFavWords() {
|
export function useFavWords() {
|
||||||
const { data: favWords, save } = useStorage(
|
const { data: favWords, save: saveWords } = useStorage(
|
||||||
STOKEY_WORDS,
|
STOKEY_WORDS,
|
||||||
DEFAULT_FAVWORDS,
|
DEFAULT_FAVWORDS,
|
||||||
KV_WORDS_KEY
|
KV_WORDS_KEY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const save = useCallback(
|
||||||
|
(objOrFn) => {
|
||||||
|
saveWords(objOrFn);
|
||||||
|
debounceSyncMeta(KV_WORDS_KEY);
|
||||||
|
},
|
||||||
|
[saveWords]
|
||||||
|
);
|
||||||
|
|
||||||
const toggleFav = useCallback(
|
const toggleFav = useCallback(
|
||||||
(word) => {
|
(word) => {
|
||||||
save((prev) => {
|
save((prev) => {
|
||||||
|
|||||||
@@ -2,18 +2,27 @@ import { STOKEY_RULES, DEFAULT_RULES, KV_RULES_KEY } from "../config";
|
|||||||
import { useStorage } from "./Storage";
|
import { useStorage } from "./Storage";
|
||||||
import { checkRules } from "../libs/rules";
|
import { checkRules } from "../libs/rules";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import { debounceSyncMeta } from "../libs/storage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 规则 hook
|
* 规则 hook
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useRules() {
|
export function useRules() {
|
||||||
const { data: list = [], save } = useStorage(
|
const { data: list = [], save: saveRules } = useStorage(
|
||||||
STOKEY_RULES,
|
STOKEY_RULES,
|
||||||
DEFAULT_RULES,
|
DEFAULT_RULES,
|
||||||
KV_RULES_KEY
|
KV_RULES_KEY
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const save = useCallback(
|
||||||
|
(objOrFn) => {
|
||||||
|
saveRules(objOrFn);
|
||||||
|
debounceSyncMeta(KV_RULES_KEY);
|
||||||
|
},
|
||||||
|
[saveRules]
|
||||||
|
);
|
||||||
|
|
||||||
const add = useCallback(
|
const add = useCallback(
|
||||||
(rule) => {
|
(rule) => {
|
||||||
save((prev) => {
|
save((prev) => {
|
||||||
@@ -48,11 +57,7 @@ export function useRules() {
|
|||||||
const put = useCallback(
|
const put = useCallback(
|
||||||
(pattern, obj) => {
|
(pattern, obj) => {
|
||||||
save((prev) => {
|
save((prev) => {
|
||||||
if (
|
if (pattern !== obj.pattern) {
|
||||||
prev.some(
|
|
||||||
(item) => item.pattern === obj.pattern && item.pattern !== pattern
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
return prev.map((item) =>
|
return prev.map((item) =>
|
||||||
@@ -71,15 +76,26 @@ export function useRules() {
|
|||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = new Map();
|
// const map = new Map();
|
||||||
// 不进行深度合并
|
// // 不进行深度合并
|
||||||
// [...prev, ...adds].forEach((item) => {
|
// // [...prev, ...adds].forEach((item) => {
|
||||||
// const k = item.pattern;
|
// // const k = item.pattern;
|
||||||
// map.set(k, { ...(map.get(k) || {}), ...item });
|
// // map.set(k, { ...(map.get(k) || {}), ...item });
|
||||||
// });
|
// // });
|
||||||
prev.forEach((item) => map.set(item.pattern, item));
|
// prev.forEach((item) => map.set(item.pattern, item));
|
||||||
adds.forEach((item) => map.set(item.pattern, item));
|
// adds.forEach((item) => map.set(item.pattern, item));
|
||||||
return [...map.values()];
|
// return [...map.values()];
|
||||||
|
|
||||||
|
const addsMap = new Map(adds.map((item) => [item.pattern, item]));
|
||||||
|
const prevPatterns = new Set(prev.map((item) => item.pattern));
|
||||||
|
const updatedPrev = prev.map(
|
||||||
|
(prevItem) => addsMap.get(prevItem.pattern) || prevItem
|
||||||
|
);
|
||||||
|
const newItems = adds.filter(
|
||||||
|
(addItem) => !prevPatterns.has(addItem.pattern)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [...newItems, ...updatedPrev];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[save]
|
[save]
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const matchRule = async (href, { injectRules, subrulesList }) => {
|
|||||||
"ignoreSelector",
|
"ignoreSelector",
|
||||||
"terms",
|
"terms",
|
||||||
"aiTerms",
|
"aiTerms",
|
||||||
|
"termsStyle",
|
||||||
"selectStyle",
|
"selectStyle",
|
||||||
"parentStyle",
|
"parentStyle",
|
||||||
"grandStyle",
|
"grandStyle",
|
||||||
@@ -136,6 +137,7 @@ export const checkRules = (rules) => {
|
|||||||
ignoreSelector,
|
ignoreSelector,
|
||||||
terms,
|
terms,
|
||||||
aiTerms,
|
aiTerms,
|
||||||
|
termsStyle,
|
||||||
selectStyle,
|
selectStyle,
|
||||||
parentStyle,
|
parentStyle,
|
||||||
grandStyle,
|
grandStyle,
|
||||||
@@ -170,6 +172,7 @@ export const checkRules = (rules) => {
|
|||||||
ignoreSelector: type(ignoreSelector) === "string" ? ignoreSelector : "",
|
ignoreSelector: type(ignoreSelector) === "string" ? ignoreSelector : "",
|
||||||
terms: type(terms) === "string" ? terms : "",
|
terms: type(terms) === "string" ? terms : "",
|
||||||
aiTerms: type(aiTerms) === "string" ? aiTerms : "",
|
aiTerms: type(aiTerms) === "string" ? aiTerms : "",
|
||||||
|
termsStyle: type(termsStyle) === "string" ? termsStyle : "",
|
||||||
selectStyle: type(selectStyle) === "string" ? selectStyle : "",
|
selectStyle: type(selectStyle) === "string" ? selectStyle : "",
|
||||||
parentStyle: type(parentStyle) === "string" ? parentStyle : "",
|
parentStyle: type(parentStyle) === "string" ? parentStyle : "",
|
||||||
grandStyle: type(grandStyle) === "string" ? grandStyle : "",
|
grandStyle: type(grandStyle) === "string" ? grandStyle : "",
|
||||||
|
|||||||
@@ -959,6 +959,7 @@ export class Translator {
|
|||||||
transStartHook,
|
transStartHook,
|
||||||
transEndHook,
|
transEndHook,
|
||||||
transOnly,
|
transOnly,
|
||||||
|
termsStyle,
|
||||||
selectStyle,
|
selectStyle,
|
||||||
parentStyle,
|
parentStyle,
|
||||||
grandStyle,
|
grandStyle,
|
||||||
@@ -988,8 +989,10 @@ export class Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [processedString, placeholderMap] =
|
const [processedString, placeholderMap] = this.#serializeForTranslation(
|
||||||
this.#serializeForTranslation(nodes);
|
nodes,
|
||||||
|
termsStyle
|
||||||
|
);
|
||||||
// console.log("processedString", processedString);
|
// console.log("processedString", processedString);
|
||||||
if (this.#isInvalidText(processedString)) return;
|
if (this.#isInvalidText(processedString)) return;
|
||||||
|
|
||||||
@@ -1078,7 +1081,7 @@ export class Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理节点转为翻译字符串
|
// 处理节点转为翻译字符串
|
||||||
#serializeForTranslation(nodes) {
|
#serializeForTranslation(nodes, termsStyle) {
|
||||||
let replaceCounter = 0; // {{n}}
|
let replaceCounter = 0; // {{n}}
|
||||||
let wrapCounter = 0; // <tagn>
|
let wrapCounter = 0; // <tagn>
|
||||||
const placeholderMap = new Map();
|
const placeholderMap = new Map();
|
||||||
@@ -1118,7 +1121,7 @@ export class Translator {
|
|||||||
const termValue = this.#termValues[matchedIndex];
|
const termValue = this.#termValues[matchedIndex];
|
||||||
|
|
||||||
return pushReplace(
|
return pushReplace(
|
||||||
`<i class="${Translator.KISS_CLASS.term}">${termValue || fullMatch}</i>`
|
`<i class="${Translator.KISS_CLASS.term}" style="${termsStyle}">${termValue || fullMatch}</i>`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Alert from "@mui/material/Alert";
|
|||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
||||||
|
import Link from "@mui/material/Link";
|
||||||
import { useAlert } from "../../hooks/Alert";
|
import { useAlert } from "../../hooks/Alert";
|
||||||
import { useApiList, useApiItem } from "../../hooks/Api";
|
import { useApiList, useApiItem } from "../../hooks/Api";
|
||||||
import { useConfirm } from "../../hooks/Confirm";
|
import { useConfirm } from "../../hooks/Confirm";
|
||||||
@@ -263,7 +264,7 @@ function ApiFields({ apiSlug, isUserApi, deleteApi }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{API_SPE_TYPES.ai.has(apiType) && (
|
{(API_SPE_TYPES.ai.has(apiType) || apiType === OPT_TRANS_CUSTOMIZE) && (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
<Grid container spacing={2} columns={12}>
|
<Grid container spacing={2} columns={12}>
|
||||||
@@ -806,6 +807,12 @@ export default function Apis() {
|
|||||||
{i18n("about_api_2")}
|
{i18n("about_api_2")}
|
||||||
<br />
|
<br />
|
||||||
{i18n("about_api_3")}
|
{i18n("about_api_3")}
|
||||||
|
<Link
|
||||||
|
href="https://github.com/fishjar/kiss-translator/blob/master/custom-api_v2.md"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{i18n("goto_custom_api_example")}
|
||||||
|
</Link>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
ignoreSelector = "",
|
ignoreSelector = "",
|
||||||
terms = "",
|
terms = "",
|
||||||
aiTerms = "",
|
aiTerms = "",
|
||||||
|
termsStyle = "",
|
||||||
selectStyle = "",
|
selectStyle = "",
|
||||||
parentStyle = "",
|
parentStyle = "",
|
||||||
grandStyle = "",
|
grandStyle = "",
|
||||||
@@ -547,10 +548,19 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
maxRows={10}
|
maxRows={10}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
size="small"
|
||||||
|
label={i18n("terms_style")}
|
||||||
|
name="termsStyle"
|
||||||
|
value={termsStyle}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={10}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={i18n("selector_style")}
|
label={i18n("selector_style")}
|
||||||
helperText={i18n("selector_style_helper")}
|
|
||||||
name="selectStyle"
|
name="selectStyle"
|
||||||
value={selectStyle}
|
value={selectStyle}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -561,7 +571,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={i18n("selector_parent_style")}
|
label={i18n("selector_parent_style")}
|
||||||
helperText={i18n("selector_style_helper")}
|
|
||||||
name="parentStyle"
|
name="parentStyle"
|
||||||
value={parentStyle}
|
value={parentStyle}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -572,7 +581,6 @@ function RuleFields({ rule, rules, setShow, setKeyword }) {
|
|||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
label={i18n("selector_grand_style")}
|
label={i18n("selector_grand_style")}
|
||||||
helperText={i18n("selector_style_helper")}
|
|
||||||
name="grandStyle"
|
name="grandStyle"
|
||||||
value={grandStyle}
|
value={grandStyle}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -867,9 +875,9 @@ function UserRules({ subRules, rules }) {
|
|||||||
|
|
||||||
<UploadButton text={i18n("import")} handleImport={handleImport} />
|
<UploadButton text={i18n("import")} handleImport={handleImport} />
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
handleData={() => JSON.stringify([...rules.list].reverse(), null, 2)}
|
handleData={() => JSON.stringify([...rules.list], null, 2)}
|
||||||
text={i18n("export")}
|
text={i18n("export")}
|
||||||
fileName={`kiss-rules_${Date.now()}.json`}
|
fileName={`kiss-rules_v2_${Date.now()}.json`}
|
||||||
/>
|
/>
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
handleData={async () => JSON.stringify(await getRulesOld(), null, 2)}
|
handleData={async () => JSON.stringify(await getRulesOld(), null, 2)}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default function Settings() {
|
|||||||
<DownloadButton
|
<DownloadButton
|
||||||
handleData={() => JSON.stringify(setting, null, 2)}
|
handleData={() => JSON.stringify(setting, null, 2)}
|
||||||
text={i18n("export")}
|
text={i18n("export")}
|
||||||
fileName={`kiss-setting_${Date.now()}.json`}
|
fileName={`kiss-setting_v2_${Date.now()}.json`}
|
||||||
/>
|
/>
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
handleData={async () =>
|
handleData={async () =>
|
||||||
|
|||||||
Reference in New Issue
Block a user