- {`备案号: `}
({
layout: {
@@ -87,6 +92,8 @@ function useQuery() {
}
function LoginForm() {
+ const { t } = useTranslation();
+
const [email, setEmail] = useState("");
const [pwd, setPwd] = useState("");
const [loading, setLoading] = useState(false);
@@ -95,7 +102,9 @@ function LoginForm() {
const [faCode, setFACode] = useState("");
const loginCaptcha = useSelector((state) => state.siteConfig.loginCaptcha);
- const registerEnabled = useSelector((state) => state.siteConfig.registerEnabled);
+ const registerEnabled = useSelector(
+ (state) => state.siteConfig.registerEnabled
+ );
const title = useSelector((state) => state.siteConfig.title);
const authn = useSelector((state) => state.siteConfig.authn);
@@ -143,7 +152,7 @@ function LoginForm() {
SetSessionStatus(true);
history.push("/home");
- ToggleSnackbar("top", "right", "登录成功", "success");
+ ToggleSnackbar("top", "right", t("login.success"), "success");
localStorage.removeItem("siteConfigCache");
};
@@ -151,7 +160,12 @@ function LoginForm() {
const authnLogin = (e) => {
e.preventDefault();
if (!navigator.credentials) {
- ToggleSnackbar("top", "right", "当前浏览器或环境不支持", "warning");
+ ToggleSnackbar(
+ "top",
+ "right",
+ t("login.browserNotSupport"),
+ "warning"
+ );
return;
}
@@ -261,13 +275,13 @@ function LoginForm() {
- 登录 {title}
+ {t("login.title", { title })}
{!useAuthn && (
)}
@@ -315,7 +329,7 @@ function LoginForm() {
)}
- 忘记密码
+
+ {t("login.forgetPassword")}
+
- { registerEnabled && 注册账号 }
+ {registerEnabled && (
+
+ {t("login.signUpAccount")}
+
+ )}
@@ -368,7 +388,7 @@ function LoginForm() {
marginRight: 8,
}}
/>
- 使用外部验证器登录
+ {t("login.useFIDO2")}
>
)}
{useAuthn && (
@@ -378,7 +398,7 @@ function LoginForm() {
marginRight: 8,
}}
/>
- 使用密码登录
+ {t("login.usePassword")}
>
)}
@@ -392,12 +412,12 @@ function LoginForm() {
- 二步验证
+ {t("login.2FA")}
{" "}
diff --git a/src/component/Placeholder/ErrorBoundary.js b/src/component/Placeholder/ErrorBoundary.js
index 2311df4..ae4bab9 100644
--- a/src/component/Placeholder/ErrorBoundary.js
+++ b/src/component/Placeholder/ErrorBoundary.js
@@ -1,5 +1,6 @@
import React from "react";
import { withStyles } from "@material-ui/core";
+import { withTranslation } from "react-i18next";
const styles = {
h1: {
@@ -18,7 +19,6 @@ class ErrorBoundary extends React.Component {
}
static getDerivedStateFromError() {
- // 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
@@ -30,20 +30,17 @@ class ErrorBoundary extends React.Component {
}
render() {
- const { classes } = this.props;
+ const { classes, t } = this.props;
if (this.state.hasError) {
- // 你可以自定义降级后的 UI 并渲染
return (
<>
:(
-
- 页面渲染出现错误,请尝试刷新此页面。
-
+
{t("renderError")}
{this.state.error &&
this.state.errorInfo &&
this.state.errorInfo.componentStack && (
- 错误详情
+ {t("errorDetails")}
{this.state.error.toString()}
@@ -62,4 +59,4 @@ class ErrorBoundary extends React.Component {
}
}
-export default withStyles(styles)(ErrorBoundary);
+export default withTranslation(["common"])(withStyles(styles)(ErrorBoundary));
diff --git a/src/i18n.ts b/src/i18n.ts
new file mode 100644
index 0000000..47ed3be
--- /dev/null
+++ b/src/i18n.ts
@@ -0,0 +1,21 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+
+import Backend from "i18next-http-backend";
+import LanguageDetector from "i18next-browser-languagedetector";
+
+i18n.use(Backend)
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ fallbackLng: "en-US",
+ debug: true,
+ ns: ["common", "application"],
+ load: "currentOnly",
+ defaultNS: "application",
+ interpolation: {
+ escapeValue: false,
+ },
+ });
+
+export default i18n;
diff --git a/src/index.js b/src/index.js
index d411ac2..dd9e9e5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -11,16 +11,16 @@ import { UpdateSiteConfig } from "./middleware/Init";
import ErrorBoundary from "./component/Placeholder/ErrorBoundary";
import { createBrowserHistory } from "history";
import { ConnectedRouter, routerMiddleware } from "connected-react-router";
+import i18next from "./i18n";
const Admin = React.lazy(() => import("./Admin"));
if (window.location.hash !== "") {
window.location.href = window.location.hash.split("#")[1];
}
-
serviceWorker.register({
onUpdate: (registration) => {
- alert("当前页面有新版本可用,准备刷新。");
+ alert(i18next.t("newVersionRefresh", { ns: "common" }));
if (registration && registration.waiting) {
registration.waiting.postMessage({ type: "SKIP_WAITING" });
}
@@ -41,21 +41,21 @@ const store = createStore(cloureveApp(history), reduxEnhance);
UpdateSiteConfig(store);
ReactDOM.render(
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+ ,
document.getElementById("root")
);
diff --git a/src/middleware/Api.ts b/src/middleware/Api.ts
index 267eb23..f1daf1c 100644
--- a/src/middleware/Api.ts
+++ b/src/middleware/Api.ts
@@ -1,5 +1,6 @@
import axios from "axios";
import Auth from "./Auth";
+import i18next from "../i18n";
export const baseURL = "/api/v3";
@@ -34,7 +35,16 @@ class AppError extends Error {
constructor(message: string | undefined, public code: any, error: any) {
super(message);
this.code = code;
- this.message = message || "未知错误";
+ if (i18next.exists(`errors.${code}`, { ns: "common" })) {
+ this.message = i18next.t(`errors.${code}`, {
+ ns: "common",
+ message,
+ });
+ } else {
+ this.message =
+ message || i18next.t("unknownError", { ns: "common" });
+ }
+
this.message += error ? " " + error : "";
this.stack = new Error().stack;
}
@@ -49,13 +59,13 @@ instance.interceptors.response.use(
response.rawData.code !== 0 &&
response.rawData.code !== 203
) {
- // 登录过期
+ // Login expired
if (response.rawData.code === 401) {
Auth.signout();
window.location.href = "/login";
}
- // 非管理员
+ // Non-admin
if (response.rawData.code === 40008) {
window.location.href = "/home";
}
diff --git a/src/middleware/Init.js b/src/middleware/Init.js
index fc4e943..8aa4585 100644
--- a/src/middleware/Init.js
+++ b/src/middleware/Init.js
@@ -6,6 +6,7 @@ import {
setSiteConfig,
toggleSnackbar,
} from "../redux/explorer";
+import i18next from "../i18n";
const initUserConfig = (siteConfig) => {
if (siteConfig.user !== undefined && !siteConfig.user.anonymous) {
@@ -81,7 +82,8 @@ export async function UpdateSiteConfig(store) {
toggleSnackbar(
"top",
"right",
- "无法加载站点配置:" + error.message,
+ i18next.t("errLoadingSiteConfig", { ns: "common" }) +
+ error.message,
"error"
)
);
diff --git a/yarn.lock b/yarn.lock
index 097f977..7bcd6a6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1173,6 +1173,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.2":
+ version "7.17.9"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
+ integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.16.7", "@babel/template@^7.4.0", "@babel/template@^7.6.0", "@babel/template@^7.8.6":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@@ -3599,6 +3606,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+cross-fetch@3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
+ integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
+ dependencies:
+ node-fetch "2.6.7"
+
cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -5642,7 +5656,7 @@ html-entities@^1.3.1:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
-html-escaper@^2.0.0:
+html-escaper@^2.0.0, html-escaper@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
@@ -5660,6 +5674,13 @@ html-minifier@^3.5.20:
relateurl "0.2.x"
uglify-js "3.4.x"
+html-parse-stringify@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
+ integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
+ dependencies:
+ void-elements "3.1.0"
+
html-webpack-plugin@4.0.0-beta.5:
version "4.0.0-beta.5"
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz#2c53083c1151bfec20479b1f8aaf0039e77b5513"
@@ -5777,6 +5798,27 @@ hyphenate-style-name@^1.0.3:
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+i18next-browser-languagedetector@^6.1.4:
+ version "6.1.4"
+ resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz#7b087c5edb6f6acd38ef54ede2160ab9cde0108f"
+ integrity sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==
+ dependencies:
+ "@babel/runtime" "^7.14.6"
+
+i18next-http-backend@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.0.tgz#e6abef0615316e00837798d2385c13395096f963"
+ integrity sha512-wsvx7E/CT1pHmBM99Vu57YLJpsrHbVjxGxf25EIJ/6oTjsvCkZZ6c3SA4TejcK5jIHfv9oLxQX8l+DFKZHZ0Gg==
+ dependencies:
+ cross-fetch "3.1.5"
+
+i18next@^21.6.16:
+ version "21.6.16"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.16.tgz#8cff8c3ba2ffaf8438a8c83fe284083f15cf3941"
+ integrity sha512-xJlzrVxG9CyAGsbMP1aKuiNr1Ed2m36KiTB7hjGMG2Zo4idfw3p9THUEu+GjBwIgEZ7F11ZbCzJcfv4uyfKNuw==
+ dependencies:
+ "@babel/runtime" "^7.17.2"
+
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -7806,6 +7848,13 @@ node-ensure@^0.0.0:
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
+node-fetch@2.6.7:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+ integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+ dependencies:
+ whatwg-url "^5.0.0"
+
node-forge@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@@ -9564,6 +9613,15 @@ react-hotkeys@^2.0.0:
dependencies:
prop-types "^15.6.1"
+react-i18next@^11.16.7:
+ version "11.16.7"
+ resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.16.7.tgz#8d0680b7f4c8e43f59996336b7183ad576a28df7"
+ integrity sha512-7yotILJLnKfvUfrl/nt9eK9vFpVFjZPLWAwBzWL6XppSZZEvlmlKk0GBGDCAPfLfs8oND7WAbry8wGzdoiW5Nw==
+ dependencies:
+ "@babel/runtime" "^7.14.5"
+ html-escaper "^2.0.2"
+ html-parse-stringify "^3.0.1"
+
react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -11252,6 +11310,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
+
ts-pnp@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90"
@@ -11596,6 +11659,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
+void-elements@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
+ integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
+
w3c-hr-time@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
@@ -11644,6 +11712,11 @@ wbuf@^1.1.0, wbuf@^1.7.3:
dependencies:
minimalistic-assert "^1.0.0"
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
+
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -11785,6 +11858,14 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
whatwg-url@^6.4.1:
version "6.5.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"