mirror of
https://github.com/go-gost/gost-ui.git
synced 2024-08-11 17:53:29 +00:00
Add i18n
This commit is contained in:
+2
-1
@@ -23,4 +23,5 @@ dist-ssr
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
public/monaco-editor
|
||||
public/monaco-editor
|
||||
src/i18n/*.json
|
||||
Generated
+77
@@ -13,6 +13,8 @@
|
||||
"axios": "^1.6.0",
|
||||
"classnames": "^2.5.1",
|
||||
"events": "^3.3.0",
|
||||
"i18next": "^23.11.5",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"idb": "^7.1.1",
|
||||
"idb-open-plus": "^1.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
@@ -21,6 +23,7 @@
|
||||
"qs": "^6.11.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.1.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -33,6 +36,7 @@
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"cpx2": "^7.0.1",
|
||||
"csv-parse": "^5.5.6",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
@@ -1947,6 +1951,12 @@
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/csv-parse": {
|
||||
"version": "5.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/csv-parse/-/csv-parse-5.5.6.tgz",
|
||||
"integrity": "sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.10",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.10.tgz",
|
||||
@@ -2812,6 +2822,44 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "23.11.5",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-23.11.5.tgz",
|
||||
"integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-browser-languagedetector": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
|
||||
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/idb": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz",
|
||||
@@ -6664,6 +6712,27 @@
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/react-i18next/-/react-i18next-14.1.2.tgz",
|
||||
"integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-18.2.0.tgz",
|
||||
@@ -7353,6 +7422,14 @@
|
||||
"source-map": "^0.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
|
||||
|
||||
+9
-1
@@ -9,8 +9,12 @@
|
||||
"analyzer": "tsc && vite build --mode=analyzer",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview",
|
||||
"init": "npm run init:monaco && npm run init:i18n",
|
||||
"init:i18n": "node ./script/language.js",
|
||||
"init:monaco": "cpx ./node_modules/monaco-editor/min/** ./public/monaco-editor/min",
|
||||
"predev": "npm run init:monaco"
|
||||
"predev": "npm run init",
|
||||
"prebuild": "npm run init:i18n",
|
||||
"preanalyzer": "npm run init:i18n"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
@@ -18,6 +22,8 @@
|
||||
"axios": "^1.6.0",
|
||||
"classnames": "^2.5.1",
|
||||
"events": "^3.3.0",
|
||||
"i18next": "^23.11.5",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"idb": "^7.1.1",
|
||||
"idb-open-plus": "^1.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
@@ -26,6 +32,7 @@
|
||||
"qs": "^6.11.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.1.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -38,6 +45,7 @@
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"cpx2": "^7.0.1",
|
||||
"csv-parse": "^5.5.6",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import fs from "fs";
|
||||
import { writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
import url from "url";
|
||||
import { parse } from "csv-parse";
|
||||
|
||||
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
||||
const importFile = path.join(__dirname, "../src/i18n/languages.csv");
|
||||
const exportDir = path.join(__dirname, "../src/i18n");
|
||||
|
||||
let languages;
|
||||
const langMap = new Map();
|
||||
const init = (arr) => {
|
||||
const [_key, ...langs] = arr;
|
||||
languages = langs.map((langName) => {
|
||||
const lang = {};
|
||||
langMap.set(langName, lang);
|
||||
return lang;
|
||||
});
|
||||
};
|
||||
const output = () => {
|
||||
// 保存为json文件
|
||||
const langList = [];
|
||||
const arr = [];
|
||||
langMap.forEach((json, lang) => {
|
||||
langList.push({ lang, label: json.language });
|
||||
arr.push(
|
||||
writeFile(
|
||||
path.join(exportDir, `${lang}.json`),
|
||||
JSON.stringify(json, undefined, 4)
|
||||
)
|
||||
);
|
||||
});
|
||||
arr.push(
|
||||
writeFile(
|
||||
path.join(exportDir, `languages.json`),
|
||||
JSON.stringify(langList, undefined, 4)
|
||||
)
|
||||
);
|
||||
Promise.all(arr).then(() => console.log("done"));
|
||||
};
|
||||
|
||||
const setValue = (obj, key, value) => {
|
||||
if (!obj || !key) return;
|
||||
const keys = key.split(".");
|
||||
let _obj = obj;
|
||||
while (keys.length > 1) {
|
||||
const _key = keys.shift();
|
||||
if (_obj[_key] === undefined) {
|
||||
_obj = _obj[_key] = {};
|
||||
} else if (typeof _obj[_key] === "object") {
|
||||
_obj = _obj[_key];
|
||||
} else {
|
||||
console.error(`Key [${key}] error!`);
|
||||
}
|
||||
}
|
||||
const _key = keys[0];
|
||||
if (_obj[_key] !== undefined) {
|
||||
console.error(`Key [${key}] repeat!`);
|
||||
}
|
||||
_obj[_key] = value || "";
|
||||
};
|
||||
|
||||
function main(){
|
||||
const csvp = parse();
|
||||
csvp.on("end", output);
|
||||
csvp.on("data", function (chunk) {
|
||||
// 解析language.csv
|
||||
if (!languages) return init(chunk); // 第一行为表头
|
||||
const [key, ...values] = chunk;
|
||||
values.forEach((value, index) => {
|
||||
setValue(languages[index], key, value);
|
||||
});
|
||||
});
|
||||
fs.createReadStream(importFile).pipe(csvp);
|
||||
}
|
||||
|
||||
main();
|
||||
+13
-1
@@ -20,9 +20,12 @@ import "./App.css";
|
||||
import { configEvent } from "./uitls/events";
|
||||
|
||||
import zhCN from "antd/locale/zh_CN";
|
||||
import enGB from "antd/locale/en_GB";
|
||||
import { ServerComm } from "./api/local";
|
||||
import Ctx from "./uitls/ctx";
|
||||
import { useIsDark } from "./uitls/useTheme";
|
||||
import './uitls/i18n.ts';
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Manage = React.lazy(() => import("./Pages/Manage"));
|
||||
|
||||
@@ -45,6 +48,7 @@ const AntdGlobal: React.FC<{ children: React.ReactNode }> = (props) => {
|
||||
|
||||
function App() {
|
||||
const info = useInfo();
|
||||
const { t, i18n } = useTranslation();
|
||||
// const [gostConfig, setGostConfig] = useState<any>(null);
|
||||
// const [localConfig, setLocalConfig] = useState<any>(null);
|
||||
const gostConfig = useServerConfig();
|
||||
@@ -53,10 +57,12 @@ function App() {
|
||||
// const [userTheme, setUserTheme] = useState<any>(null); // 用户主题
|
||||
const [serverLoading, setServerLoading] = useState<any>(false);
|
||||
const [localLoading, setLocalLoading] = useState<any>(false);
|
||||
const [locale, setLocale] = useState<any>(zhCN);
|
||||
const isLoading = useMemo(
|
||||
() => serverLoading || localLoading,
|
||||
[serverLoading, localLoading]
|
||||
);
|
||||
|
||||
const slef = useRef({
|
||||
update: async () => {
|
||||
try {
|
||||
@@ -137,6 +143,12 @@ function App() {
|
||||
(window as any)?.monaco?.editor.setTheme("vs");
|
||||
}
|
||||
}, [isDark]);
|
||||
|
||||
useEffect(() => {
|
||||
const lang = i18n.resolvedLanguage;
|
||||
setLocale(lang === 'zh' ? zhCN : enGB);
|
||||
},[i18n.resolvedLanguage])
|
||||
|
||||
return (
|
||||
<Ctx.Provider
|
||||
value={{
|
||||
@@ -147,7 +159,7 @@ function App() {
|
||||
>
|
||||
<ConfigProvider
|
||||
theme={{ algorithm: isDark ? theme.darkAlgorithm : undefined }}
|
||||
locale={zhCN}
|
||||
locale={locale}
|
||||
>
|
||||
<AntdGlobal>
|
||||
<React.Suspense fallback="loading...">
|
||||
|
||||
+17
-15
@@ -1,12 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import React from "react";
|
||||
import { GlobalOutlined, LockOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { Button, Checkbox, Form, Input } from "antd";
|
||||
import { Button, Checkbox, Form, Input, Space } from "antd";
|
||||
import { login } from "../uitls/server";
|
||||
import LocalServers from "../components/LocalServers";
|
||||
import { ThemeButton } from "../components/Theme";
|
||||
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { LanguageButton } from "../components/Language";
|
||||
const Home: React.FC = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
@@ -36,30 +38,27 @@ const Home: React.FC = () => {
|
||||
);
|
||||
}}
|
||||
>
|
||||
<h1>GOST API Manage</h1>
|
||||
<h2>首先连接API服务</h2>
|
||||
<h1>{t('base.title')}</h1>
|
||||
<h2>{t('base.description')}</h2>
|
||||
<Form.Item
|
||||
name="baseURL"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "请输入API地址",
|
||||
message: t('msg.baseURL.required'),
|
||||
},
|
||||
{
|
||||
validator(rule, value, callback) {
|
||||
if (value === "http://") {
|
||||
callback("请输入API地址");
|
||||
callback(t('msg.baseURL.required'));
|
||||
}
|
||||
callback();
|
||||
},
|
||||
},
|
||||
// {
|
||||
// type:'url'
|
||||
// }
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="API baseURL"
|
||||
placeholder={t('placeholder.baseURL')}
|
||||
prefix={<GlobalOutlined className={"prefixIcon"} />}
|
||||
></Input>
|
||||
</Form.Item>
|
||||
@@ -76,18 +75,21 @@ const Home: React.FC = () => {
|
||||
></Input.Password>
|
||||
</Form.Item>
|
||||
<Form.Item name="save" valuePropName="checked">
|
||||
<Checkbox>保存到本地</Checkbox>
|
||||
<Checkbox>{t('base.form.local')}</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item noStyle style={{ marginBottom: "1em" }}>
|
||||
<Button block type="primary" htmlType="submit">
|
||||
链接
|
||||
{t('base.cmd.connect')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<LocalServers></LocalServers>
|
||||
</Form>
|
||||
<ThemeButton
|
||||
style={{ position: "absolute", top: "1em", right: "1em" }}
|
||||
></ThemeButton>
|
||||
<div style={{ position: "absolute", top: "1em", right: "1em" }}>
|
||||
<Space>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
</Space>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+30
-17
@@ -36,6 +36,8 @@ import {
|
||||
SettingOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { ThemeButton } from "../components/Theme";
|
||||
import { LanguageButton } from "../components/Language";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const colSpan = {
|
||||
xs: 24,
|
||||
@@ -52,6 +54,7 @@ const colSpan1 = {
|
||||
|
||||
const Manage = () => {
|
||||
const info = useInfo()!;
|
||||
const { t } = useTranslation();
|
||||
const gostConfig = useServerConfig();
|
||||
const [show, setShow] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -128,7 +131,7 @@ const Manage = () => {
|
||||
if (locals.length) {
|
||||
items.push({
|
||||
key: "2",
|
||||
label: " 切换 ",
|
||||
label: t("manage.cmd.switch"),
|
||||
children: locals,
|
||||
});
|
||||
items.push({
|
||||
@@ -137,13 +140,13 @@ const Manage = () => {
|
||||
}
|
||||
items.push({
|
||||
key: "new",
|
||||
label: "打开新链接",
|
||||
label: t("manage.cmd.new"),
|
||||
onClick: () => {
|
||||
window.open(location.href, undefined, "noopener");
|
||||
},
|
||||
});
|
||||
return items;
|
||||
}, [locals]);
|
||||
}, [locals, t]);
|
||||
|
||||
return (
|
||||
<Layout style={{ height: "100vh", overflow: "hidden" }}>
|
||||
@@ -162,7 +165,7 @@ const Manage = () => {
|
||||
useServerConfig.set((await API.getConfig()) as any);
|
||||
}}
|
||||
>
|
||||
刷新配置
|
||||
{t("manage.cmd.reload")}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col>{info.addr}</Col>
|
||||
@@ -174,7 +177,7 @@ const Manage = () => {
|
||||
loading={loading}
|
||||
onClick={() => ref.current?.onSave?.()}
|
||||
>
|
||||
保存到服务器
|
||||
{t("manage.cmd.save")}
|
||||
</Button>
|
||||
<Button
|
||||
icon={<SettingOutlined />}
|
||||
@@ -188,15 +191,13 @@ const Manage = () => {
|
||||
download(jsonFormat(gostConfig!), "gost.json");
|
||||
}}
|
||||
>
|
||||
下载当前配置
|
||||
{t("manage.cmd.download")}
|
||||
</Button>
|
||||
<Dropdown.Button menu={{ items }} onClick={logout}>
|
||||
退出
|
||||
{/* <Button type="link" onClick={logout}>
|
||||
切换连接
|
||||
</Button> */}
|
||||
{t("manage.cmd.logout")}
|
||||
</Dropdown.Button>
|
||||
<ThemeButton size="small"></ThemeButton>
|
||||
<ThemeButton />
|
||||
<LanguageButton />
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -218,17 +219,29 @@ const Manage = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form.Item name="autoSave" label="自动保存" valuePropName="checked">
|
||||
<Form.Item
|
||||
name="autoSave"
|
||||
label={t("manage.label.autoSave")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="saveFormat" label="格式" initialValue={"json"}>
|
||||
<Form.Item
|
||||
name="saveFormat"
|
||||
label={t("manage.label.format")}
|
||||
initialValue={"json"}
|
||||
>
|
||||
<Radio.Group optionType="button" buttonStyle="solid">
|
||||
<Radio value="json">json</Radio>
|
||||
<Radio value="yaml">yaml</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item name="savePath" label="保存路径" trigger="onChange">
|
||||
<Input placeholder="指定保存,默认保存到上下文目录" />
|
||||
<Form.Item
|
||||
name="savePath"
|
||||
label={t("manage.label.path")}
|
||||
trigger="onChange"
|
||||
>
|
||||
<Input placeholder={t("placeholder.savePath")} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
@@ -276,7 +289,7 @@ const Manage = () => {
|
||||
<ListCard module="observer" />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ProCard boxShadow title="限速限流">
|
||||
<ProCard boxShadow title={t("manage.label.limit")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...colSpan1}>
|
||||
<ListCard module="limiter" bordered boxShadow={false} />
|
||||
@@ -293,7 +306,7 @@ const Manage = () => {
|
||||
<Col span={24}>
|
||||
<ProCard
|
||||
boxShadow
|
||||
title="All Config JSON"
|
||||
title={t("manage.label.allConfig")}
|
||||
styles={{ body: { padding: 20 } }}
|
||||
>
|
||||
<CodeEditor
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CardCtx } from "../../uitls/ctx";
|
||||
import { jsonParse } from "../../uitls";
|
||||
import { GostCommit } from "../../api/local";
|
||||
import { UseTemplates } from "../ListCard/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
keyName: string;
|
||||
@@ -14,20 +15,21 @@ type Props = {
|
||||
};
|
||||
|
||||
const AddButton: React.FC<Props> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { keyName, title } = props;
|
||||
const { comm } = useContext(CardCtx);
|
||||
const templates = UseTemplates({ name: keyName! });
|
||||
|
||||
|
||||
return (
|
||||
<JsonForm
|
||||
title={`添加 ${title || ""}`}
|
||||
title={t("title.add", { name: title })}
|
||||
templates={templates}
|
||||
trigger={<Button icon={<PlusOutlined />} size="small" />}
|
||||
onFinish={async (values: any) => {
|
||||
const { value } = values;
|
||||
const json = jsonParse(value);
|
||||
await comm!.addValue(json);
|
||||
return true
|
||||
return true;
|
||||
}}
|
||||
></JsonForm>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import React, { useRef } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Button, Dropdown, Form, FormInstance, Space, ConfigProvider } from "antd";
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Form,
|
||||
FormInstance,
|
||||
Space,
|
||||
ConfigProvider,
|
||||
} from "antd";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { jsonFormat, jsonStringFormat, jsonParse, jsonEdit } from "../../uitls";
|
||||
import { Template } from "../../templates";
|
||||
@@ -10,6 +17,8 @@ import ModalForm, { ModalFormProps } from "../ModalForm";
|
||||
import { CodeEditor } from "../../uitls/useMonacoEdit";
|
||||
import { globalConfig } from "antd/es/config-provider";
|
||||
import { HookAPI } from "antd/es/modal/useModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getLabel } from "../../uitls/i18n";
|
||||
|
||||
const template2menu: any = (template: Template) => {
|
||||
const { children, ...other } = template;
|
||||
@@ -49,12 +58,15 @@ type JsonFromProps = ModalFormProps & {
|
||||
|
||||
const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
const { templates, ...other } = props;
|
||||
const { t } = useTranslation();
|
||||
const formRef = useRef<FormInstance<any>>();
|
||||
const templateHandle = (json: string) => {
|
||||
let value = json;
|
||||
if (props.initialValues?.value) {
|
||||
const _old = jsonParse(props.initialValues.value);
|
||||
value = _old.name ? jsonEdit(json, [{path:'name', value: _old.name}]) : json;
|
||||
value = _old.name
|
||||
? jsonEdit(json, [{ path: "name", value: _old.name }])
|
||||
: json;
|
||||
}
|
||||
value = jsonStringFormat(value);
|
||||
formRef.current?.setFieldValue("value", value);
|
||||
@@ -70,8 +82,10 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
children: children.map(template2menu),
|
||||
};
|
||||
} else if (json) {
|
||||
const { label } = other;
|
||||
return {
|
||||
...other,
|
||||
label: getLabel(label),
|
||||
title: cli,
|
||||
onClick: () => templateHandle(json),
|
||||
};
|
||||
@@ -91,8 +105,11 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
{hasTemplate ? (
|
||||
<Space size={"small"} style={{ marginBottom: 5 }}>
|
||||
<span>模板:</span>
|
||||
<Space
|
||||
size={"small"}
|
||||
style={{ marginBottom: 5, width: "100%", overflow: "auto" }}
|
||||
>
|
||||
<span>{t("terms.template")}:</span>
|
||||
{templates.map((template, index) => {
|
||||
if (template.children?.length) {
|
||||
const menu = {
|
||||
@@ -115,7 +132,7 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
onClick={() => templateHandle(template.json)}
|
||||
menu={menu}
|
||||
>
|
||||
{template.label}
|
||||
{getLabel(template.label)}
|
||||
</Dropdown.Button>
|
||||
);
|
||||
} else {
|
||||
@@ -123,7 +140,7 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
<Dropdown key={index} menu={menu}>
|
||||
<Button size="small">
|
||||
<Space>
|
||||
{template.label}
|
||||
{getLabel(template.label)}
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
@@ -138,7 +155,7 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
title={template.cli}
|
||||
onClick={() => templateHandle(template.json)}
|
||||
>
|
||||
<Space>{template.label}</Space>
|
||||
<Space>{getLabel(template.label)}</Space>
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
@@ -147,25 +164,10 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
})}
|
||||
</Space>
|
||||
) : null}
|
||||
{/* <ProFormTextArea
|
||||
fieldProps={{ rows: 8 }}
|
||||
name="value"
|
||||
rules={[
|
||||
{ required: true, message: "不能为空" },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (value) jsonParse(value);
|
||||
resolve(null);
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
></ProFormTextArea> */}
|
||||
<Form.Item
|
||||
name="value"
|
||||
rules={[
|
||||
{ required: true, message: "不能为空" },
|
||||
{ required: true, message: t("msg.require") },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -175,7 +177,7 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
resolve(null);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
throw new Error("json 格式错误");
|
||||
throw new Error(t("msg.formatError", { name: "JSON" }));
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -187,7 +189,8 @@ const JsonForm: React.FC<JsonFromProps> = (props) => {
|
||||
language="json"
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
{/* <MonacoEditor
|
||||
className={"g-boder"}
|
||||
height={300}
|
||||
@@ -211,7 +214,11 @@ export const showJsonForm = (props: JsonFromProps) => {
|
||||
const config = globalConfig();
|
||||
timeoutId = setTimeout(() => {
|
||||
document.body.append(container);
|
||||
root.render(<ConfigProvider theme={config.getTheme()}><JsonForm {...props} /></ConfigProvider>);
|
||||
root.render(
|
||||
<ConfigProvider theme={config.getTheme()}>
|
||||
<JsonForm {...props} />
|
||||
</ConfigProvider>
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Button, ButtonProps } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const LanguageButton = (props: ButtonProps) => {
|
||||
const { i18n } = useTranslation();
|
||||
return (
|
||||
<Button
|
||||
shape="circle"
|
||||
{...props}
|
||||
onClick={() => {
|
||||
i18n.changeLanguage(i18n.resolvedLanguage === "en" ? "zh" : "en");
|
||||
}}
|
||||
>
|
||||
{i18n.resolvedLanguage === "en" ? "中" : "En"}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -21,6 +21,7 @@ import { GostCommit } from "../../api/local";
|
||||
import Ctx, { CardCtx } from "../../uitls/ctx";
|
||||
import { useListData1, UseTemplates } from "../ListCard/hooks";
|
||||
import { configEvent } from "../../uitls/events";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type PublicListProps = {
|
||||
name: string;
|
||||
@@ -56,6 +57,7 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
renderConfig = defaultRenderConfig,
|
||||
filter = defFilter,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
const { localList, comm } = useContext(CardCtx);
|
||||
const { gostConfig, localConfig } = useContext(Ctx);
|
||||
const { dataList, dataSource } = useListData1({
|
||||
@@ -116,7 +118,7 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
});
|
||||
};
|
||||
showJsonForm({
|
||||
title: "修改",
|
||||
title: t("base.cmd.edit"),
|
||||
initialValues: { value: jsonFormatValue(v) },
|
||||
onFinish: async (values: any) => {
|
||||
onup(jsonParse(values.value));
|
||||
@@ -142,9 +144,14 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
size="small"
|
||||
dataSource={showList}
|
||||
columns={[
|
||||
{ title: rowKey, dataIndex: rowKey, ellipsis: true, width: 100 },
|
||||
{
|
||||
title: "详情",
|
||||
title: t("base.form.name"),
|
||||
dataIndex: rowKey,
|
||||
ellipsis: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t("base.form.details"),
|
||||
ellipsis: true,
|
||||
render: (value, record, index) => {
|
||||
const isEnable = dataList.includes(record);
|
||||
@@ -165,7 +172,7 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
title: t("base.cmd.controls"),
|
||||
width: localApi ? 120 : 90,
|
||||
align: "right",
|
||||
dataIndex: rowKey,
|
||||
@@ -179,7 +186,7 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
{localApi ? (
|
||||
isEnable ? (
|
||||
<Button
|
||||
title="点击禁用"
|
||||
title={t("base.cmd.disable")}
|
||||
icon={
|
||||
<CheckCircleOutlined
|
||||
style={{ color: green.primary }}
|
||||
@@ -195,7 +202,7 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
title="点击启用"
|
||||
title={t("base.cmd.enabled")}
|
||||
type="link"
|
||||
icon={<StopOutlined style={{ color: red.primary }} />}
|
||||
size={"small"}
|
||||
@@ -208,13 +215,13 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
)
|
||||
) : null}
|
||||
<Button
|
||||
title="修改"
|
||||
title={t("base.cmd.edit")}
|
||||
icon={<EditOutlined />}
|
||||
type="link"
|
||||
size={"small"}
|
||||
onClick={() => {
|
||||
showJsonForm({
|
||||
title: `修改 ${value || ""}`,
|
||||
title: t("title.edit", {name: value || ''}),
|
||||
templates: templates,
|
||||
initialValues: { value: jsonFormatValue(content) },
|
||||
onFinish: async (values: any) => {
|
||||
@@ -234,13 +241,13 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
title="复制"
|
||||
title={t("base.cmd.copy")}
|
||||
icon={<CopyOutlined />}
|
||||
type="link"
|
||||
size={"small"}
|
||||
onClick={() => {
|
||||
showJsonForm({
|
||||
title: `复制自 ${value || ""}`,
|
||||
title: t("title.copied", {name: value || ''}),
|
||||
templates: templates,
|
||||
initialValues: { value: jsonFormatValue(content) },
|
||||
onFinish: async (values: any) => {
|
||||
@@ -253,8 +260,8 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
}}
|
||||
/>
|
||||
<Popconfirm
|
||||
title="警告"
|
||||
description="确定要删除吗?"
|
||||
title={t("text.warn")}
|
||||
description={t("text.deleteing")}
|
||||
onConfirm={() => {
|
||||
if (isEnable) {
|
||||
deleteValue(record);
|
||||
@@ -264,7 +271,7 @@ const PublicList: React.FC<PublicListProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title="删除"
|
||||
title={t("base.cmd.del")}
|
||||
icon={<DeleteOutlined />}
|
||||
type="link"
|
||||
size={"small"}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getModule } from "../../api/modules";
|
||||
import { configEvent } from "../../uitls/events";
|
||||
import { CloseOutlined } from "@ant-design/icons";
|
||||
import classnames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type ListCardProps = {
|
||||
module?: string;
|
||||
@@ -36,6 +37,7 @@ export const ProCard = (props: CardProps & { boxShadow?: boolean }) => {
|
||||
};
|
||||
|
||||
const ListCard: React.FC<ListCardProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
title,
|
||||
subTitle,
|
||||
@@ -49,7 +51,14 @@ const ListCard: React.FC<ListCardProps> = (props) => {
|
||||
localApi,
|
||||
filter,
|
||||
} = useMemo(() => {
|
||||
return { ...getModule(props.module || "")!, ...props };
|
||||
return {
|
||||
...getModule(props.module || "")!,
|
||||
...{
|
||||
title: t(`modules.${props.module}.title`),
|
||||
subTitle: t(`modules.${props.module}.subTitle`),
|
||||
},
|
||||
...props,
|
||||
};
|
||||
}, [props]);
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const _prop = {
|
||||
@@ -95,8 +104,8 @@ const ListCard: React.FC<ListCardProps> = (props) => {
|
||||
if (hasName()) {
|
||||
const confirmed = await new Promise((resolve, reject) => {
|
||||
Modal.confirm({
|
||||
title: "name无效",
|
||||
content: "是否自动分配name",
|
||||
title: t("msg.invalidName"),
|
||||
content: t("msg.autofixName"),
|
||||
zIndex: 2000,
|
||||
onOk: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
@@ -112,8 +121,8 @@ const ListCard: React.FC<ListCardProps> = (props) => {
|
||||
await addValue({ ...json, name: addName });
|
||||
json.name !== addName &&
|
||||
notification.info({
|
||||
description: `新分配 name 为 "${addName}"`,
|
||||
message: "自动修正提醒",
|
||||
description: t("msg.fixName", { name: addName }),
|
||||
message: t("msg.autofix"),
|
||||
});
|
||||
update && configEvent.emit("apiUpdate", {});
|
||||
},
|
||||
|
||||
@@ -2,7 +2,10 @@ import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Col, Flex, Row, Space } from "antd";
|
||||
import { DeleteOutlined } from "@ant-design/icons";
|
||||
import { GostApiConfig, getLocalServers, deleteLocal } from "../uitls/server";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const LocalServers = () => {
|
||||
const { t } = useTranslation();
|
||||
const [list, setList] = useState<GostApiConfig[]>();
|
||||
// const [local, setLocal] = useState<Record<string, GostApiConfig>>();
|
||||
|
||||
@@ -26,7 +29,7 @@ const LocalServers = () => {
|
||||
<>
|
||||
{list && list?.length > 0 ? (
|
||||
<Space direction="vertical" style={{ display: "flex" }}>
|
||||
<div>快速连接</div>
|
||||
<div>{t('home.quickConnect')}</div>
|
||||
<Row gutter={10}>
|
||||
{list.map((item) => {
|
||||
return (
|
||||
|
||||
@@ -10,9 +10,9 @@ export const ThemeButton = (props: ButtonProps) => {
|
||||
const Icon = isDark ? <MoonOutlined /> : <SunOutlined />;
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
shape="circle"
|
||||
icon={Icon}
|
||||
{...props}
|
||||
onClick={() => {
|
||||
const _settings = { ...settings };
|
||||
_settings.theme = isDark ? "light" : "dark";
|
||||
|
||||
@@ -6,6 +6,7 @@ import { showJsonForm } from "../Forms/Json";
|
||||
import { jsonFormatValue, jsonParse } from "../../uitls";
|
||||
import { useContext } from "react";
|
||||
import { UpdateCtx } from "../List/Public";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function viewChain(this: Partial<Config>, chain: ChainConfig) {
|
||||
const { hops } = chain;
|
||||
@@ -14,6 +15,7 @@ export default function viewChain(this: Partial<Config>, chain: ChainConfig) {
|
||||
|
||||
export function ViewHops(props: { hops: HopConfig[]; root: any }) {
|
||||
const { update } = useContext(UpdateCtx);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Space size={5}>
|
||||
{props.hops
|
||||
@@ -30,10 +32,10 @@ export function ViewHops(props: { hops: HopConfig[]; root: any }) {
|
||||
bordered={false}
|
||||
color="blue"
|
||||
className="editor-json"
|
||||
title="双击修改"
|
||||
title={t("text.doubleClickEdit")}
|
||||
onDoubleClick={() => {
|
||||
showJsonForm({
|
||||
title: "修改",
|
||||
title: t('base.cmd.edit'),
|
||||
initialValues: { value: jsonFormatValue(hop) },
|
||||
onFinish: async (values: any) => {
|
||||
props.hops[i] = jsonParse(values.value);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Space, Tag, Tooltip } from "antd";
|
||||
import { showJsonForm } from "../Forms/Json";
|
||||
import { jsonFormatValue, jsonParse } from "../../uitls";
|
||||
import { UpdateCtx } from "../List/Public";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function viewNode(this: Partial<Config>, data: NodeConfig) {
|
||||
const {
|
||||
@@ -55,6 +56,7 @@ export const ViewNode = ({
|
||||
isLink?: boolean;
|
||||
upjson?: (newNode: NodeConfig) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { name } = node;
|
||||
const { update } = useContext(UpdateCtx);
|
||||
return (
|
||||
@@ -63,11 +65,11 @@ export const ViewNode = ({
|
||||
bordered={false}
|
||||
color="green"
|
||||
className="editor-json"
|
||||
title="双击修改"
|
||||
title={t("text.doubleClickEdit")}
|
||||
onDoubleClick={() => {
|
||||
if (!upjson) return;
|
||||
showJsonForm({
|
||||
title: "修改",
|
||||
title: t('base.cmd.edit'),
|
||||
initialValues: { value: jsonFormatValue(node) },
|
||||
onFinish: async (values: any) => {
|
||||
upjson(jsonParse(values.value));
|
||||
|
||||
@@ -12,6 +12,7 @@ import { UpdateCtx } from "../List/Public";
|
||||
import { Space, Tag, Tooltip } from "antd";
|
||||
import { ViewNode } from "./node";
|
||||
import { ViewChain } from "./chain";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function viewService(
|
||||
this: Partial<Config>,
|
||||
@@ -116,6 +117,8 @@ export const ViewByChain = (props: { chainName: string }) => {
|
||||
|
||||
export const ViewService: React.FC<ServiceConfig> = (service) => {
|
||||
const { name, addr, handler, listener, forwarder } = service;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const type = useMemo(() => {
|
||||
const hType = handler?.type;
|
||||
const lType = listener?.type;
|
||||
@@ -132,7 +135,7 @@ export const ViewService: React.FC<ServiceConfig> = (service) => {
|
||||
color="#c7e7ff"
|
||||
arrow={false}
|
||||
>
|
||||
<Tag>转发链</Tag>
|
||||
<Tag>{t("modules.chain.subTitle")}</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
{listener.chain && (
|
||||
@@ -141,7 +144,7 @@ export const ViewService: React.FC<ServiceConfig> = (service) => {
|
||||
color="#fff0d7"
|
||||
arrow={false}
|
||||
>
|
||||
<Tag color="orange">远程端口转发</Tag>
|
||||
<Tag color="orange">{t("terms.key0")}</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
{forwarder && (
|
||||
@@ -150,7 +153,7 @@ export const ViewService: React.FC<ServiceConfig> = (service) => {
|
||||
color="#c7e7ff"
|
||||
arrow={false}
|
||||
>
|
||||
<Tag>转发/反代 节点</Tag>
|
||||
<Tag>{t("terms.forwarder")}</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
key,zh,en
|
||||
language,简体中文,English
|
||||
base.title,GOST API Manage,GOST API Manage
|
||||
base.description,连接API服务,Connecting to API Services
|
||||
base.form.username,用户名,Username
|
||||
base.form.password,密码,Password
|
||||
base.form.local,保存到本地,Save to local
|
||||
base.form.name,名称,Name
|
||||
base.form.details,详情,Details
|
||||
base.cmd.connect,连接,Connect
|
||||
base.cmd.controls,操作,Operation
|
||||
base.cmd.enabled,启用,Enabled
|
||||
base.cmd.disable,禁用,Disable
|
||||
base.cmd.copy,复制,Copy
|
||||
base.cmd.edit,修改,Edit
|
||||
base.cmd.del,删除,Delete
|
||||
text.warn,警告,Warning
|
||||
text.deleteing,确定要删除吗?,Are you sure you want to delete?
|
||||
text.doubleClickEdit,双击修改,Double click to modify
|
||||
modules.admission.title,准入控制器(Admission),Admission
|
||||
modules.admission.subTitle,准入控制器,admission
|
||||
modules.auther.title,认证器(Auther),Auther
|
||||
modules.auther.subTitle,认证器,auther
|
||||
modules.bypass.title,分流器(Bypass),Bypass
|
||||
modules.bypass.subTitle,分流器,bypass
|
||||
modules.chain.title,转发链(Chain),Chain
|
||||
modules.chain.subTitle,转发链,chain
|
||||
modules.climiter.title,并发连接数限制,climiter
|
||||
modules.climiter.subTitle,,climiter
|
||||
modules.limiter.title,流量速率限制,limiter
|
||||
modules.limiter.subTitle,,limiter
|
||||
modules.rlimiter.title,请求速率限制,rlimiter
|
||||
modules.rlimiter.subTitle,,rlimiter
|
||||
modules.hop.title,跳跃点(Hop),Hop
|
||||
modules.hop.subTitle,跳跃点,hop
|
||||
modules.host.title,主机映射器(Hosts),Hosts
|
||||
modules.host.subTitle,主机映射器,hosts
|
||||
modules.ingress.title,Ingress,Ingress
|
||||
modules.ingress.subTitle,Ingress,Ingress
|
||||
modules.resolver.title,域名解析器(Resolver),Resolver
|
||||
modules.resolver.subTitle,域名解析器,resolver
|
||||
modules.service.title,服务(Service),Service
|
||||
modules.service.subTitle,服务,service
|
||||
modules.sd.title,服务发现(SD),SD
|
||||
modules.sd.subTitle,服务发现,SD
|
||||
modules.observer.title,观测器(Observer),Observer
|
||||
modules.observer.subTitle,观测器,observer
|
||||
msg.baseURL.required,请输入API地址,BaseURL cannot be empty
|
||||
msg.require,不能为空!,Can not be empty!
|
||||
msg.success,操作成功!,Success!
|
||||
msg.unknown,出现未知错误!,Unknown error!
|
||||
msg.connectionFailed,连接失败!,Connection failed!
|
||||
msg.wordWrap,自动换行,Word Wrap
|
||||
msg.invalidName,name无效,Invalid name
|
||||
msg.autofixName,是否自动分配name?,Whether to automatically assign a name?
|
||||
msg.autofix,自动修正提醒,Autofix Reminder
|
||||
msg.fixName,新分配 name 为 {{name}},Reassign name to {{name}}
|
||||
msg.formatError,{{name}}格式错误!,{{name}}Format error!
|
||||
placeholder.baseURL,API baseURL,API baseURL
|
||||
placeholder.savePath,默认保存到上下文目录,"By default, it is saved to the context directory."
|
||||
home.quickConnect,快速连接,Quick Connect
|
||||
manage.cmd.reload,刷新配置,Reload Config
|
||||
manage.cmd.save,保存到服务器,Save to server
|
||||
manage.cmd.download,下载当前配置,Download Config
|
||||
manage.cmd.logout,退出,Logout
|
||||
manage.cmd.switch,切换服务,Switch Services
|
||||
manage.cmd.new,连接新服务,Connect to new Services
|
||||
manage.label.autoSave,自动保存,Autosave
|
||||
manage.label.format,格式,Format
|
||||
manage.label.path,保存路径,Save path
|
||||
manage.label.limit,限速限流,Speed and current limit
|
||||
manage.label.allConfig,完整配置,All Config JSON
|
||||
title.edit,修改 {{name}},Edit {{name}}
|
||||
title.add,添加 {{name}},Add {{name}}
|
||||
title.copied,复制自 {{name}},Copied from {{name}}
|
||||
template.default,默认,Default
|
||||
template.inline,内联,Inline
|
||||
terms.template,模板,Template
|
||||
terms.forwarder,转发/反代,Forwarder
|
||||
terms.key0,远程端口转发,Remote Port Forwarding
|
||||
|
@@ -1,15 +1,14 @@
|
||||
import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
const {def, docUrl, _docUrl }= getByName("admissions");
|
||||
|
||||
const def = getByName("admissions");
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/admission
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json: _docUrl + `
|
||||
{
|
||||
"name": "admission-0",
|
||||
"matchers": [
|
||||
@@ -19,5 +18,5 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("admission", "https://gost.run/concepts/admission"),
|
||||
...getOtherAll("admission", docUrl),
|
||||
] as Template[];
|
||||
|
||||
@@ -2,27 +2,9 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("authers");
|
||||
const {def, docUrl }= getByName("authers");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/auth
|
||||
{
|
||||
"name": "auther-0",
|
||||
"auths": [
|
||||
{
|
||||
"username": "user1",
|
||||
"password": "pass1"
|
||||
},
|
||||
{
|
||||
"username": "user2",
|
||||
"password": "pass2"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("auther", "https://gost.run/concepts/auth"),
|
||||
def,
|
||||
...getOtherAll("auther", docUrl),
|
||||
] as Template[];
|
||||
|
||||
@@ -2,14 +2,14 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("bypasses");
|
||||
const {def, docUrl, _docUrl }= getByName("bypasses");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/bypass/
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json: _docUrl + `
|
||||
{
|
||||
"name": "bypass-0",
|
||||
"matchers": [
|
||||
@@ -21,5 +21,5 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("bypass", "https://gost.run/concepts/bypass/"),
|
||||
...getOtherAll("bypass", docUrl),
|
||||
] as Template[];
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { getByName } from "./default";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("chains");
|
||||
const {def, docUrl, _docUrl }= getByName("chains");
|
||||
|
||||
export default [
|
||||
def,
|
||||
{
|
||||
label: "典型转发链",
|
||||
label: { zh: "典型转发链", en: "Typical" },
|
||||
cli: "",
|
||||
json: `
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "chain-0",
|
||||
"hops": [
|
||||
|
||||
@@ -2,14 +2,14 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("climiter");
|
||||
const {def, docUrl, _docUrl }= getByName("climiter");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/limiter/
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json: _docUrl + `
|
||||
{
|
||||
"name": "climiter-0",
|
||||
"limits": [
|
||||
@@ -19,7 +19,7 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("climiter", "https://gost.run/concepts/limiter/", {
|
||||
...getOtherAll("climiter", docUrl, {
|
||||
redisType: "set",
|
||||
}),
|
||||
] as Template[];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { findNodeAtLocation, parseTree } from "jsonc-parser";
|
||||
import { Template } from "./type";
|
||||
|
||||
// 源自 https://gost.run/reference/configuration/file/
|
||||
const defaultTemplates = `{
|
||||
"services": [
|
||||
@@ -241,6 +242,29 @@ const defaultTemplates = `{
|
||||
}
|
||||
}`;
|
||||
|
||||
const defaultLabel = {
|
||||
en: "Default",
|
||||
zh: "默认",
|
||||
};
|
||||
|
||||
const docs: { [key: string]: string } = {
|
||||
admissions: "https://gost.run/concepts/admission/",
|
||||
authers: "https://gost.run/concepts/auth/",
|
||||
bypasses: "https://gost.run/concepts/bypass/",
|
||||
chains: "https://gost.run/concepts/chain/",
|
||||
hops: "https://gost.run/concepts/hop/",
|
||||
hosts: "https://gost.run/concepts/hosts/",
|
||||
ingresses: "https://gost.run/concepts/ingress/",
|
||||
observers: "https://gost.run/concepts/observer/",
|
||||
resolvers: "https://gost.run/concepts/resolver/",
|
||||
limiters: "https://gost.run/concepts/limiter/",
|
||||
rlimiter: "https://gost.run/concepts/limiter/",
|
||||
climiter: "https://gost.run/concepts/limiter/",
|
||||
routers: "https://gost.run/concepts/router/",
|
||||
sds: "https://gost.run/concepts/sd/",
|
||||
services: "https://gost.run/concepts/service/",
|
||||
};
|
||||
|
||||
const error: any[] = [];
|
||||
const tree = parseTree(defaultTemplates, error, { allowTrailingComma: true });
|
||||
|
||||
@@ -254,15 +278,22 @@ export const getDefaultTempplates = (name: string) => {
|
||||
return;
|
||||
};
|
||||
|
||||
export const getByName = (name: string, label = "默认") => {
|
||||
export const getByName = (name: string, label = defaultLabel) => {
|
||||
const defT = getDefaultTempplates(name);
|
||||
// console.log("defT", name, defT);
|
||||
const docUrl = docs[name];
|
||||
const _docUrl = "// " + docUrl + "\n";
|
||||
let def;
|
||||
if (defT) {
|
||||
return {
|
||||
def = {
|
||||
label,
|
||||
json: defT,
|
||||
json: _docUrl + defT,
|
||||
} as Template;
|
||||
}
|
||||
return {
|
||||
def,
|
||||
docUrl,
|
||||
_docUrl,
|
||||
};
|
||||
};
|
||||
|
||||
export default defaultTemplates;
|
||||
|
||||
@@ -2,14 +2,14 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("hops");
|
||||
const {def, docUrl, _docUrl }= getByName("hops");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/hop
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json: _docUrl + `
|
||||
{
|
||||
"name": "hop-0",
|
||||
"nodes": [
|
||||
@@ -36,5 +36,5 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("hop", "https://gost.run/concepts/hop"),
|
||||
...getOtherAll("hop", docUrl),
|
||||
] as Template[];
|
||||
|
||||
+21
-19
@@ -2,27 +2,29 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("hosts");
|
||||
const {def, docUrl, _docUrl }= getByName("hosts");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/hosts/
|
||||
{
|
||||
"name": "hosts-0",
|
||||
"mappings": [
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"hostname": "example.com"
|
||||
},
|
||||
{
|
||||
"ip": "2001:db8::1",
|
||||
"hostname": "example.com"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("hosts", "https://gost.run/concepts/hosts/"),
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "hosts-0",
|
||||
"mappings": [
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"hostname": "example.com"
|
||||
},
|
||||
{
|
||||
"ip": "2001:db8::1",
|
||||
"hostname": "example.com"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("hosts", docUrl),
|
||||
] as Template[];
|
||||
|
||||
@@ -2,13 +2,14 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("ingresses");
|
||||
const {def, docUrl, _docUrl }= getByName("ingresses");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `{
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json: _docUrl + `{
|
||||
"name": "ingress-0",
|
||||
"rules": [
|
||||
{
|
||||
@@ -22,7 +23,7 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("ingress", "https://gost.run/concepts/ingress/", {
|
||||
...getOtherAll("ingress", docUrl, {
|
||||
redisType: "hash",
|
||||
}),
|
||||
] as Template[];
|
||||
|
||||
@@ -2,14 +2,16 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("limiters");
|
||||
const {def, docUrl, _docUrl }= getByName("limiters");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/limiter/
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "limiter-0",
|
||||
"limits": [
|
||||
@@ -20,7 +22,7 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("limiters", "https://gost.run/concepts/limiter/", {
|
||||
...getOtherAll("limiters", docUrl, {
|
||||
redisType: "set",
|
||||
}),
|
||||
] as Template[];
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
// import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("observers");
|
||||
const {def, docUrl, _docUrl }= getByName("observers");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "插件",
|
||||
json: `
|
||||
// https://gost.run/concepts/observer/
|
||||
label: "Plugin",
|
||||
json: _docUrl + `
|
||||
{
|
||||
"name": "observer-0",
|
||||
"plugin": {
|
||||
|
||||
@@ -53,7 +53,7 @@ export const getOtherAll = (
|
||||
const doc = docUrl ? `// ${docUrl} \n` : "";
|
||||
return [
|
||||
{
|
||||
label: "文件",
|
||||
label: "File",
|
||||
json: doc + getFileTemplate(name),
|
||||
},
|
||||
{
|
||||
@@ -65,7 +65,7 @@ export const getOtherAll = (
|
||||
json: doc + getHttpTemplate(name),
|
||||
},
|
||||
{
|
||||
label: "插件",
|
||||
label: "Plugin",
|
||||
json: doc + getPluginTemplate(name),
|
||||
},
|
||||
] as Template[];
|
||||
|
||||
@@ -2,58 +2,12 @@ import { getByName } from "./default";
|
||||
import { getPluginTemplate } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("resolvers");
|
||||
const {def, docUrl, _docUrl }= getByName("resolvers");
|
||||
|
||||
export default [
|
||||
def,
|
||||
// {
|
||||
// label: "默认",
|
||||
// json: `
|
||||
// // https://gost.run/concepts/resolver/
|
||||
// {
|
||||
// "name": "resolver-0",
|
||||
// "nameservers": [
|
||||
// {
|
||||
// "addr": "1.1.1.1"
|
||||
// },
|
||||
// {
|
||||
// "addr": "tcp://8.8.8.8"
|
||||
// },
|
||||
// {
|
||||
// "addr": "tls://8.8.8.8:853"
|
||||
// },
|
||||
// {
|
||||
// "addr": "https://1.0.0.1/dns-query"
|
||||
// }
|
||||
// ]
|
||||
// }`,
|
||||
// },
|
||||
// {
|
||||
// label: "使用转发链",
|
||||
// json: `
|
||||
// // https://gost.run/concepts/resolver/
|
||||
// {
|
||||
// "name": "resolver-0",
|
||||
// "nameservers": [
|
||||
// {
|
||||
// "addr": "1.1.1.1"
|
||||
// },
|
||||
// {
|
||||
// "addr": "tcp://8.8.8.8:53",
|
||||
// "chain": "chain-0"
|
||||
// },
|
||||
// {
|
||||
// "addr": "tls://8.8.8.8:853",
|
||||
// "chain": "chain-1"
|
||||
// },
|
||||
// {
|
||||
// "addr": "https://1.0.0.1/dns-query",
|
||||
// "chain": "chain-2"
|
||||
// }
|
||||
// ]
|
||||
// }`,
|
||||
// },
|
||||
{
|
||||
label: "插件",
|
||||
json: getPluginTemplate("resolver"),
|
||||
},
|
||||
{
|
||||
label: "Plugin",
|
||||
json: _docUrl + getPluginTemplate("resolver"),
|
||||
},
|
||||
] as Template[];
|
||||
|
||||
@@ -2,14 +2,16 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("rlimiter");
|
||||
const {def, docUrl, _docUrl }= getByName("rlimiter");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/limiter/
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "rlimiter-0",
|
||||
"limits": [
|
||||
@@ -20,7 +22,7 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("rlimiter", "https://gost.run/concepts/limiter/", {
|
||||
...getOtherAll("rlimiter", docUrl, {
|
||||
redisType: "set",
|
||||
}),
|
||||
] as Template[];
|
||||
|
||||
@@ -2,14 +2,14 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("routers");
|
||||
const {def, docUrl, _docUrl }= getByName("routers");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "内联",
|
||||
json: `
|
||||
// https://gost.run/concepts/router/
|
||||
label: { zh: "内联", en: "Inline" },
|
||||
json: _docUrl + `
|
||||
{
|
||||
"name": "router-0",
|
||||
"routes": [
|
||||
@@ -24,5 +24,5 @@ export default [
|
||||
]
|
||||
}`,
|
||||
},
|
||||
...getOtherAll("router", "https://gost.run/concepts/router/"),
|
||||
...getOtherAll("router", docUrl),
|
||||
] as Template[];
|
||||
|
||||
@@ -2,13 +2,14 @@ import { getByName } from "./default";
|
||||
import { getOtherAll } from "./otherOrigin";
|
||||
import { Template } from "./type";
|
||||
|
||||
const def = getByName("sds");
|
||||
const {def, docUrl, _docUrl }= getByName("sds");
|
||||
|
||||
export default [
|
||||
def
|
||||
? def
|
||||
: {
|
||||
label: "HTTP插件",
|
||||
json: `
|
||||
label: "HTTP Plugin",
|
||||
json: _docUrl + `
|
||||
// https://gost.run/concepts/sd/
|
||||
{
|
||||
"name": "sd-0",
|
||||
|
||||
+20
-24
@@ -2,12 +2,16 @@ import * as JSONC from "jsonc-parser";
|
||||
import { getByName } from "./default";
|
||||
import { Template } from "./type";
|
||||
|
||||
const {def, docUrl, _docUrl }= getByName("services");
|
||||
|
||||
const getProxyJson = (
|
||||
handlerType: string,
|
||||
listenerType: string,
|
||||
metadata?: object
|
||||
) => {
|
||||
let jsonc = `
|
||||
let jsonc =
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "service-0",
|
||||
"addr": ":1080",
|
||||
@@ -28,33 +32,21 @@ const getProxyJson = (
|
||||
edits.push(...JSONC.modify(jsonc, ["listener", "metadata"], metadata, {}));
|
||||
// edits.push(...JSONC.modify(jsonc, ["name"], undefined, {})) // undefined 可删除
|
||||
jsonc = JSONC.applyEdits(jsonc, edits);
|
||||
// jsonc = JSONC.applyEdits(
|
||||
// jsonc,
|
||||
// JSONC.modify(jsonc, ["metadata"], metadata, {})
|
||||
// );
|
||||
// jsonc = JSONC.applyEdits(
|
||||
// jsonc,
|
||||
// JSONC.modify(jsonc, ["handler","metadata"], metadata, {})
|
||||
// );
|
||||
// jsonc = JSONC.applyEdits(
|
||||
// jsonc,
|
||||
// JSONC.modify(jsonc, ["listener","metadata"], metadata, {})
|
||||
// );
|
||||
}
|
||||
|
||||
return jsonc;
|
||||
};
|
||||
|
||||
const def = getByName("services");
|
||||
|
||||
export default [
|
||||
def,
|
||||
{
|
||||
label: "端口转发",
|
||||
label: { zh: "端口转发", en: "Port Forwarding" },
|
||||
cli: "-L tcp://:80/:8080",
|
||||
json: `
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "service-0", // 服务名称
|
||||
"name": "service-0",
|
||||
"addr": ":80",
|
||||
"handler": {
|
||||
"type": "tcp"
|
||||
@@ -73,9 +65,11 @@ export default [
|
||||
}`,
|
||||
},
|
||||
{
|
||||
label: "反向代理",
|
||||
label: { zh: "反向代理", en: "Reverse Proxy" },
|
||||
cli: "",
|
||||
json: `
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "service-0",
|
||||
"addr": ":80",
|
||||
@@ -104,7 +98,7 @@ export default [
|
||||
}`,
|
||||
},
|
||||
{
|
||||
label: "代理服务",
|
||||
label: { zh: "代理服务", en: "Proxy Service" },
|
||||
children: [
|
||||
{
|
||||
label: "http",
|
||||
@@ -156,7 +150,7 @@ export default [
|
||||
json: getProxyJson("socks5", "tcp"),
|
||||
},
|
||||
{
|
||||
label: "socks5(支持udp)",
|
||||
label: "socks5(udp)",
|
||||
json: getProxyJson("socks5", "tcp", { udp: "true" }),
|
||||
},
|
||||
{
|
||||
@@ -168,9 +162,11 @@ export default [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "代理转发",
|
||||
label: { zh: "代理转发", en: "Porxy Forwarding" },
|
||||
cli: "-L socks5://:1080 -F socks5://xxx.com:1080",
|
||||
json: `
|
||||
json:
|
||||
_docUrl +
|
||||
`
|
||||
{
|
||||
"name": "service-0",
|
||||
"addr": ":1080",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type Template = {
|
||||
label: string;
|
||||
json: string;
|
||||
cli?: string;
|
||||
children?: Template[];
|
||||
}
|
||||
label: string | { [key: string]: string };
|
||||
json: string;
|
||||
cli?: string;
|
||||
children?: Template[];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import { isValidElement } from "react"
|
||||
const en = await import("../i18n/en.json");
|
||||
const zh = await import("../i18n/zh.json");
|
||||
const fallbackLng = 'en';
|
||||
i18n
|
||||
// 检测用户当前使用的语言
|
||||
// 文档: https://github.com/i18next/i18next-browser-languageDetector
|
||||
.use(LanguageDetector)
|
||||
// 注入 react-i18next 实例
|
||||
.use(initReactI18next)
|
||||
// 初始化 i18next
|
||||
// 配置参数的文档: https://www.i18next.com/overview/configuration-options
|
||||
.init({
|
||||
debug: true,
|
||||
fallbackLng: fallbackLng,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
resources: {
|
||||
en: { translation: en },
|
||||
zh: { translation: zh },
|
||||
},
|
||||
});
|
||||
|
||||
export function getLabel(label: string | { [key: string]: string }) {
|
||||
if(typeof label === "string") return label;
|
||||
if(isValidElement(label)) return label;
|
||||
const lang = i18n.resolvedLanguage!;
|
||||
return label[lang] || label[fallbackLng] || '<unknown>';
|
||||
}
|
||||
export default i18n;
|
||||
@@ -2,6 +2,7 @@ import axios from "axios";
|
||||
import { getInfo } from "./server";
|
||||
import { message } from "antd";
|
||||
import { configEvent } from "./events";
|
||||
import { t } from "i18next";
|
||||
|
||||
const require = axios.create();
|
||||
require.interceptors.request.use((config) => {
|
||||
@@ -14,7 +15,7 @@ require.interceptors.response.use(
|
||||
(res) => {
|
||||
if (res.config.method !== "get" && !(res.config as any)?.noMsg) {
|
||||
// configEvent.emit("apiUpdate", res.config);
|
||||
message.success('操作成功!')
|
||||
message.success(t("msg.success"));
|
||||
}
|
||||
if (res.data) {
|
||||
return res.data;
|
||||
@@ -23,7 +24,7 @@ require.interceptors.response.use(
|
||||
},
|
||||
(error) => {
|
||||
const { response } = error || {};
|
||||
let msg = error.message || "出现未知错误!";
|
||||
let msg = error.message || t("msg.unknown");
|
||||
if (response?.data?.msg) {
|
||||
msg = response?.data?.msg;
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,6 +2,7 @@ import getUseValue from "./getUseValue";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import { message } from "antd";
|
||||
import { t } from "i18next";
|
||||
import { ServerComm } from "../api/local";
|
||||
import { Config } from "../api/types";
|
||||
const gostServerKey = "__GOST_SERVER__";
|
||||
@@ -111,7 +112,7 @@ export const login = async (arg: GostApiConfig, save?: false) => {
|
||||
} catch (e: any) {
|
||||
if (e === "verify error") {
|
||||
logout();
|
||||
message.error(e?.message || "连接失败");
|
||||
message.error(e?.message || t("msg.connectionFailed"));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type * as Monaco from "monaco-editor";
|
||||
import classnames from "classnames";
|
||||
import { useIsDark } from "./useTheme";
|
||||
import { Input } from "antd";
|
||||
import { getI18n, useTranslation } from 'react-i18next';
|
||||
|
||||
(function () {
|
||||
const require = (window as any).require;
|
||||
@@ -28,10 +29,14 @@ import { Input } from "antd";
|
||||
// const monacoURL = `//unpkg.com/monaco-editor@0.47.0/min/vs`
|
||||
// require.config({ paths: { vs: `${monacoURL}` } });
|
||||
if (require) {
|
||||
const i18n = getI18n();
|
||||
i18n.on('languageChanged',(event)=>{
|
||||
console.log('languageChanged', event)
|
||||
})
|
||||
require.config({
|
||||
"vs/nls": {
|
||||
availableLanguages: {
|
||||
"*": "zh-cn",
|
||||
"*": i18n.resolvedLanguage === "zh" ? "zh-cn" : "en",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -76,6 +81,7 @@ const CodeEditor_: React.ForwardRefRenderFunction<
|
||||
CodeEditorProps
|
||||
> = (props, ref) => {
|
||||
const { onChange, options, height, className, theme, style } = props;
|
||||
const { t } = useTranslation();
|
||||
const [language, setLanguage] = useBindValue(props.language, "javascript");
|
||||
const [value, setValue] = useBindValue(props.value, props.defaultValue, "");
|
||||
const [isReady, setReady] = useState(false);
|
||||
@@ -131,7 +137,7 @@ const CodeEditor_: React.ForwardRefRenderFunction<
|
||||
|
||||
_editor.addAction({
|
||||
id: "my-autoWrap-toggle",
|
||||
label: "切换自动换行",
|
||||
label: t("msg.wordWrap"),
|
||||
keybindings: [
|
||||
monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,
|
||||
// monaco.KeyMod.chord(
|
||||
|
||||
Reference in New Issue
Block a user