This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
The following commit(s) were added to refs/heads/master by this push:
new cac5699 fix(web): switch language without reloading the page
cac5699 is described below
commit cac56995ead0bb7885866b08d65dbde372ff28bf
Author: Yang Luo <[email protected]>
AuthorDate: Thu Aug 20 09:36:08 2026 +0800
fix(web): switch language without reloading the page
---
web/src/App.tsx | 8 ++-
web/src/Setting.tsx | 5 --
web/src/components/DataTable.tsx | 2 +-
web/src/components/NodeTable.tsx | 3 +-
web/src/components/charts/ChartCards.tsx | 5 +-
web/src/components/layout/Footer.tsx | 4 +-
web/src/components/layout/LanguageSelect.tsx | 2 +-
web/src/components/rules/IpRuleTable.tsx | 2 +-
web/src/components/rules/RowActions.tsx | 7 +--
web/src/locales/en/data.json | 25 +++++++++
web/src/locales/zh/data.json | 79 ++++++++++++++++++----------
web/src/pages/AccountPage.tsx | 4 +-
web/src/pages/AuthCallback.tsx | 7 +--
web/src/pages/CertEditPage.tsx | 8 +--
web/src/pages/CertListPage.tsx | 22 ++++----
web/src/pages/ChannelListPage.tsx | 2 +-
web/src/pages/NodeEditPage.tsx | 8 +--
web/src/pages/NodeListPage.tsx | 14 ++---
web/src/pages/RecordEditPage.tsx | 8 +--
web/src/pages/RecordListPage.tsx | 16 +++---
web/src/pages/RuleEditPage.tsx | 14 ++---
web/src/pages/RuleListPage.tsx | 12 ++---
web/src/pages/SigninPage.tsx | 4 +-
web/src/pages/SiteEditPage.tsx | 8 +--
web/src/pages/SiteListPage.tsx | 16 +++---
25 files changed, 170 insertions(+), 115 deletions(-)
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 32bacca..17d42fc 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -14,6 +14,8 @@
import * as React from "react";
import {Navigate, Route, Routes, useLocation} from "react-router-dom";
+import {useTranslation} from "react-i18next";
+import i18next from "i18next";
import * as AccountBackend from "@/backend/AccountBackend";
import * as Conf from "@/Conf";
@@ -55,6 +57,8 @@ Setting.initCasdoorSdk(Conf.AuthConfig);
const collapsedKey = "sidebarCollapsed";
export default function App() {
+ // Pages call i18next.t() directly, so re-render the whole tree on a
language change.
+ useTranslation();
// undefined while the account request is in flight, null when signed out.
const [account, setAccount] = React.useState<Account | null |
undefined>(undefined);
const [collapsed, setCollapsed] = React.useState(
@@ -104,10 +108,10 @@ export default function App() {
AccountBackend.signout().then(res => {
if (res.status === "ok") {
setAccount(null);
- Setting.showMessage("success", "Successfully signed out, redirected to
homepage");
+ Setting.showMessage("success", i18next.t("general:Signed out
successfully"));
Setting.goToLink("/");
} else {
- Setting.showMessage("error", `Signout failed: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to sign
out")}: ${res.msg}`);
}
});
};
diff --git a/web/src/Setting.tsx b/web/src/Setting.tsx
index 643eff4..1cf46fd 100644
--- a/web/src/Setting.tsx
+++ b/web/src/Setting.tsx
@@ -198,11 +198,6 @@ export function setLanguage(language: string) {
i18next.changeLanguage(language);
}
-export function changeLanguage(language: string) {
- setLanguage(language);
- window.location.reload();
-}
-
export function isResponseDenied(data: {msg?: string}) {
return data.msg === "Unauthorized operation" || data.msg === "未授权的操作";
}
diff --git a/web/src/components/DataTable.tsx b/web/src/components/DataTable.tsx
index 76a13ed..2759933 100644
--- a/web/src/components/DataTable.tsx
+++ b/web/src/components/DataTable.tsx
@@ -209,7 +209,7 @@ export function DataTable<T>({
{pageRows.length === 0 ? (
<TableRow className="hover:bg-transparent">
<TableCell colSpan={columnCount} className="h-24 text-center
text-muted-foreground">
- {loading ? <Spinner className="mx-auto" /> : (emptyText ?? "No
data")}
+ {loading ? <Spinner className="mx-auto" /> : (emptyText ??
i18next.t("general:No data"))}
</TableCell>
</TableRow>
) : (
diff --git a/web/src/components/NodeTable.tsx b/web/src/components/NodeTable.tsx
index ac2567d..08df2b7 100644
--- a/web/src/components/NodeTable.tsx
+++ b/web/src/components/NodeTable.tsx
@@ -14,6 +14,7 @@
import * as React from "react";
import {CircleCheck, CircleMinus, RefreshCw} from "lucide-react";
+import i18next from "i18next";
import * as Setting from "@/Setting";
import {DataTable, type Column} from "@/components/DataTable";
@@ -107,7 +108,7 @@ export function NodeTable({
render: (text: string, _record, index) => (
<Select value={text || undefined} onValueChange={value =>
updateField(index, "name", value)}>
<SelectTrigger>
- <SelectValue placeholder="Node" />
+ <SelectValue placeholder={i18next.t("site:Node")} />
</SelectTrigger>
<SelectContent>
{availableNodes(text).map(node => (
diff --git a/web/src/components/charts/ChartCards.tsx
b/web/src/components/charts/ChartCards.tsx
index 04e5107..b0d62c6 100644
--- a/web/src/components/charts/ChartCards.tsx
+++ b/web/src/components/charts/ChartCards.tsx
@@ -80,6 +80,7 @@ export function PieChartCard({
title: string;
data: {name: string; value: number}[] | null;
}) {
+ const other = i18next.t("general:Other");
// Rank the slices, keep the five that get their own color, and sum the tail
// into a single neutral "Other" slice so no color is ever reused.
const {slices, config} = React.useMemo(() => {
@@ -92,7 +93,7 @@ export function PieChartCard({
if (tail.length > 0) {
head.push({
- name: i18next.t("general:Other"),
+ name: other,
value: tail.reduce((sum, item) => sum + item.value, 0),
fill: OTHER_COLOR,
});
@@ -104,7 +105,7 @@ export function PieChartCard({
});
return {slices: head, config: chartConfig};
- }, [data]);
+ }, [data, other]);
return (
<Card className="h-full">
diff --git a/web/src/components/layout/Footer.tsx
b/web/src/components/layout/Footer.tsx
index 18bcab3..f79434b 100644
--- a/web/src/components/layout/Footer.tsx
+++ b/web/src/components/layout/Footer.tsx
@@ -12,13 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import i18next from "i18next";
+
import * as Setting from "@/Setting";
export function Footer() {
return (
<footer className="mt-auto border-t py-4">
<div className="flex items-center justify-center gap-2 text-sm
text-muted-foreground">
- Powered by
+ {i18next.t("general:Powered by")}
<a
target="_blank"
rel="noreferrer"
diff --git a/web/src/components/layout/LanguageSelect.tsx
b/web/src/components/layout/LanguageSelect.tsx
index e2a45ea..c79c2a1 100644
--- a/web/src/components/layout/LanguageSelect.tsx
+++ b/web/src/components/layout/LanguageSelect.tsx
@@ -33,7 +33,7 @@ export function LanguageSelect() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{Setting.Countries.map(country => (
- <DropdownMenuItem key={country.key} onClick={() =>
Setting.changeLanguage(country.key)}>
+ <DropdownMenuItem key={country.key} onClick={() =>
Setting.setLanguage(country.key)}>
<img
src={`${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`}
alt={country.alt}
diff --git a/web/src/components/rules/IpRuleTable.tsx
b/web/src/components/rules/IpRuleTable.tsx
index 45d7ebc..9a66f75 100644
--- a/web/src/components/rules/IpRuleTable.tsx
+++ b/web/src/components/rules/IpRuleTable.tsx
@@ -85,7 +85,7 @@ export function IpRuleTable({title, table, onUpdateTable}:
ExpressionTableProps)
// split apart for editing and joined back on every change.
render: (text: string, _record, index) => (
<TagsInput
- placeholder="Input IP Addresses"
+ placeholder={i18next.t("general:Input IP Addresses")}
value={text ? text.split(",") : []}
onChange={value => updateField(index, "value", value.map(item =>
item.trim()).join(","))}
/>
diff --git a/web/src/components/rules/RowActions.tsx
b/web/src/components/rules/RowActions.tsx
index 5ac684e..377ed55 100644
--- a/web/src/components/rules/RowActions.tsx
+++ b/web/src/components/rules/RowActions.tsx
@@ -13,6 +13,7 @@
// limitations under the License.
import {ArrowDown, ArrowUp, Trash2} from "lucide-react";
+import i18next from "i18next";
import {Button} from "@/components/ui/button";
import {Tooltip} from "@/components/ui/tooltip";
@@ -33,17 +34,17 @@ export function RowActions({
}) {
return (
<div className="flex items-center gap-1">
- <Tooltip title="Up">
+ <Tooltip title={i18next.t("general:Up")}>
<Button variant="outline" size="icon-sm" disabled={index === 0}
onClick={onUp}>
<ArrowUp />
</Button>
</Tooltip>
- <Tooltip title="Down">
+ <Tooltip title={i18next.t("general:Down")}>
<Button variant="outline" size="icon-sm" disabled={index === length -
1} onClick={onDown}>
<ArrowDown />
</Button>
</Tooltip>
- <Tooltip title="Delete">
+ <Tooltip title={i18next.t("general:Delete")}>
<Button variant="outline" size="icon-sm" onClick={onDelete}>
<Trash2 />
</Button>
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index 42de594..c449adc 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -92,10 +92,12 @@
"Copy private key": "Copy private key",
"Crypto algorithm": "Crypto algorithm",
"Domain expire": "Domain expire",
+ "Domain expire refreshed successfully": "Domain expire refreshed
successfully",
"Download certificate": "Download certificate",
"Download private key": "Download private key",
"Edit Cert": "Edit Cert",
"Expire time": "Expire time",
+ "Failed to refresh domain expire": "Failed to refresh domain expire",
"Private key": "Private key",
"Private key copied to clipboard successfully": "Private key copied to
clipboard successfully",
"Provider": "Provider",
@@ -134,6 +136,7 @@
"general": {
"Action": "Action",
"Add": "Add",
+ "Added successfully": "Added successfully",
"Advanced": "Advanced",
"At Any Time": "At Any Time",
"Back Home": "Back Home",
@@ -147,9 +150,16 @@
"CreatedTime": "CreatedTime",
"Dashboard": "Dashboard",
"Delete": "Delete",
+ "Deleted successfully": "Deleted successfully",
"Display name": "Display name",
+ "Down": "Down",
"Edit": "Edit",
"Edit Record": "Edit Record",
+ "Failed to add": "Failed to add",
+ "Failed to delete": "Failed to delete",
+ "Failed to get data": "Failed to get data",
+ "Failed to save": "Failed to save",
+ "Failed to sign out": "Failed to sign out",
"Gateway Analytics": "Gateway Analytics",
"Go to writable demo site?": "Go to writable demo site?",
"Half A Hour": "Half A Hour",
@@ -157,23 +167,33 @@
"Host": "Host",
"ID": "ID",
"IP Address": "IP Address",
+ "Input IP Addresses": "Input IP Addresses",
+ "Logged in successfully": "Logged in successfully",
+ "Login Error": "Login Error",
"Method": "Method",
"Name": "Name",
"No Upgrade": "No Upgrade",
+ "No data": "No data",
"Nodes": "Nodes",
"OK": "OK",
"Other": "Other",
"Owner": "Owner",
"Password": "Password",
"Path": "Path",
+ "Powered by": "Powered by",
"Records": "Records",
"Refresh": "Refresh",
+ "Request Count Over Time": "Request Count Over Time",
"Restore": "Restore",
"Rules": "Rules",
"Save": "Save",
"Self": "Self",
+ "Signed out successfully": "Signed out successfully",
+ "Signing in...": "Signing in...",
"Sites": "Sites",
"Sorry, you do not have permission to access this page or logged in status
invalid.": "Sorry, you do not have permission to access this page or logged in
status invalid.",
+ "Successfully saved": "Successfully saved",
+ "Sure to delete {name} ?": "Sure to delete {name} ?",
"Tag": "Tag",
"This is a read-only demo site!": "This is a read-only demo site!",
"Toggle sidebar": "Toggle sidebar",
@@ -181,6 +201,7 @@
"Top 10 User-Agents": "Top 10 User-Agents",
"Total Request Count": "Total Request Count",
"Unique IP Count": "Unique IP Count",
+ "Up": "Up",
"Update time": "Update time",
"Upgrade mode": "Upgrade mode",
"User-Agent": "User-Agent",
@@ -200,13 +221,17 @@
"Expressions": "Expressions",
"IP List": "IP List",
"IP Rate Limiting": "IP Rate Limiting",
+ "IPs": "IPs",
"Logic": "Logic",
"Operator": "Operator",
"Rate": "Rate",
"Reason": "Reason",
"Rule": "Rule",
+ "Seclang": "Seclang",
"Status code": "Status code",
"Type": "Type",
+ "URL Paths": "URL Paths",
+ "User-Agents": "User-Agents",
"Value": "Value",
"Verbose mode": "Verbose mode",
"and": "and",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index a31db89..b961f07 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -84,7 +84,7 @@
},
"cert": {
"Access key": "访问密钥",
- "Access secret": "访问密钥",
+ "Access secret": "访问密钥 Secret",
"Account": "账户",
"Certificate": "证书",
"Certificate copied to clipboard successfully": "证书成功复制到剪贴板",
@@ -92,10 +92,12 @@
"Copy private key": "复制私钥",
"Crypto algorithm": "加密算法",
"Domain expire": "域名到期",
+ "Domain expire refreshed successfully": "域名到期时间刷新成功",
"Download certificate": "下载证书",
"Download private key": "下载私钥",
"Edit Cert": "编辑证书",
"Expire time": "到期时间",
+ "Failed to refresh domain expire": "刷新域名到期时间失败",
"Private key": "私钥",
"Private key copied to clipboard successfully": "私钥成功复制到剪贴板",
"Provider": "提供者",
@@ -134,12 +136,13 @@
"general": {
"Action": "操作",
"Add": "添加",
+ "Added successfully": "添加成功",
"Advanced": "高级",
- "At Any Time": "At Any Time",
+ "At Any Time": "任意时间",
"Back Home": "返回首页",
"Cancel": "取消",
"Certs": "证书",
- "Client IP": "Client IP",
+ "Client IP": "客户端 IP",
"Client ip": "客户端IP",
"Count": "计数",
"Create time": "创建时间",
@@ -147,33 +150,50 @@
"CreatedTime": "创建时间",
"Dashboard": "数据看板",
"Delete": "删除",
+ "Deleted successfully": "删除成功",
"Display name": "显示名称",
+ "Down": "下移",
"Edit": "编辑",
"Edit Record": "编辑日志",
+ "Failed to add": "添加失败",
+ "Failed to delete": "删除失败",
+ "Failed to get data": "获取数据失败",
+ "Failed to save": "保存失败",
+ "Failed to sign out": "退出登录失败",
"Gateway Analytics": "网关统计",
"Go to writable demo site?": "跳转至可写演示站点?",
- "Half A Hour": "Half A Hour",
+ "Half A Hour": "半小时",
"Home": "首页",
"Host": "主机",
"ID": "ID",
"IP Address": "IP地址",
+ "Input IP Addresses": "请输入 IP 地址",
+ "Logged in successfully": "登录成功",
+ "Login Error": "登录失败",
"Method": "方法",
"Name": "名称",
- "No Upgrade": "No Upgrade",
- "Nodes": "Nodes",
+ "No Upgrade": "不升级",
+ "No data": "暂无数据",
+ "Nodes": "节点",
"OK": "确定",
"Other": "其他",
"Owner": "所有者",
"Password": "密码",
"Path": "路径",
+ "Powered by": "技术支持",
"Records": "日志",
- "Refresh": "Refresh",
+ "Refresh": "刷新",
+ "Request Count Over Time": "请求数趋势",
"Restore": "复原",
"Rules": "规则",
"Save": "保存",
"Self": "自己",
+ "Signed out successfully": "已退出登录",
+ "Signing in...": "正在登录...",
"Sites": "站点",
"Sorry, you do not have permission to access this page or logged in status
invalid.": "抱歉,您无权访问该页面或登录状态无效。",
+ "Successfully saved": "保存成功",
+ "Sure to delete {name} ?": "确定删除 {name} ?",
"Tag": "标签",
"This is a read-only demo site!": "这是一个只读演示站点!",
"Toggle sidebar": "折叠/展开侧边栏",
@@ -181,58 +201,63 @@
"Top 10 User-Agents": "前10名 User-Agent",
"Total Request Count": "总请求数",
"Unique IP Count": "独立IP统计",
- "Update time": "创建时间",
- "Upgrade mode": "Upgrade mode",
+ "Up": "上移",
+ "Update time": "更新时间",
+ "Upgrade mode": "升级模式",
"User-Agent": "User-Agent",
- "{total} in total": "{total} in total"
+ "{total} in total": "共 {total} 条"
},
"node": {
- "Edit Node": "Edit Node"
+ "Edit Node": "编辑节点"
},
"rule": {
"Allow": "允许",
"Block": "阻止",
"Block Duration": "阻断时间",
- "Captcha": "Captcha",
- "Compound": "Compound",
+ "Captcha": "验证码",
+ "Compound": "组合",
"Edit Rule": "编辑规则",
"Expression": "表达式",
"Expressions": "表达式",
"IP List": "IP 列表",
"IP Rate Limiting": "IP 速率限制",
- "Logic": "Logic",
+ "IPs": "IP 列表",
+ "Logic": "逻辑",
"Operator": "操作符",
"Rate": "速率",
"Reason": "原因",
- "Rule": "Rule",
+ "Rule": "规则",
+ "Seclang": "Seclang",
"Status code": "状态码",
"Type": "类型",
+ "URL Paths": "URL 路径",
+ "User-Agents": "User-Agent 列表",
"Value": "值",
"Verbose mode": "详细模式",
- "and": "and",
- "begin": "begin",
+ "and": "且",
+ "begin": "以…开头",
"contains": "包含",
"does not contain": "不包含",
"does not equal": "不等于",
"equals": "等于",
"is abroad": "在国外",
- "is in": "包含",
- "is not in": "不包含于",
- "or": "or",
+ "is in": "属于",
+ "is not in": "不属于",
+ "or": "或",
"regex match": "正则表达式匹配"
},
"site": {
- "Alert interval": "Alert interval",
- "Alert providers": "Alert providers",
- "Alert try times": "Alert try times",
+ "Alert interval": "告警间隔",
+ "Alert providers": "告警提供商",
+ "Alert try times": "告警重试次数",
"Casdoor app": "Casdoor应用",
"Challenges": "挑战",
- "Disable verbose": "Disable verbose",
+ "Disable verbose": "关闭详细日志",
"Domain": "域名",
"Edit Site": "编辑站点",
- "Enable alert": "Enable alert",
+ "Enable alert": "启用告警",
"Host": "主机",
- "Hosts": "Hosts",
+ "Hosts": "主机列表",
"Mode": "模式",
"Need redirect": "需要重定向",
"Node": "节点",
@@ -240,7 +265,7 @@
"Other domains": "其他域名",
"Port": "端口",
"Public IP": "公网IP",
- "Rules": "Rules",
+ "Rules": "规则",
"SSL cert": "SSL证书",
"Status": "状态",
"The reverse proxy is not enabled": "反向代理未启用",
diff --git a/web/src/pages/AccountPage.tsx b/web/src/pages/AccountPage.tsx
index 6829dda..6b50ac8 100644
--- a/web/src/pages/AccountPage.tsx
+++ b/web/src/pages/AccountPage.tsx
@@ -46,7 +46,7 @@ export default function AccountPage({account}: {account:
Account}) {
AccountBackend.updateAccount({displayName: displayName, avatar: avatar})
.then(res => {
if (res.status === "ok") {
- Setting.showMessage("success", "Successfully saved");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
window.location.reload();
} else {
Setting.showMessage("error", res.msg);
@@ -64,7 +64,7 @@ export default function AccountPage({account}: {account:
Account}) {
})
.then(res => {
if (res.status === "ok") {
- Setting.showMessage("success", "Successfully saved");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
setPasswordOpen(false);
setCurrentPassword("");
setNewPassword("");
diff --git a/web/src/pages/AuthCallback.tsx b/web/src/pages/AuthCallback.tsx
index 8021ca7..c895def 100644
--- a/web/src/pages/AuthCallback.tsx
+++ b/web/src/pages/AuthCallback.tsx
@@ -13,6 +13,7 @@
// limitations under the License.
import * as React from "react";
+import i18next from "i18next";
import * as Setting from "@/Setting";
import {Result} from "@/components/Result";
@@ -25,7 +26,7 @@ export default function AuthCallback() {
React.useEffect(() => {
Setting.signin().then(res => {
if (res.status === "ok") {
- Setting.showMessage("success", "Logged in successfully");
+ Setting.showMessage("success", i18next.t("general:Logged in
successfully"));
Setting.goToLink(sessionStorage.getItem("from") || "/");
} else {
setMessage(res.msg);
@@ -34,8 +35,8 @@ export default function AuthCallback() {
}, []);
if (message === null) {
- return <PageSpinner tip="Signing in..." />;
+ return <PageSpinner tip={i18next.t("general:Signing in...")} />;
}
- return <Result status="error" title="Login Error" subTitle={message} />;
+ return <Result status="error" title={i18next.t("general:Login Error")}
subTitle={message} />;
}
diff --git a/web/src/pages/CertEditPage.tsx b/web/src/pages/CertEditPage.tsx
index 5e24fca..4c02512 100644
--- a/web/src/pages/CertEditPage.tsx
+++ b/web/src/pages/CertEditPage.tsx
@@ -45,7 +45,7 @@ export default function CertEditPage() {
if (res.status === "ok") {
setCert(res.data);
} else {
- Setting.showMessage("error", `Failed to get cert: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
}, [certName, owner]);
@@ -62,13 +62,13 @@ export default function CertEditPage() {
CertBackend.updateCert(cert.owner, certName, Setting.deepCopy(cert))
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to save: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
save")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Successfully saved");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
navigate(`/certs/${cert.owner}/${cert.name}`);
}
})
- .catch(error => Setting.showMessage("error", `failed to save:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to save")}: ${error}`));
};
if (cert === null) {
diff --git a/web/src/pages/CertListPage.tsx b/web/src/pages/CertListPage.tsx
index 6e29695..2cc49f9 100644
--- a/web/src/pages/CertListPage.tsx
+++ b/web/src/pages/CertListPage.tsx
@@ -63,7 +63,7 @@ export default function CertListPage({account}: {account:
Account}) {
setPage(nextPage);
setPageSize(nextPageSize);
} else {
- Setting.showMessage("error", `Failed to get certs: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
},
@@ -80,39 +80,39 @@ export default function CertListPage({account}: {account:
Account}) {
CertBackend.addCert(newCert(account.name))
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to add: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to add")}:
${res.msg}`);
} else {
- Setting.showMessage("success", "Cert added successfully");
+ Setting.showMessage("success", i18next.t("general:Added
successfully"));
fetchCerts();
}
})
- .catch(error => Setting.showMessage("error", `Cert failed to add:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to add")}: ${error}`));
};
const deleteCert = (cert: Cert) => {
CertBackend.deleteCert(cert)
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to delete: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
delete")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Cert deleted successfully");
+ Setting.showMessage("success", i18next.t("general:Deleted
successfully"));
fetchCerts();
}
})
- .catch(error => Setting.showMessage("error", `Cert failed to delete:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to delete")}: ${error}`));
};
const refreshCert = (cert: Cert) => {
CertBackend.refreshDomainExpire(cert.owner, cert.name)
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to refresh domain expire:
${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("cert:Failed to refresh
domain expire")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Domain expire refresh successfully");
+ Setting.showMessage("success", i18next.t("cert:Domain expire
refreshed successfully"));
fetchCerts();
}
})
- .catch(error => Setting.showMessage("error", `Domain expire failed to
refresh: ${error}`));
+ .catch(error => Setting.showMessage("error", `${i18next.t("cert:Failed
to refresh domain expire")}: ${error}`));
};
const copyButton = (text: string, prefix: RegExp, message: string) => (
@@ -224,7 +224,7 @@ export default function CertListPage({account}: {account:
Account}) {
{i18next.t("general:Edit")}
</Button>
<ConfirmButton
- title={`Sure to delete cert: ${record.name} ?`}
+ title={i18next.t("general:Sure to delete {name}
?").replace("{name}", record.name)}
onConfirm={() => deleteCert(record)}
>
<Button size="sm" variant="destructive">
diff --git a/web/src/pages/ChannelListPage.tsx
b/web/src/pages/ChannelListPage.tsx
index c6e9506..c19762a 100644
--- a/web/src/pages/ChannelListPage.tsx
+++ b/web/src/pages/ChannelListPage.tsx
@@ -221,7 +221,7 @@ export default function ChannelListPage({account}:
{account: Account}) {
{i18next.t("general:Edit")}
</Button>
<ConfirmButton
- title={`${i18next.t("general:Delete")} channel "${record.name}"?`}
+ title={i18next.t("general:Sure to delete {name}
?").replace("{name}", record.name)}
onConfirm={() => deleteChannel(record)}
>
<Button size="sm" variant="destructive">
diff --git a/web/src/pages/NodeEditPage.tsx b/web/src/pages/NodeEditPage.tsx
index b3e8d54..34b25a0 100644
--- a/web/src/pages/NodeEditPage.tsx
+++ b/web/src/pages/NodeEditPage.tsx
@@ -44,7 +44,7 @@ export default function NodeEditPage() {
if (res.status === "ok") {
setNode(res.data);
} else {
- Setting.showMessage("error", `Failed to get node: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
}, [nodeName, owner]);
@@ -61,13 +61,13 @@ export default function NodeEditPage() {
NodeBackend.updateNode(node.owner, nodeName, Setting.deepCopy(node))
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to save: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
save")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Successfully saved");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
navigate(`/nodes/${node.owner}/${node.name}`);
}
})
- .catch(error => Setting.showMessage("error", `Failed to connect to
server: ${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to save")}: ${error}`));
};
if (node === null) {
diff --git a/web/src/pages/NodeListPage.tsx b/web/src/pages/NodeListPage.tsx
index a8b1862..f67d687 100644
--- a/web/src/pages/NodeListPage.tsx
+++ b/web/src/pages/NodeListPage.tsx
@@ -65,26 +65,26 @@ export default function NodeListPage({account}: {account:
Account}) {
NodeBackend.addNode(newNode(account.name))
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to add: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to add")}:
${res.msg}`);
} else {
- Setting.showMessage("success", "Node added successfully");
+ Setting.showMessage("success", i18next.t("general:Added
successfully"));
fetchNodes();
}
})
- .catch(error => Setting.showMessage("error", `Node failed to add:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to add")}: ${error}`));
};
const deleteNode = (node: Node) => {
NodeBackend.deleteNode(node)
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to delete: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
delete")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Node deleted successfully");
+ Setting.showMessage("success", i18next.t("general:Deleted
successfully"));
fetchNodes();
}
})
- .catch(error => Setting.showMessage("error", `Node failed to delete:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to delete")}: ${error}`));
};
if (!authorized) {
@@ -156,7 +156,7 @@ export default function NodeListPage({account}: {account:
Account}) {
{i18next.t("general:Edit")}
</Button>
<ConfirmButton
- title={`Sure to delete node: ${record.name} ?`}
+ title={i18next.t("general:Sure to delete {name}
?").replace("{name}", record.name)}
onConfirm={() => deleteNode(record)}
>
<Button size="sm" variant="destructive">
diff --git a/web/src/pages/RecordEditPage.tsx b/web/src/pages/RecordEditPage.tsx
index 7f59170..7456d31 100644
--- a/web/src/pages/RecordEditPage.tsx
+++ b/web/src/pages/RecordEditPage.tsx
@@ -35,7 +35,7 @@ export default function RecordEditPage() {
if (res.status === "ok") {
setRecord(res.data);
} else {
- Setting.showMessage("error", `Failed to get record: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
}, [id, owner]);
@@ -52,13 +52,13 @@ export default function RecordEditPage() {
RecordBackend.updateRecord(record.owner, id, Setting.deepCopy(record))
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to save: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
save")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Successfully saved");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
navigate(`/records/${record.owner}/${record.id}`);
}
})
- .catch(error => Setting.showMessage("error", `failed to save:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to save")}: ${error}`));
};
if (record === null) {
diff --git a/web/src/pages/RecordListPage.tsx b/web/src/pages/RecordListPage.tsx
index 94f7b44..d04f426 100644
--- a/web/src/pages/RecordListPage.tsx
+++ b/web/src/pages/RecordListPage.tsx
@@ -42,7 +42,7 @@ export default function RecordListPage({account}: {account:
Account}) {
setPage(nextPage);
setPageSize(nextPageSize);
} else {
- Setting.showMessage("error", `Failed to get records: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
},
@@ -67,26 +67,26 @@ export default function RecordListPage({account}: {account:
Account}) {
})
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to add: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to add")}:
${res.msg}`);
} else {
- Setting.showMessage("success", "Record added successfully");
+ Setting.showMessage("success", i18next.t("general:Added
successfully"));
fetchRecords();
}
})
- .catch(error => Setting.showMessage("error", `Record failed to add:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to add")}: ${error}`));
};
const deleteRecord = (record: GatewayRecord) => {
RecordBackend.deleteRecord(record)
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to delete: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
delete")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Record deleted successfully");
+ Setting.showMessage("success", i18next.t("general:Deleted
successfully"));
fetchRecords();
}
})
- .catch(error => Setting.showMessage("error", `Record failed to delete:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to delete")}: ${error}`));
};
const columns: Column<GatewayRecord>[] = [
@@ -160,7 +160,7 @@ export default function RecordListPage({account}: {account:
Account}) {
<Button size="sm" onClick={() =>
navigate(`/records/${record.owner}/${record.id}`)}>
{i18next.t("general:Edit")}
</Button>
- <ConfirmButton title="Sure to delete record?" onConfirm={() =>
deleteRecord(record)}>
+ <ConfirmButton title={i18next.t("general:Sure to delete {name}
?").replace("{name}", String(record.id))} onConfirm={() =>
deleteRecord(record)}>
<Button size="sm" variant="destructive">
{i18next.t("general:Delete")}
</Button>
diff --git a/web/src/pages/RuleEditPage.tsx b/web/src/pages/RuleEditPage.tsx
index 788d3fb..e6b9f36 100644
--- a/web/src/pages/RuleEditPage.tsx
+++ b/web/src/pages/RuleEditPage.tsx
@@ -49,7 +49,7 @@ export default function RuleEditPage() {
if (res.status === "ok") {
setRule(res.data);
} else {
- Setting.showMessage("error", `Failed to get rule: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
}, [owner, ruleName]);
@@ -80,10 +80,10 @@ export default function RuleEditPage() {
RuleBackend.updateRule(owner, ruleName, Setting.deepCopy(rule)).then(res
=> {
if (res.status !== "error") {
- Setting.showMessage("success", "Rule updated successfully");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
navigate(`/rules/${rule.owner}/${rule.name}`);
} else {
- Setting.showMessage("error", `Rule failed to update: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to save")}:
${res.msg}`);
getRule();
}
});
@@ -98,18 +98,18 @@ export default function RuleEditPage() {
const renderExpressions = () => {
switch (rule.type) {
case "WAF":
- return <WafRuleTable title="Seclang" table={rule.expressions}
onUpdateTable={setExpressions} />;
+ return <WafRuleTable title={i18next.t("rule:Seclang")}
table={rule.expressions} onUpdateTable={setExpressions} />;
case "IP":
- return <IpRuleTable title="IPs" table={rule.expressions}
onUpdateTable={setExpressions} />;
+ return <IpRuleTable title={i18next.t("rule:IPs")}
table={rule.expressions} onUpdateTable={setExpressions} />;
case "User-Agent":
return (
- <UaRuleTable title="User-Agents" table={rule.expressions}
onUpdateTable={setExpressions} />
+ <UaRuleTable title={i18next.t("rule:User-Agents")}
table={rule.expressions} onUpdateTable={setExpressions} />
);
case "URL Path":
return (
<UaRuleTable
kind="urlPath"
- title="URL Paths"
+ title={i18next.t("rule:URL Paths")}
table={rule.expressions}
onUpdateTable={setExpressions}
/>
diff --git a/web/src/pages/RuleListPage.tsx b/web/src/pages/RuleListPage.tsx
index 144eacb..2dc28f2 100644
--- a/web/src/pages/RuleListPage.tsx
+++ b/web/src/pages/RuleListPage.tsx
@@ -59,7 +59,7 @@ export default function RuleListPage({account}: {account:
Account}) {
setPage(nextPage);
setPageSize(nextPageSize);
} else {
- Setting.showMessage("error", `Failed to get rules: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
},
@@ -74,9 +74,9 @@ export default function RuleListPage({account}: {account:
Account}) {
const addRule = () => {
RuleBackend.addRule(newRule(account.name)).then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to add: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to add")}:
${res.msg}`);
} else {
- Setting.showMessage("success", "Rule added successfully");
+ Setting.showMessage("success", i18next.t("general:Added
successfully"));
fetchRules();
}
});
@@ -85,9 +85,9 @@ export default function RuleListPage({account}: {account:
Account}) {
const deleteRule = (rule: Rule) => {
RuleBackend.deleteRule(rule).then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to delete: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
delete")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Deleted successfully");
+ Setting.showMessage("success", i18next.t("general:Deleted
successfully"));
fetchRules();
}
});
@@ -182,7 +182,7 @@ export default function RuleListPage({account}: {account:
Account}) {
{i18next.t("general:Edit")}
</Button>
<ConfirmButton
- title={`Sure to delete rule: ${record.name} ?`}
+ title={i18next.t("general:Sure to delete {name}
?").replace("{name}", record.name)}
onConfirm={() => deleteRule(record)}
>
<Button size="sm" variant="destructive">
diff --git a/web/src/pages/SigninPage.tsx b/web/src/pages/SigninPage.tsx
index 65062a8..a35069f 100644
--- a/web/src/pages/SigninPage.tsx
+++ b/web/src/pages/SigninPage.tsx
@@ -76,14 +76,14 @@ export default function SigninPage() {
};
if (loading) {
- return <PageSpinner tip="Signing in..." />;
+ return <PageSpinner tip={i18next.t("general:Signing in...")} />;
}
if (!showSignin) {
return (
<Result
status="warning"
- title="Login Error"
+ title={i18next.t("general:Login Error")}
subTitle={errorMessage || i18next.t("account:Sign in is unavailable")}
/>
);
diff --git a/web/src/pages/SiteEditPage.tsx b/web/src/pages/SiteEditPage.tsx
index dcac8a6..e12725d 100644
--- a/web/src/pages/SiteEditPage.tsx
+++ b/web/src/pages/SiteEditPage.tsx
@@ -62,7 +62,7 @@ export default function SiteEditPage({account}: {account:
Account}) {
if (res.status === "ok") {
setSite(res.data);
} else {
- Setting.showMessage("error", `Failed to get site: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
}, [owner, siteName]);
@@ -113,13 +113,13 @@ export default function SiteEditPage({account}: {account:
Account}) {
SiteBackend.updateSite(site.owner, siteName, Setting.deepCopy(site))
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to save: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
save")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Successfully saved");
+ Setting.showMessage("success", i18next.t("general:Successfully
saved"));
navigate(`/sites/${site.owner}/${site.name}`);
}
})
- .catch(error => Setting.showMessage("error", `failed to save:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to save")}: ${error}`));
};
if (site === null) {
diff --git a/web/src/pages/SiteListPage.tsx b/web/src/pages/SiteListPage.tsx
index 30e77e4..b20b84b 100644
--- a/web/src/pages/SiteListPage.tsx
+++ b/web/src/pages/SiteListPage.tsx
@@ -72,7 +72,7 @@ export default function SiteListPage({account}: {account:
Account}) {
if (res.status === "ok") {
setData(res.data ?? []);
} else {
- Setting.showMessage("error", `Failed to get sites: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to get
data")}: ${res.msg}`);
}
});
}, [account.name]);
@@ -94,26 +94,26 @@ export default function SiteListPage({account}: {account:
Account}) {
SiteBackend.addSite(site)
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to add: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to add")}:
${res.msg}`);
} else {
- Setting.showMessage("success", "Site added successfully");
+ Setting.showMessage("success", i18next.t("general:Added
successfully"));
fetchSites();
}
})
- .catch(error => Setting.showMessage("error", `Site failed to add:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to add")}: ${error}`));
};
const deleteSite = (site: Site) => {
SiteBackend.deleteSite(site)
.then(res => {
if (res.status === "error") {
- Setting.showMessage("error", `Failed to delete: ${res.msg}`);
+ Setting.showMessage("error", `${i18next.t("general:Failed to
delete")}: ${res.msg}`);
} else {
- Setting.showMessage("success", "Site deleted successfully");
+ Setting.showMessage("success", i18next.t("general:Deleted
successfully"));
fetchSites();
}
})
- .catch(error => Setting.showMessage("error", `Site failed to delete:
${error}`));
+ .catch(error => Setting.showMessage("error",
`${i18next.t("general:Failed to delete")}: ${error}`));
};
const columns: Column<Site>[] = [
@@ -290,7 +290,7 @@ export default function SiteListPage({account}: {account:
Account}) {
{i18next.t("general:Edit")}
</Button>
<ConfirmButton
- title={`Sure to delete site: ${record.name} ?`}
+ title={i18next.t("general:Sure to delete {name}
?").replace("{name}", record.name)}
onConfirm={() => deleteSite(record)}
>
<Button size="sm" variant="destructive">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]