diff --git a/src/apis/index.js b/src/apis/index.js
index da3857d..eef30e5 100644
--- a/src/apis/index.js
+++ b/src/apis/index.js
@@ -6,7 +6,6 @@ import {
OPT_TRANS_DEEPL,
OPT_TRANS_OPENAI,
OPT_TRANS_CUSTOMIZE,
- URL_MICROSOFT_TRANS,
OPT_LANGS_SPECIAL,
PROMPT_PLACE_FROM,
PROMPT_PLACE_TO,
@@ -49,8 +48,13 @@ export const apiFetchRules = (url, isBg = false) =>
* @param {*} from
* @returns
*/
-const apiGoogleTranslate = async (translator, text, to, from, setting) => {
- const { url, key } = setting;
+const apiGoogleTranslate = async (
+ translator,
+ text,
+ to,
+ from,
+ { url, key, useCache = true }
+) => {
const params = {
client: "gtx",
dt: "t",
@@ -61,15 +65,19 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
q: text,
};
const input = `${url}?${queryString.stringify(params)}`;
- return fetchPolyfill(input, {
+ const res = await fetchPolyfill(input, {
headers: {
"Content-type": "application/json",
},
- useCache: true,
+ useCache,
usePool: true,
translator,
token: key,
});
+ const trText = res.sentences.map((item) => item.trans).join(" ");
+ const isSame = to === res.src;
+
+ return [trText, isSame];
};
/**
@@ -79,23 +87,33 @@ const apiGoogleTranslate = async (translator, text, to, from, setting) => {
* @param {*} from
* @returns
*/
-const apiMicrosoftTranslate = (translator, text, to, from) => {
+const apiMicrosoftTranslate = async (
+ translator,
+ text,
+ to,
+ from,
+ { url, useCache = true }
+) => {
const params = {
from,
to,
"api-version": "3.0",
};
- const input = `${URL_MICROSOFT_TRANS}?${queryString.stringify(params)}`;
- return fetchPolyfill(input, {
+ const input = `${url}?${queryString.stringify(params)}`;
+ const res = await fetchPolyfill(input, {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify([{ Text: text }]),
- useCache: true,
+ useCache,
usePool: true,
translator,
});
+ const trText = res[0].translations[0].text;
+ const isSame = to === res[0].detectedLanguage?.language;
+
+ return [trText, isSame];
};
/**
@@ -105,8 +123,13 @@ const apiMicrosoftTranslate = (translator, text, to, from) => {
* @param {*} from
* @returns
*/
-const apiDeepLTranslate = (translator, text, to, from, setting) => {
- const { url, key } = setting;
+const apiDeepLTranslate = async (
+ translator,
+ text,
+ to,
+ from,
+ { url, key, useCache = true }
+) => {
const data = {
text: [text],
target_lang: to,
@@ -115,17 +138,21 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
if (from) {
data.source_lang = from;
}
- return fetchPolyfill(url, {
+ const res = await fetchPolyfill(url, {
headers: {
"Content-type": "application/json",
},
method: "POST",
body: JSON.stringify(data),
- useCache: true,
+ useCache,
usePool: true,
translator,
token: key,
});
+ const trText = res.translations.map((item) => item.text).join(" ");
+ const isSame = to === res.translations[0].detected_source_language;
+
+ return [trText, isSame];
};
/**
@@ -135,12 +162,17 @@ const apiDeepLTranslate = (translator, text, to, from, setting) => {
* @param {*} from
* @returns
*/
-const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
- let { url, key, model, prompt } = setting;
+const apiOpenaiTranslate = async (
+ translator,
+ text,
+ to,
+ from,
+ { url, key, model, prompt, useCache = true }
+) => {
prompt = prompt
.replaceAll(PROMPT_PLACE_FROM, from)
.replaceAll(PROMPT_PLACE_TO, to);
- return fetchPolyfill(url, {
+ const res = await fetchPolyfill(url, {
headers: {
"Content-type": "application/json",
},
@@ -160,11 +192,17 @@ const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
temperature: 0,
max_tokens: 256,
}),
- useCache: true,
+ useCache,
usePool: true,
translator,
token: key,
});
+ const trText = res?.choices?.[0].message.content;
+ const sLang = await tryDetectLang(text);
+ const tLang = await tryDetectLang(trText);
+ const isSame = text === trText || (sLang && tLang && sLang === tLang);
+
+ return [trText, isSame];
};
/**
@@ -174,12 +212,16 @@ const apiOpenaiTranslate = async (translator, text, to, from, setting) => {
* @param {*} from
* @returns
*/
-const apiCustomizeTranslate = async (translator, text, to, from, setting) => {
- let { url, key, headers } = setting;
- return fetchPolyfill(url, {
+const apiCustomTranslate = async (
+ translator,
+ text,
+ to,
+ from,
+ { url, key, useCache = true }
+) => {
+ const res = await fetchPolyfill(url, {
headers: {
"Content-type": "application/json",
- ...JSON.parse(headers),
},
method: "POST",
body: JSON.stringify({
@@ -187,11 +229,15 @@ const apiCustomizeTranslate = async (translator, text, to, from, setting) => {
from,
to,
}),
- useCache: true,
+ useCache,
usePool: true,
translator,
token: key,
});
+ const trText = res.text;
+ const isSame = to === res.from;
+
+ return [trText, isSame];
};
/**
@@ -199,40 +245,29 @@ const apiCustomizeTranslate = async (translator, text, to, from, setting) => {
* @param {*} param0
* @returns
*/
-export const apiTranslate = async ({
+export const apiTranslate = ({
translator,
- q,
+ text,
fromLang,
toLang,
- setting,
+ apiSetting,
}) => {
- let trText = "";
- let isSame = false;
+ const from = OPT_LANGS_SPECIAL[translator]?.get(fromLang) ?? fromLang;
+ const to = OPT_LANGS_SPECIAL[translator]?.get(toLang) ?? toLang;
+ const callApi = (api) => api(translator, text, to, from, apiSetting);
- let from = OPT_LANGS_SPECIAL?.[translator]?.get(fromLang) ?? fromLang;
- let to = OPT_LANGS_SPECIAL?.[translator]?.get(toLang) ?? toLang;
-
- if (translator === OPT_TRANS_GOOGLE) {
- const res = await apiGoogleTranslate(translator, q, to, from, setting);
- trText = res.sentences.map((item) => item.trans).join(" ");
- isSame = to === res.src;
- } else if (translator === OPT_TRANS_MICROSOFT) {
- const res = await apiMicrosoftTranslate(translator, q, to, from);
- trText = res[0].translations[0].text;
- isSame = to === res[0].detectedLanguage?.language;
- } else if (translator === OPT_TRANS_DEEPL) {
- const res = await apiDeepLTranslate(translator, q, to, from, setting);
- trText = res.translations.map((item) => item.text).join(" ");
- isSame = to === (from || res.translations[0].detected_source_language);
- } else if (translator === OPT_TRANS_OPENAI) {
- const res = await apiOpenaiTranslate(translator, q, to, from, setting);
- trText = res?.choices?.[0].message.content;
- const sLang = await tryDetectLang(q);
- const tLang = await tryDetectLang(trText);
- isSame = q === trText || (sLang && tLang && sLang === tLang);
- } else if (translator === OPT_TRANS_CUSTOMIZE) {
- // todo
+ switch (translator) {
+ case OPT_TRANS_GOOGLE:
+ return callApi(apiGoogleTranslate);
+ case OPT_TRANS_MICROSOFT:
+ return callApi(apiMicrosoftTranslate);
+ case OPT_TRANS_DEEPL:
+ return callApi(apiDeepLTranslate);
+ case OPT_TRANS_OPENAI:
+ return callApi(apiOpenaiTranslate);
+ case OPT_TRANS_CUSTOMIZE:
+ return callApi(apiCustomTranslate);
+ default:
+ return ["", false];
}
-
- return [trText, isSame];
};
diff --git a/src/config/i18n.js b/src/config/i18n.js
index 79dbabd..6b01457 100644
--- a/src/config/i18n.js
+++ b/src/config/i18n.js
@@ -3,6 +3,99 @@ export const UI_LANGS = [
["zh", "中文"],
];
+const customApiLangs = `["en", "English - English"],
+["zh-CN", "Simplified Chinese - 简体中文"],
+["zh-TW", "Traditional Chinese - 繁體中文"],
+["ar", "Arabic - العربية"],
+["bg", "Bulgarian - Български"],
+["ca", "Catalan - Català"],
+["hr", "Croatian - Hrvatski"],
+["cs", "Czech - Čeština"],
+["da", "Danish - Dansk"],
+["nl", "Dutch - Nederlands"],
+["fi", "Finnish - Suomi"],
+["fr", "French - Français"],
+["de", "German - Deutsch"],
+["el", "Greek - Ελληνικά"],
+["hi", "Hindi - हिन्दी"],
+["hu", "Hungarian - Magyar"],
+["id", "Indonesian - Indonesia"],
+["it", "Italian - Italiano"],
+["ja", "Japanese - 日本語"],
+["ko", "Korean - 한국어"],
+["ms", "Malay - Melayu"],
+["mt", "Maltese - Malti"],
+["nb", "Norwegian - Norsk Bokmål"],
+["pl", "Polish - Polski"],
+["pt", "Portuguese - Português"],
+["ro", "Romanian - Română"],
+["ru", "Russian - Русский"],
+["sk", "Slovak - Slovenčina"],
+["sl", "Slovenian - Slovenščina"],
+["es", "Spanish - Español"],
+["sv", "Swedish - Svenska"],
+["ta", "Tamil - தமிழ்"],
+["te", "Telugu - తెలుగు"],
+["th", "Thai - ไทย"],
+["tr", "Turkish - Türkçe"],
+["uk", "Ukrainian - Українська"],
+["vi", "Vietnamese - Tiếng Việt"],
+`;
+
+const customApiHelpZH = `/// 自定义翻译源接口说明
+// 请求(Request)数据将按下面规范发送
+{
+ url: {{YOUR_URL}},
+ method: "POST",
+ headers: {
+ "Content-type": "application/json",
+ "Authorization"] = "Bearer {{YOUR_KEY}}"
+ },
+ body: {
+ text, // 需要翻译的文字
+ from, // 源语言,可能为空,表示需要接口自动识别语言
+ to, // 目标语言
+ }
+}
+
+// 返回(Response)数据需符合下面的JSON规范
+{
+ text, // 翻译后的文字
+ from, // 识别的源语言
+ to, // 目标语言(可选)
+}
+
+// 支持的语言代码如下
+${customApiLangs}
+`;
+
+const customApiHelpEN = `/// Custom translation source interface description
+// Request data will be sent according to the following specifications
+{
+ url: {{YOUR_URL}},
+ method: "POST",
+ headers: {
+ "Content-type": "application/json",
+ "Authorization"] = "Bearer {{YOUR_KEY}}"
+ },
+ body: {
+ text, // text to be translated
+ from, // Source language, may be empty
+ to, // Target language
+ }
+}
+
+// The returned data must conform to the following JSON specification
+{
+ text, // translated text
+ from, // Recognized source language
+ to, // Target language (optional)
+}
+
+// The supported language codes are as follows
+${customApiLangs}
+`;
+
export const I18N = {
app_name: {
zh: `简约翻译`,
@@ -12,6 +105,10 @@ export const I18N = {
zh: `翻译`,
en: `Translate`,
},
+ custom_api_help: {
+ zh: customApiHelpZH,
+ en: customApiHelpEN,
+ },
translate_alt: {
zh: `翻译 (Alt+Q)`,
en: `Translate (Alt+Q)`,
diff --git a/src/config/index.js b/src/config/index.js
index 606b3b0..a105bfa 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -64,14 +64,12 @@ export const URL_KISS_RULES_NEW_ISSUE =
export const URL_RAW_PREFIX =
"https://raw.githubusercontent.com/fishjar/kiss-translator/master";
export const URL_MICROSOFT_AUTH = "https://edge.microsoft.com/translate/auth";
-export const URL_MICROSOFT_TRANS =
- "https://api-edge.cognitive.microsofttranslator.com/translate";
export const OPT_TRANS_GOOGLE = "Google";
export const OPT_TRANS_MICROSOFT = "Microsoft";
export const OPT_TRANS_DEEPL = "DeepL";
export const OPT_TRANS_OPENAI = "OpenAI";
-export const OPT_TRANS_CUSTOMIZE = "Customize";
+export const OPT_TRANS_CUSTOMIZE = "Custom";
export const OPT_TRANS_ALL = [
OPT_TRANS_GOOGLE,
OPT_TRANS_MICROSOFT,
@@ -135,6 +133,7 @@ export const OPT_LANGS_SPECIAL = {
[OPT_TRANS_OPENAI]: new Map(
OPT_LANGS_FROM.map(([key, val]) => [key, val.split(" - ")[0]])
),
+ [OPT_TRANS_CUSTOMIZE]: new Map([["auto", ""]]),
};
export const OPT_STYLE_NONE = "style_none"; // 无
@@ -204,6 +203,7 @@ export const DEFAULT_SUBRULES_LIST = [
export const DEFAULT_TRANS_APIS = {
[OPT_TRANS_GOOGLE]: {
url: "https://translate.googleapis.com/translate_a/single",
+ key: "",
},
[OPT_TRANS_MICROSOFT]: {
url: "https://api-edge.cognitive.microsofttranslator.com/translate",
@@ -222,7 +222,6 @@ export const DEFAULT_TRANS_APIS = {
[OPT_TRANS_CUSTOMIZE]: {
url: "",
key: "",
- headers: "",
},
};
@@ -243,13 +242,6 @@ export const DEFAULT_SETTING = {
subrulesList: DEFAULT_SUBRULES_LIST, // 订阅列表
owSubrule: DEFAULT_OW_RULE, // 覆写订阅规则
transApis: DEFAULT_TRANS_APIS, // 翻译接口
- googleUrl: "https://translate.googleapis.com/translate_a/single", // 谷歌翻译接口
- deeplUrl: "https://api-free.deepl.com/v2/translate",
- deeplKey: "",
- openaiUrl: "https://api.openai.com/v1/chat/completions",
- openaiKey: "",
- openaiModel: "gpt-4",
- openaiPrompt: `You will be provided with a sentence in ${PROMPT_PLACE_FROM}, and your task is to translate it into ${PROMPT_PLACE_TO}.`,
};
export const DEFAULT_RULES = [GLOBLA_RULE];
diff --git a/src/hooks/Api.js b/src/hooks/Api.js
index ec659bb..37ac6f8 100644
--- a/src/hooks/Api.js
+++ b/src/hooks/Api.js
@@ -5,8 +5,6 @@ import { useSetting } from "./Setting";
export function useApi(translator) {
const { setting, updateSetting } = useSetting();
const apis = setting?.transApis || DEFAULT_TRANS_APIS;
- const api = apis[translator] || {};
- console.log("apis", translator, apis);
const updateApi = useCallback(
async (obj) => {
@@ -22,5 +20,5 @@ export function useApi(translator) {
await updateSetting({ transApis });
}, [translator, apis, updateSetting]);
- return { api, updateApi, resetApi };
+ return { api: apis[translator] || {}, updateApi, resetApi };
}
diff --git a/src/hooks/Setting.js b/src/hooks/Setting.js
index 1272a17..a919244 100644
--- a/src/hooks/Setting.js
+++ b/src/hooks/Setting.js
@@ -6,7 +6,7 @@ import { createContext, useCallback, useContext, useMemo } from "react";
import { debounce } from "../libs/utils";
const SettingContext = createContext({
- setting: null,
+ setting: {},
updateSetting: async () => {},
reloadSetting: async () => {},
});
diff --git a/src/hooks/Translate.js b/src/hooks/Translate.js
index f9c3af3..2065217 100644
--- a/src/hooks/Translate.js
+++ b/src/hooks/Translate.js
@@ -2,6 +2,7 @@ import { useEffect } from "react";
import { useState } from "react";
import { tryDetectLang } from "../libs";
import { apiTranslate } from "../apis";
+import { DEFAULT_TRANS_APIS } from "../config";
/**
* 翻译hook
@@ -28,10 +29,10 @@ export function useTranslate(q, rule, setting) {
} else {
const [trText, isSame] = await apiTranslate({
translator,
- q,
+ text: q,
fromLang,
toLang,
- setting: setting[translator],
+ apiSetting: (setting.transApis || DEFAULT_TRANS_APIS)[translator],
});
setText(trText);
setSamelang(isSame);
diff --git a/src/libs/fetch.js b/src/libs/fetch.js
index 3360616..ccddd96 100644
--- a/src/libs/fetch.js
+++ b/src/libs/fetch.js
@@ -177,6 +177,10 @@ export const fetchData = async (
* @returns
*/
export const fetchPolyfill = async (input, { isBg = false, ...opts } = {}) => {
+ if (!input.trim()) {
+ throw new Error("URL is empty");
+ }
+
// 插件
if (isExt && !isBg) {
const res = await sendBgMsg(MSG_FETCH, { input, opts });
diff --git a/src/libs/translator.js b/src/libs/translator.js
index a1f00eb..c251070 100644
--- a/src/libs/translator.js
+++ b/src/libs/translator.js
@@ -211,6 +211,10 @@ export class Translator {
};
_register = () => {
+ if (this._rule.fromLang === this._rule.toLang) {
+ return;
+ }
+
// 搜索节点
this._queryNodes();
diff --git a/src/views/Options/Apis.js b/src/views/Options/Apis.js
index 1917fdd..829a97d 100644
--- a/src/views/Options/Apis.js
+++ b/src/views/Options/Apis.js
@@ -7,6 +7,7 @@ import {
OPT_TRANS_MICROSOFT,
OPT_TRANS_OPENAI,
OPT_TRANS_CUSTOMIZE,
+ URL_KISS_PROXY,
} from "../../config";
import { useState } from "react";
import { useI18n } from "../../hooks/I18n";
@@ -15,9 +16,12 @@ 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 Alert from "@mui/material/Alert";
import { useAlert } from "../../hooks/Alert";
import { useApi } from "../../hooks/Api";
import { apiTranslate } from "../../apis";
+import Box from "@mui/material/Box";
+import Link from "@mui/material/Link";
function TestButton({ translator, api }) {
const i18n = useI18n();
@@ -28,10 +32,10 @@ function TestButton({ translator, api }) {
setLoading(true);
const [text] = await apiTranslate({
translator,
- q: "hello world",
- fromLang: "auto",
+ text: "hello world",
+ fromLang: "en",
toLang: "zh-CN",
- setting: api,
+ apiSetting: { ...api, useCache: false },
});
if (!text) {
throw new Error("empty reault");
@@ -45,7 +49,7 @@ function TestButton({ translator, api }) {
};
if (loading) {
- return
{i18n("custom_api_help")}
+ )}
);
}
@@ -155,7 +153,22 @@ function ApiAccordion({ translator }) {
}
export default function Apis() {
- return OPT_TRANS_ALL.map((translator) => (
-