收口账号菜单侧栏导航

This commit is contained in:
baiyanyun
2026-06-12 16:33:18 +08:00
parent 81d2797035
commit 6323987a0d
10 changed files with 458 additions and 71 deletions

View File

@@ -13,22 +13,25 @@ import {
Badge, Badge,
Spin, Spin,
Button, Button,
Dropdown,
Modal,
notification, notification,
message, message,
} from "antd"; } from "antd";
import type { MenuProps } from "antd";
import type { PresetStatusColorType } from "antd/es/_util/colors"; import type { PresetStatusColorType } from "antd/es/_util/colors";
import { import {
SettingOutlined, SettingOutlined,
DashboardOutlined, DashboardOutlined,
FolderOutlined,
InfoCircleOutlined,
SafetyOutlined,
FileTextOutlined,
TeamOutlined, TeamOutlined,
ArrowLeftOutlined, ArrowLeftOutlined,
ReloadOutlined, ReloadOutlined,
ApiOutlined,
AppstoreOutlined, AppstoreOutlined,
SkinOutlined,
QuestionCircleOutlined,
SyncOutlined,
LogoutOutlined,
UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { import {
setupService, setupService,
@@ -40,6 +43,7 @@ import {
syncConfigToServer, syncConfigToServer,
normalizeServerHost, normalizeServerHost,
loginAndRegister, loginAndRegister,
logout as authLogout,
} from "./services/core/auth"; } from "./services/core/auth";
import { import {
APP_DISPLAY_NAME, APP_DISPLAY_NAME,
@@ -65,6 +69,14 @@ import { createLogger } from "./services/utils/rendererLog";
import styles from "./styles/components/App.module.css"; import styles from "./styles/components/App.module.css";
import { lightTheme, darkTheme } from "./styles/theme"; import { lightTheme, darkTheme } from "./styles/theme";
import { FEATURES } from "@shared/featureFlags"; import { FEATURES } from "@shared/featureFlags";
import {
ACCOUNT_MENU_KEYS,
PRIMARY_NAV_KEYS,
getAccountMenuAction,
type AccountMenuKey,
type AppTabKey,
type SettingsFocusTarget,
} from "./utils/sidebarNavigation";
// 主题类型 // 主题类型
export type ThemeMode = "light" | "dark" | "system"; export type ThemeMode = "light" | "dark" | "system";
@@ -104,17 +116,7 @@ export function useI18nLang(): I18nContextValue {
} }
// Tab 类型定义(对齐 Tauri 客户端) // Tab 类型定义(对齐 Tauri 客户端)
type TabKey = type TabKey = AppTabKey;
| "client"
| "overview"
| "sessions"
| "mcp"
| "settings"
| "dependencies"
| "permissions"
| "logs"
| "about"
| "model";
// 状态配置(对齐 Tauri 客户端) // 状态配置(对齐 Tauri 客户端)
// 就绪、繁忙使用橙色warning、小点展示 // 就绪、繁忙使用橙色warning、小点展示
@@ -343,10 +345,14 @@ function App() {
// 核心状态 // 核心状态
// ============================================ // ============================================
const [activeTab, setActiveTab] = useState<TabKey>("client"); const [activeTab, setActiveTab] = useState<TabKey>("client");
const [settingsFocusTarget, setSettingsFocusTarget] =
useState<SettingsFocusTarget | null>(null);
const [settingsFocusNonce, setSettingsFocusNonce] = useState(0);
const [sessionsAutoOpen, setSessionsAutoOpen] = useState(false); const [sessionsAutoOpen, setSessionsAutoOpen] = useState(false);
const [webviewActions, setWebviewActions] = const [webviewActions, setWebviewActions] =
useState<WebviewHeaderActions | null>(null); useState<WebviewHeaderActions | null>(null);
const [username, setUsername] = useState<string>(""); const [username, setUsername] = useState<string>("");
const [userAvatar, setUserAvatar] = useState<string>("");
const [onlineStatus, setOnlineStatus] = useState<boolean | null>(null); const [onlineStatus, setOnlineStatus] = useState<boolean | null>(null);
const [agentStatus, setAgentStatus] = useState<string>("idle"); const [agentStatus, setAgentStatus] = useState<string>("idle");
const [services, setServices] = useState<ServiceItem[]>([]); const [services, setServices] = useState<ServiceItem[]>([]);
@@ -432,6 +438,7 @@ function App() {
setUsername( setUsername(
user.displayName || user.username || t("Claw.App.defaultUsername"), user.displayName || user.username || t("Claw.App.defaultUsername"),
); );
setUserAvatar(user.avatar || "");
} }
// 加载在线状态 // 加载在线状态
@@ -452,8 +459,56 @@ function App() {
setUsername( setUsername(
user.displayName || user.username || t("Claw.App.defaultUsername"), user.displayName || user.username || t("Claw.App.defaultUsername"),
); );
setUserAvatar(user.avatar || "");
} else { } else {
setUsername(""); setUsername("");
setUserAvatar("");
}
}, []);
const navigateToSettings = useCallback((focus?: SettingsFocusTarget) => {
if (focus) {
setSettingsFocusTarget(focus);
setSettingsFocusNonce((value) => value + 1);
} else {
setSettingsFocusTarget(null);
}
setActiveTab("settings");
}, []);
const handleCheckUpdate = useCallback(async () => {
setUpdateState((prev) => ({ ...prev, status: "checking" }));
try {
const result = await window.electronAPI?.app?.checkUpdate();
if (!result) {
setUpdateState({ status: "idle" });
return;
}
if (result.alreadyChecking) {
const state = await window.electronAPI?.app?.getUpdateState?.();
if (state) setUpdateState(state);
return;
}
if (result.error) {
message.error(
t("Claw.About.checkFailedWithDetail", { error: result.error }),
);
} else if (!result.hasUpdate) {
message.info(t("Claw.About.alreadyLatest"));
}
const authoritative = await window.electronAPI?.app?.getUpdateState?.();
if (authoritative) {
setUpdateState(authoritative);
} else if (result.error || !result.hasUpdate) {
setUpdateState({ status: "idle" });
}
} catch (error) {
const detail = error instanceof Error ? error.message : String(error);
message.error(t("Claw.About.checkFailedWithDetail", { error: detail }));
setUpdateState({ status: "idle" });
} }
}, []); }, []);
@@ -626,6 +681,71 @@ function App() {
} }
}, []); }, []);
const handleLogout = useCallback(() => {
Modal.confirm({
title: t("Claw.Client.logoutConfirm"),
content: t("Claw.Client.logoutConfirmDetail"),
okText: t("Claw.Client.logout"),
cancelText: t("Claw.Client.cancel"),
okButtonProps: { danger: true },
onOk: async () => {
try {
const toStop = services.filter((svc) => svc.running || !!svc.error);
for (const svc of toStop) {
try {
if (svc.key === "agent") {
await window.electronAPI?.agent.destroy();
} else if (svc.key === "fileServer") {
await window.electronAPI?.fileServer.stop();
} else if (svc.key === "lanproxy") {
await window.electronAPI?.lanproxy.stop();
} else if (svc.key === "mcpProxy") {
await window.electronAPI?.mcp.stop();
} else if (svc.key === "guiServer") {
await window.electronAPI?.guiServer?.stop();
}
} catch (error) {
console.error(`[App] Failed to stop ${svc.label}:`, error);
}
}
await window.electronAPI?.computerServer.stop().catch((error) => {
console.error("[App] Failed to stop computerServer:", error);
});
await authLogout();
await handleAuthChange();
setAuthRefreshTrigger((value) => value + 1);
await pollServicesStatus();
setActiveTab("client");
} catch {
message.error(t("Claw.Client.logoutFailed"));
}
},
});
}, [handleAuthChange, pollServicesStatus, services]);
const handleAccountMenuClick = useCallback(
({ key }: { key: string }) => {
const action = getAccountMenuAction(key as AccountMenuKey);
if ("tab" in action) {
if (action.tab === "settings") {
navigateToSettings(action.settingsFocus);
} else {
setSettingsFocusTarget(null);
setActiveTab(action.tab);
}
return;
}
if (action.command === "checkUpdate") {
handleCheckUpdate();
} else if (action.command === "logout") {
handleLogout();
}
},
[handleCheckUpdate, handleLogout, navigateToSettings],
);
// ============================================ // ============================================
// 逐个启动服务(实时更新状态) // 逐个启动服务(实时更新状态)
// ============================================ // ============================================
@@ -776,6 +896,7 @@ function App() {
user.username || user.username ||
t("Claw.App.defaultUsername"), t("Claw.App.defaultUsername"),
); );
setUserAvatar(user.avatar || "");
} }
setAuthRefreshTrigger((v) => v + 1); setAuthRefreshTrigger((v) => v + 1);
await startServicesSequentially(await getStartupServiceKeys()); await startServicesSequentially(await getStartupServiceKeys());
@@ -1042,64 +1163,66 @@ function App() {
// ============================================ // ============================================
const badge = STATUS_CONFIG[agentStatus] || STATUS_CONFIG.idle; const badge = STATUS_CONFIG[agentStatus] || STATUS_CONFIG.idle;
// ============================================
// 平台检测
// ============================================
const isMacOS = navigator.platform.toUpperCase().includes("MAC");
// ============================================ // ============================================
// 菜单配置(对齐 Tauri 客户端) // 菜单配置(对齐 Tauri 客户端)
// ============================================ // ============================================
const menuItems = useMemo(() => { const menuItems = useMemo(() => {
const items = [ const itemMap = {
{ client: {
key: "client", key: "client",
icon: <DashboardOutlined />, icon: <DashboardOutlined />,
label: t("Claw.Menu.client"), label: t("Claw.Menu.client"),
}, },
{ overview: {
key: "overview", key: "overview",
icon: <AppstoreOutlined />, icon: <AppstoreOutlined />,
label: t("Claw.Menu.overview"), label: t("Claw.Menu.overview"),
}, },
{ sessions: {
key: "sessions", key: "sessions",
icon: <TeamOutlined />, icon: <TeamOutlined />,
label: t("Claw.Menu.session"), label: t("Claw.Menu.session"),
}, },
{ } satisfies Record<
key: "mcp", (typeof PRIMARY_NAV_KEYS)[number],
icon: <ApiOutlined />, NonNullable<MenuProps["items"]>[number]
label: t("Claw.Menu.mcp"), >;
},
{ return PRIMARY_NAV_KEYS.map((key) => itemMap[key]);
}, [i18nLang]);
const accountMenuItems = useMemo<MenuProps["items"]>(() => {
const itemMap = {
settings: {
key: "settings", key: "settings",
icon: <SettingOutlined />, icon: <SettingOutlined />,
label: t("Claw.Menu.settings"), label: t("Claw.AccountMenu.settings"),
}, },
{ appearance: {
key: "dependencies", key: "appearance",
icon: <FolderOutlined />, icon: <SkinOutlined />,
label: t("Claw.Menu.dependencies"), label: t("Claw.AccountMenu.appearance"),
}, },
]; helpFeedback: {
if (isMacOS) { key: "helpFeedback",
items.push({ icon: <QuestionCircleOutlined />,
key: "permissions", label: t("Claw.AccountMenu.helpFeedback"),
icon: <SafetyOutlined />,
label: t("Claw.Menu.authorization"),
});
}
items.push(
{ key: "logs", icon: <FileTextOutlined />, label: t("Claw.Menu.logs") },
{
key: "about",
icon: <InfoCircleOutlined />,
label: t("Claw.Menu.about"),
}, },
); checkUpdate: {
return items; key: "checkUpdate",
}, [isMacOS, i18nLang]); icon: <SyncOutlined />,
label: t("Claw.AccountMenu.checkUpdate"),
},
logout: {
key: "logout",
danger: true,
icon: <LogoutOutlined />,
label: t("Claw.AccountMenu.logout"),
},
} satisfies Record<AccountMenuKey, NonNullable<MenuProps["items"]>[number]>;
return ACCOUNT_MENU_KEYS.map((key) => itemMap[key]);
}, [i18nLang]);
// ============================================ // ============================================
// i18n Context value // i18n Context value
@@ -1310,21 +1433,55 @@ function App() {
className={ className={
// 英文菜单文案通常更长,侧边栏适当加宽以减少截断;其他语言保持默认宽度 // 英文菜单文案通常更长,侧边栏适当加宽以减少截断;其他语言保持默认宽度
i18nLang.toLowerCase().startsWith("en") i18nLang.toLowerCase().startsWith("en")
? "app-sider app-sider-en" ? `app-sider app-sider-en ${styles.siderWithAccount}`
: "app-sider" : `app-sider ${styles.siderWithAccount}`
} }
> >
<Menu <div className={styles.siderNavArea}>
mode="inline" <Menu
inlineIndent={0} mode="inline"
selectedKeys={[activeTab]} inlineIndent={0}
items={menuItems.map((item) => ({ selectedKeys={
key: item.key, PRIMARY_NAV_KEYS.includes(activeTab as any)
icon: item.icon, ? [activeTab]
label: item.label, : []
onClick: () => setActiveTab(item.key as TabKey), }
}))} items={menuItems?.map((item) => ({
/> ...item,
onClick: () => setActiveTab(item?.key as TabKey),
}))}
/>
</div>
<Dropdown
trigger={["click"]}
placement="topLeft"
menu={{
items: accountMenuItems,
onClick: handleAccountMenuClick,
}}
>
<button
type="button"
className={styles.accountButton}
aria-label={t("Claw.AccountMenu.open")}
>
<span className={styles.accountAvatar}>
{userAvatar ? (
<img src={userAvatar} alt="" />
) : (
<UserOutlined />
)}
</span>
<span className={styles.accountText}>
<span className={styles.accountName}>
{username || t("Claw.App.defaultUsername")}
</span>
<span className={styles.accountHint}>
{t("Claw.AccountMenu.hint")}
</span>
</span>
</button>
</Dropdown>
</div> </div>
)} )}
@@ -1370,7 +1527,12 @@ function App() {
/> />
)} )}
{activeTab === "mcp" && <MCPSettings />} {activeTab === "mcp" && <MCPSettings />}
{activeTab === "settings" && <SettingsPage />} {activeTab === "settings" && (
<SettingsPage
focusTarget={settingsFocusTarget}
focusNonce={settingsFocusNonce}
/>
)}
{activeTab === "dependencies" && <DependenciesPage />} {activeTab === "dependencies" && <DependenciesPage />}
{activeTab === "permissions" && <PermissionsPage />} {activeTab === "permissions" && <PermissionsPage />}
{activeTab === "logs" && <LogViewer />} {activeTab === "logs" && <LogViewer />}

View File

@@ -7,7 +7,13 @@
* - 系统设置(主题、开机自启动、日志目录) * - 系统设置(主题、开机自启动、日志目录)
*/ */
import React, { useState, useEffect, useCallback, Suspense } from "react"; import React, {
useState,
useEffect,
useCallback,
Suspense,
useRef,
} from "react";
import { import {
Button, Button,
Form, Form,
@@ -59,6 +65,7 @@ import i18next from "../../services/i18n";
import styles from "../../styles/components/ClientPage.module.css"; import styles from "../../styles/components/ClientPage.module.css";
import { useTheme, useI18nLang, type ThemeMode } from "../../App"; import { useTheme, useI18nLang, type ThemeMode } from "../../App";
import type { SettingsFocusTarget } from "../../utils/sidebarNavigation";
import type { import type {
SandboxCapabilities, SandboxCapabilities,
SandboxPolicy, SandboxPolicy,
@@ -95,9 +102,18 @@ const LOCAL_LANG_OPTIONS = [
{ value: "zh-hk", label: t("Claw.Settings.system.langChineseHK") }, { value: "zh-hk", label: t("Claw.Settings.system.langChineseHK") },
]; ];
export default function SettingsPage() { interface SettingsPageProps {
focusTarget?: SettingsFocusTarget | null;
focusNonce?: number;
}
export default function SettingsPage({
focusTarget,
focusNonce = 0,
}: SettingsPageProps) {
// 主题 // 主题
const { themeMode, setThemeMode } = useTheme(); const { themeMode, setThemeMode } = useTheme();
const appearanceRowRef = useRef<HTMLDivElement | null>(null);
// 服务配置 // 服务配置
const [form] = Form.useForm<Step1Config>(); const [form] = Form.useForm<Step1Config>();
@@ -264,7 +280,24 @@ export default function SettingsPage() {
handleAutolaunchChanged as any, handleAutolaunchChanged as any,
); );
}; };
}, [loadConfig, loadAiConfig, loadSystemSettings, loadSandboxState]); }, [
loadConfig,
loadAiConfig,
loadSystemSettings,
loadSandboxState,
loadGuiMcpState,
]);
useEffect(() => {
if (focusTarget !== "appearance") return;
window.requestAnimationFrame(() => {
appearanceRowRef.current?.scrollIntoView({
behavior: "smooth",
block: "center",
});
appearanceRowRef.current?.focus({ preventScroll: true });
});
}, [focusTarget, focusNonce]);
// ========== 加载语言列表 ========== // ========== 加载语言列表 ==========
useEffect(() => { useEffect(() => {
@@ -786,7 +819,11 @@ export default function SettingsPage() {
</div> </div>
{/* 主题设置 */} {/* 主题设置 */}
<div className={styles.serviceRow}> <div
ref={appearanceRowRef}
className={styles.serviceRow}
tabIndex={-1}
>
<div className={styles.serviceInfo}> <div className={styles.serviceInfo}>
<div> <div>
<span className={styles.serviceLabel}> <span className={styles.serviceLabel}>

View File

@@ -36,6 +36,7 @@ export interface AuthUserInfo {
id?: number; id?: number;
username: string; username: string;
displayName?: string; displayName?: string;
avatar?: string;
token?: string; token?: string;
userId?: string; userId?: string;
email?: string; email?: string;

View File

@@ -35,3 +35,81 @@
align-items: center; align-items: center;
gap: 6px; gap: 6px;
} }
.siderWithAccount {
display: flex;
flex-direction: column;
overflow: hidden;
}
.siderNavArea {
flex: 1;
min-height: 0;
overflow-y: auto;
padding-top: 4px;
}
.accountButton {
width: calc(100% - 16px);
min-height: 54px;
margin: 8px;
padding: 8px;
border: 0;
border-radius: var(--border-radius);
background: transparent;
color: var(--color-text);
display: flex;
align-items: center;
gap: 8px;
text-align: left;
cursor: pointer;
transition: background-color 0.16s ease;
}
.accountButton:hover,
.accountButton:focus-visible {
background: var(--color-bg-section-header);
outline: none;
}
.accountAvatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--color-bg-elevated);
color: var(--color-icon);
border: 1px solid var(--color-border);
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
overflow: hidden;
}
.accountAvatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.accountText {
min-width: 0;
display: flex;
flex-direction: column;
line-height: 1.25;
}
.accountName {
color: var(--color-text);
font-size: 13px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.accountHint {
color: var(--color-text-tertiary);
font-size: 11px;
margin-top: 2px;
}

View File

@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import {
ACCOUNT_MENU_KEYS,
PRIMARY_NAV_KEYS,
getAccountMenuAction,
} from "./sidebarNavigation";
describe("sidebar navigation", () => {
it("keeps only client, overview, and sessions in the primary sidebar", () => {
expect(PRIMARY_NAV_KEYS).toEqual(["client", "overview", "sessions"]);
});
it("maps account menu entries to internal navigation or commands", () => {
expect(ACCOUNT_MENU_KEYS).toEqual([
"settings",
"appearance",
"helpFeedback",
"checkUpdate",
"logout",
]);
expect(getAccountMenuAction("settings")).toEqual({ tab: "settings" });
expect(getAccountMenuAction("appearance")).toEqual({
tab: "settings",
settingsFocus: "appearance",
});
expect(getAccountMenuAction("helpFeedback")).toEqual({ tab: "about" });
expect(getAccountMenuAction("checkUpdate")).toEqual({
command: "checkUpdate",
});
expect(getAccountMenuAction("logout")).toEqual({ command: "logout" });
});
});

View File

@@ -0,0 +1,49 @@
export type AppTabKey =
| "client"
| "overview"
| "sessions"
| "mcp"
| "settings"
| "dependencies"
| "permissions"
| "logs"
| "about"
| "model";
export type SettingsFocusTarget = "appearance";
export type AccountMenuKey =
| "settings"
| "appearance"
| "helpFeedback"
| "checkUpdate"
| "logout";
export type AccountMenuAction =
| { tab: AppTabKey; settingsFocus?: SettingsFocusTarget }
| { command: "checkUpdate" | "logout" };
export const PRIMARY_NAV_KEYS = ["client", "overview", "sessions"] as const;
export const ACCOUNT_MENU_KEYS = [
"settings",
"appearance",
"helpFeedback",
"checkUpdate",
"logout",
] as const;
export function getAccountMenuAction(key: AccountMenuKey): AccountMenuAction {
switch (key) {
case "settings":
return { tab: "settings" };
case "appearance":
return { tab: "settings", settingsFocus: "appearance" };
case "helpFeedback":
return { tab: "about" };
case "checkUpdate":
return { command: "checkUpdate" };
case "logout":
return { command: "logout" };
}
}

View File

@@ -172,6 +172,13 @@
"Claw.Menu.authorization": "Authorization", "Claw.Menu.authorization": "Authorization",
"Claw.Menu.logs": "Logs", "Claw.Menu.logs": "Logs",
"Claw.Menu.about": "About", "Claw.Menu.about": "About",
"Claw.AccountMenu.open": "Open account menu",
"Claw.AccountMenu.hint": "Account & settings",
"Claw.AccountMenu.settings": "Settings",
"Claw.AccountMenu.appearance": "Appearance",
"Claw.AccountMenu.helpFeedback": "Help & Feedback",
"Claw.AccountMenu.checkUpdate": "Check for Updates",
"Claw.AccountMenu.logout": "Log Out",
"Claw.App.ConfigSyncFailed": "Config Sync Failed", "Claw.App.ConfigSyncFailed": "Config Sync Failed",
"Claw.App.ConfigSyncFailedDetail": "Unable to connect to server for latest config. Services not restarted. Please check your network and try again.", "Claw.App.ConfigSyncFailedDetail": "Unable to connect to server for latest config. Services not restarted. Please check your network and try again.",
"Claw.App.Retry": "Retry", "Claw.App.Retry": "Retry",

View File

@@ -172,6 +172,13 @@
"Claw.Menu.authorization": "授权", "Claw.Menu.authorization": "授权",
"Claw.Menu.logs": "日志", "Claw.Menu.logs": "日志",
"Claw.Menu.about": "关于", "Claw.Menu.about": "关于",
"Claw.AccountMenu.open": "打开账户菜单",
"Claw.AccountMenu.hint": "账户与设置",
"Claw.AccountMenu.settings": "设置",
"Claw.AccountMenu.appearance": "外观",
"Claw.AccountMenu.helpFeedback": "帮助与反馈",
"Claw.AccountMenu.checkUpdate": "检查更新",
"Claw.AccountMenu.logout": "退出登录",
"Claw.App.ConfigSyncFailed": "配置同步失败", "Claw.App.ConfigSyncFailed": "配置同步失败",
"Claw.App.ConfigSyncFailedDetail": "无法连接到服务器获取最新配置,服务未重启。请检查网络后重试。", "Claw.App.ConfigSyncFailedDetail": "无法连接到服务器获取最新配置,服务未重启。请检查网络后重试。",
"Claw.App.Retry": "重试", "Claw.App.Retry": "重试",

View File

@@ -172,6 +172,13 @@
"Claw.Menu.authorization": "授權", "Claw.Menu.authorization": "授權",
"Claw.Menu.logs": "日誌", "Claw.Menu.logs": "日誌",
"Claw.Menu.about": "關於", "Claw.Menu.about": "關於",
"Claw.AccountMenu.open": "開啟帳戶選單",
"Claw.AccountMenu.hint": "帳戶與設定",
"Claw.AccountMenu.settings": "設定",
"Claw.AccountMenu.appearance": "外觀",
"Claw.AccountMenu.helpFeedback": "幫助與反饋",
"Claw.AccountMenu.checkUpdate": "檢查更新",
"Claw.AccountMenu.logout": "退出登入",
"Claw.App.ConfigSyncFailed": "配置同步失敗", "Claw.App.ConfigSyncFailed": "配置同步失敗",
"Claw.App.ConfigSyncFailedDetail": "無法連接到服務器獲取最新配置,服務未重啟。請檢查網絡後重試。", "Claw.App.ConfigSyncFailedDetail": "無法連接到服務器獲取最新配置,服務未重啟。請檢查網絡後重試。",
"Claw.App.Retry": "重試", "Claw.App.Retry": "重試",

View File

@@ -172,6 +172,13 @@
"Claw.Menu.authorization": "授權", "Claw.Menu.authorization": "授權",
"Claw.Menu.logs": "日誌", "Claw.Menu.logs": "日誌",
"Claw.Menu.about": "關於", "Claw.Menu.about": "關於",
"Claw.AccountMenu.open": "開啟帳戶選單",
"Claw.AccountMenu.hint": "帳戶與設定",
"Claw.AccountMenu.settings": "設定",
"Claw.AccountMenu.appearance": "外觀",
"Claw.AccountMenu.helpFeedback": "幫助與回饋",
"Claw.AccountMenu.checkUpdate": "檢查更新",
"Claw.AccountMenu.logout": "退出登入",
"Claw.App.ConfigSyncFailed": "配置同步失敗", "Claw.App.ConfigSyncFailed": "配置同步失敗",
"Claw.App.ConfigSyncFailedDetail": "無法連接到服務器獲取最新配置,服務未重啟。請檢查網絡後重試。", "Claw.App.ConfigSyncFailedDetail": "無法連接到服務器獲取最新配置,服務未重啟。請檢查網絡後重試。",
"Claw.App.Retry": "重試", "Claw.App.Retry": "重試",