diff --git a/dist-electron/main/main.js b/dist-electron/main/main.js index a8c04dd..86f81e5 100644 --- a/dist-electron/main/main.js +++ b/dist-electron/main/main.js @@ -1,2 +1,7 @@ -require('bytenode') -require('./main.jsc') \ No newline at end of file +"use strict"; +require("electron"); +require("./main-B0AKNiSn.js"); +require("electron-squirrel-startup"); +require("electron-log"); +require("bytenode"); +require("axios"); diff --git a/dist-electron/preload/preload.js b/dist-electron/preload/preload.js index 956f473..c4f6639 100644 --- a/dist-electron/preload/preload.js +++ b/dist-electron/preload/preload.js @@ -1 +1,137 @@ -"use strict";const r=require("electron");var i=(e=>(e.EXTERNAL_OPEN="external-open",e.APP_SET_FRAMELESS="app:set-frameless",e.APP_LOAD_PAGE="app:load-page",e.TAB_CREATE="tab:create",e.TAB_LIST="tab:list",e.TAB_NAVIGATE="tab:navigate",e.TAB_RELOAD="tab:reload",e.TAB_BACK="tab:back",e.TAB_FORWARD="tab:forward",e.TAB_SWITCH="tab:switch",e.TAB_CLOSE="tab:close",e.LOG_TO_MAIN="log-to-main",e.READ_FILE="read-file",e.INVOKE="ipc:invoke",e.INVOKE_ASYNC="ipc:invokeAsync",e.APP_MINIMIZE="app:minimize",e.APP_MAXIMIZE="app:maximize",e.APP_QUIT="app:quit",e.FILE_READ="file:read",e.FILE_WRITE="file:write",e.GET_WINDOW_ID="get-window-id",e.CUSTOM_EVENT="custom:event",e.TIME_UPDATE="time:update",e.RENDERER_IS_READY="renderer-ready",e.SHOW_CONTEXT_MENU="show-context-menu",e.START_A_DIALOGUE="start-a-dialogue",e.OPEN_WINDOW="open-window",e.LOG_DEBUG="log-debug",e.LOG_INFO="log-info",e.LOG_WARN="log-warn",e.LOG_ERROR="log-error",e.CONFIG_UPDATED="config-updated",e.SET_CONFIG="set-config",e.GET_CONFIG="get-config",e.UPDATE_CONFIG="update-config",e.SET_THEME_MODE="set-theme-mode",e.GET_THEME_MODE="get-theme-mode",e.IS_DARK_THEME="is-dark-theme",e.THEME_MODE_UPDATED="theme-mode-updated",e.EXECUTE_SCRIPT="execute-script",e.TASK_PROGRESS="task:progress",e.TASK_STARTED="task:started",e.TASK_COMPLETED="task:completed",e.OPEN_CHANNEL="open-channel",e.SCRIPT_LIST="script:list",e.SCRIPT_GET="script:get",e.SCRIPT_SAVE="script:save",e.SCRIPT_DELETE="script:delete",e.SCRIPT_TOGGLE="script:toggle",e.SCRIPT_RUN="script:run",e.SCRIPT_RECORD_START="script:record-start",e.SCRIPT_RECORD_STOP="script:record-stop",e.SCRIPT_CODEGEN="script:codegen",e.GATEWAY_RPC="gateway:rpc",e.GATEWAY_EVENT="gateway:event",e.UPDATE_CHECK="update:check",e.UPDATE_DOWNLOAD="update:download",e.UPDATE_INSTALL="update:install",e.UPDATE_VERSION="update:version",e.UPDATE_STATUS_CHANGED="update:status-changed",e))(i||{});const d={versions:process.versions,external:{open:e=>r.ipcRenderer.invoke("external-open",e)},platform:process.platform,windowMinimize:()=>r.ipcRenderer.invoke("window:minimize"),windowMaximize:()=>r.ipcRenderer.invoke("window:maximize"),windowClose:()=>r.ipcRenderer.invoke("window:close"),windowIsMaximized:()=>r.ipcRenderer.invoke("window:isMaximized"),viewIsReady:()=>r.ipcRenderer.send(i.RENDERER_IS_READY),app:{setFrameless:e=>r.ipcRenderer.invoke(i.APP_SET_FRAMELESS,e),loadPage:e=>r.ipcRenderer.invoke(i.APP_LOAD_PAGE,e)},readFile:e=>r.ipcRenderer.invoke(i.READ_FILE,e),invoke:(e,...n)=>r.ipcRenderer.invoke(e,...n),invokeAsync:(e,...n)=>r.ipcRenderer.invoke(e,...n),on:(e,n)=>{const t=(o,...R)=>n(...R);return r.ipcRenderer.on(e,t),()=>r.ipcRenderer.removeListener(e,t)},send:(e,...n)=>r.ipcRenderer.send(e,...n),getCurrentWindowId:()=>r.ipcRenderer.sendSync(i.GET_WINDOW_ID),logger:{debug:(e,...n)=>r.ipcRenderer.send(i.LOG_DEBUG,e,...n),info:(e,...n)=>r.ipcRenderer.send(i.LOG_INFO,e,...n),warn:(e,...n)=>r.ipcRenderer.send(i.LOG_WARN,e,...n),error:(e,...n)=>r.ipcRenderer.send(i.LOG_ERROR,e,...n)},executeScript:e=>r.ipcRenderer.invoke(i.EXECUTE_SCRIPT,e),onTaskProgress:e=>{const n=(t,o)=>e(o);return r.ipcRenderer.on(i.TASK_PROGRESS,n),()=>r.ipcRenderer.removeListener(i.TASK_PROGRESS,n)},onTaskStarted:e=>{const n=(t,o)=>e(o);return r.ipcRenderer.on(i.TASK_STARTED,n),()=>r.ipcRenderer.removeListener(i.TASK_STARTED,n)},onTaskCompleted:e=>{const n=(t,o)=>e(o);return r.ipcRenderer.on(i.TASK_COMPLETED,n),()=>r.ipcRenderer.removeListener(i.TASK_COMPLETED,n)},openChannel:e=>r.ipcRenderer.invoke(i.OPEN_CHANNEL,e),scriptApi:{list:()=>r.ipcRenderer.invoke(i.SCRIPT_LIST),get:e=>r.ipcRenderer.invoke(i.SCRIPT_GET,e),save:e=>r.ipcRenderer.invoke(i.SCRIPT_SAVE,e),delete:e=>r.ipcRenderer.invoke(i.SCRIPT_DELETE,e),toggle:(e,n)=>r.ipcRenderer.invoke(i.SCRIPT_TOGGLE,e,n),run:e=>r.ipcRenderer.invoke(i.SCRIPT_RUN,e),startRecording:e=>r.ipcRenderer.invoke(i.SCRIPT_RECORD_START,e),stopRecording:()=>r.ipcRenderer.invoke(i.SCRIPT_RECORD_STOP),codegen:(e,n)=>r.ipcRenderer.invoke(i.SCRIPT_CODEGEN,e,n)}};r.contextBridge.exposeInMainWorld("api",d); +"use strict"; +const electron = require("electron"); +var IPC_EVENTS = /* @__PURE__ */ ((IPC_EVENTS2) => { + IPC_EVENTS2["EXTERNAL_OPEN"] = "external-open"; + IPC_EVENTS2["APP_SET_FRAMELESS"] = "app:set-frameless"; + IPC_EVENTS2["APP_LOAD_PAGE"] = "app:load-page"; + IPC_EVENTS2["TAB_CREATE"] = "tab:create"; + IPC_EVENTS2["TAB_LIST"] = "tab:list"; + IPC_EVENTS2["TAB_NAVIGATE"] = "tab:navigate"; + IPC_EVENTS2["TAB_RELOAD"] = "tab:reload"; + IPC_EVENTS2["TAB_BACK"] = "tab:back"; + IPC_EVENTS2["TAB_FORWARD"] = "tab:forward"; + IPC_EVENTS2["TAB_SWITCH"] = "tab:switch"; + IPC_EVENTS2["TAB_CLOSE"] = "tab:close"; + IPC_EVENTS2["LOG_TO_MAIN"] = "log-to-main"; + IPC_EVENTS2["READ_FILE"] = "read-file"; + IPC_EVENTS2["INVOKE"] = "ipc:invoke"; + IPC_EVENTS2["INVOKE_ASYNC"] = "ipc:invokeAsync"; + IPC_EVENTS2["APP_MINIMIZE"] = "app:minimize"; + IPC_EVENTS2["APP_MAXIMIZE"] = "app:maximize"; + IPC_EVENTS2["APP_QUIT"] = "app:quit"; + IPC_EVENTS2["FILE_READ"] = "file:read"; + IPC_EVENTS2["FILE_WRITE"] = "file:write"; + IPC_EVENTS2["GET_WINDOW_ID"] = "get-window-id"; + IPC_EVENTS2["CUSTOM_EVENT"] = "custom:event"; + IPC_EVENTS2["TIME_UPDATE"] = "time:update"; + IPC_EVENTS2["RENDERER_IS_READY"] = "renderer-ready"; + IPC_EVENTS2["SHOW_CONTEXT_MENU"] = "show-context-menu"; + IPC_EVENTS2["START_A_DIALOGUE"] = "start-a-dialogue"; + IPC_EVENTS2["OPEN_WINDOW"] = "open-window"; + IPC_EVENTS2["LOG_DEBUG"] = "log-debug"; + IPC_EVENTS2["LOG_INFO"] = "log-info"; + IPC_EVENTS2["LOG_WARN"] = "log-warn"; + IPC_EVENTS2["LOG_ERROR"] = "log-error"; + IPC_EVENTS2["CONFIG_UPDATED"] = "config-updated"; + IPC_EVENTS2["SET_CONFIG"] = "set-config"; + IPC_EVENTS2["GET_CONFIG"] = "get-config"; + IPC_EVENTS2["UPDATE_CONFIG"] = "update-config"; + IPC_EVENTS2["SET_THEME_MODE"] = "set-theme-mode"; + IPC_EVENTS2["GET_THEME_MODE"] = "get-theme-mode"; + IPC_EVENTS2["IS_DARK_THEME"] = "is-dark-theme"; + IPC_EVENTS2["THEME_MODE_UPDATED"] = "theme-mode-updated"; + IPC_EVENTS2["EXECUTE_SCRIPT"] = "execute-script"; + IPC_EVENTS2["TASK_PROGRESS"] = "task:progress"; + IPC_EVENTS2["TASK_STARTED"] = "task:started"; + IPC_EVENTS2["TASK_COMPLETED"] = "task:completed"; + IPC_EVENTS2["OPEN_CHANNEL"] = "open-channel"; + IPC_EVENTS2["SCRIPT_LIST"] = "script:list"; + IPC_EVENTS2["SCRIPT_GET"] = "script:get"; + IPC_EVENTS2["SCRIPT_SAVE"] = "script:save"; + IPC_EVENTS2["SCRIPT_DELETE"] = "script:delete"; + IPC_EVENTS2["SCRIPT_TOGGLE"] = "script:toggle"; + IPC_EVENTS2["SCRIPT_RUN"] = "script:run"; + IPC_EVENTS2["SCRIPT_RECORD_START"] = "script:record-start"; + IPC_EVENTS2["SCRIPT_RECORD_STOP"] = "script:record-stop"; + IPC_EVENTS2["SCRIPT_CODEGEN"] = "script:codegen"; + IPC_EVENTS2["GATEWAY_RPC"] = "gateway:rpc"; + IPC_EVENTS2["GATEWAY_EVENT"] = "gateway:event"; + IPC_EVENTS2["UPDATE_CHECK"] = "update:check"; + IPC_EVENTS2["UPDATE_DOWNLOAD"] = "update:download"; + IPC_EVENTS2["UPDATE_INSTALL"] = "update:install"; + IPC_EVENTS2["UPDATE_VERSION"] = "update:version"; + IPC_EVENTS2["UPDATE_STATUS_CHANGED"] = "update:status-changed"; + return IPC_EVENTS2; +})(IPC_EVENTS || {}); +const api = { + versions: process.versions, + external: { + open: (url) => electron.ipcRenderer.invoke("external-open", url) + }, + platform: process.platform, + windowMinimize: () => electron.ipcRenderer.invoke("window:minimize"), + windowMaximize: () => electron.ipcRenderer.invoke("window:maximize"), + windowClose: () => electron.ipcRenderer.invoke("window:close"), + windowIsMaximized: () => electron.ipcRenderer.invoke("window:isMaximized"), + viewIsReady: () => electron.ipcRenderer.send(IPC_EVENTS.RENDERER_IS_READY), + app: { + setFrameless: (route) => electron.ipcRenderer.invoke(IPC_EVENTS.APP_SET_FRAMELESS, route), + loadPage: (page) => electron.ipcRenderer.invoke(IPC_EVENTS.APP_LOAD_PAGE, page) + }, + // 通过 IPC 调用主进程 + readFile: (filePath) => electron.ipcRenderer.invoke(IPC_EVENTS.READ_FILE, filePath), + // 异步调用(映射为 electron 的 invoke) + invoke: (channel, ...args) => electron.ipcRenderer.invoke(channel, ...args), + // 异步调用(为了兼容老代码) + invokeAsync: (channel, ...args) => electron.ipcRenderer.invoke(channel, ...args), + // 监听主进程消息 + on: (event, callback) => { + const subscription = (_event, ...args) => callback(...args); + electron.ipcRenderer.on(event, subscription); + return () => electron.ipcRenderer.removeListener(event, subscription); + }, + // 发送消息到主进程 + send: (channel, ...args) => electron.ipcRenderer.send(channel, ...args), + // 获取窗口ID + getCurrentWindowId: () => electron.ipcRenderer.sendSync(IPC_EVENTS.GET_WINDOW_ID), + // 发送日志 + logger: { + debug: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_DEBUG, message, ...meta), + info: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_INFO, message, ...meta), + warn: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_WARN, message, ...meta), + error: (message, ...meta) => electron.ipcRenderer.send(IPC_EVENTS.LOG_ERROR, message, ...meta) + }, + // 执行脚本 + executeScript: (params) => electron.ipcRenderer.invoke(IPC_EVENTS.EXECUTE_SCRIPT, params), + // 任务事件 + onTaskProgress: (cb) => { + const subscription = (_event, payload) => cb(payload); + electron.ipcRenderer.on(IPC_EVENTS.TASK_PROGRESS, subscription); + return () => electron.ipcRenderer.removeListener(IPC_EVENTS.TASK_PROGRESS, subscription); + }, + onTaskStarted: (cb) => { + const subscription = (_event, payload) => cb(payload); + electron.ipcRenderer.on(IPC_EVENTS.TASK_STARTED, subscription); + return () => electron.ipcRenderer.removeListener(IPC_EVENTS.TASK_STARTED, subscription); + }, + onTaskCompleted: (cb) => { + const subscription = (_event, payload) => cb(payload); + electron.ipcRenderer.on(IPC_EVENTS.TASK_COMPLETED, subscription); + return () => electron.ipcRenderer.removeListener(IPC_EVENTS.TASK_COMPLETED, subscription); + }, + // 打开渠道 + openChannel: (channels) => electron.ipcRenderer.invoke(IPC_EVENTS.OPEN_CHANNEL, channels), + // 脚本管理 + scriptApi: { + list: () => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_LIST), + get: (id) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_GET, id), + save: (input) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_SAVE, input), + delete: (id) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_DELETE, id), + toggle: (id, enabled) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_TOGGLE, id, enabled), + run: (id) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RUN, id), + startRecording: (url) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RECORD_START, url), + stopRecording: () => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_RECORD_STOP), + codegen: (id, url) => electron.ipcRenderer.invoke(IPC_EVENTS.SCRIPT_CODEGEN, id, url) + } +}; +electron.contextBridge.exposeInMainWorld("api", api); diff --git a/dist/index.html b/dist/index.html index 96432eb..a7a7ccb 100644 --- a/dist/index.html +++ b/dist/index.html @@ -8,7 +8,7 @@ http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: http://8.138.234.141 https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn" /> - + diff --git a/src/App.tsx b/src/App.tsx index ed0af8a..99fd971 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,12 @@ import { useEffect } from 'react'; import { HashRouter } from 'react-router-dom'; +import { useLocale } from './i18n'; import { AppRouter } from './router'; import { initSettingsStore } from './stores'; export default function App() { + useLocale(); + useEffect(() => { void initSettingsStore(); }, []); diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 506c643..df70ea6 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,5 +1,6 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { Book, Clock, Code, Cpu, House, Puzzle, Settings } from 'lucide-react'; +import { useI18n } from '../../i18n'; import { NAV_ITEMS, normalizeWorkspacePath } from '../../router/routes'; const MENU_MARKS: Record = { @@ -15,6 +16,7 @@ const MENU_MARKS: Record = { export default function Sidebar() { const location = useLocation(); const navigate = useNavigate(); + const { t } = useI18n(); const currentId = normalizeWorkspacePath(location.pathname); return ( @@ -53,7 +55,7 @@ export default function Sidebar() { className="mt-[4px] mb-[8px] text-[14px] hover:text-[#2B7FFF]" style={{ color: active ? '#2B7FFF' : '#525866' }} > - {item.label} + {t(item.labelKey)} diff --git a/src/components/layout/TitleBar.tsx b/src/components/layout/TitleBar.tsx index 5a220d3..36d5b7f 100644 --- a/src/components/layout/TitleBar.tsx +++ b/src/components/layout/TitleBar.tsx @@ -1,8 +1,11 @@ +import { useI18n } from '../../i18n'; + type TitleBarProps = { variant?: 'default' | 'light'; }; export default function TitleBar({ variant = 'default' }: TitleBarProps) { + const { t } = useI18n(); const platform = (window as any).api?.platform ?? ''; if (platform === 'linux') return null; @@ -34,7 +37,7 @@ export default function TitleBar({ variant = 'default' }: TitleBarProps) { className={['flex h-full w-11 items-center justify-center hover:bg-[#999] hover:text-white transition-colors', iconColorClass].join( ' ', )} - title="Minimize" + title={t('window.minimize')} onClick={() => { (window as any).api?.windowMinimize?.(); }} @@ -46,7 +49,7 @@ export default function TitleBar({ variant = 'default' }: TitleBarProps) { className={['flex h-full w-11 items-center justify-center hover:bg-[#999] hover:text-white transition-colors', iconColorClass].join( ' ', )} - title="Maximize" + title={t('window.maximize')} onClick={() => { (window as any).api?.windowMaximize?.(); }} @@ -58,7 +61,7 @@ export default function TitleBar({ variant = 'default' }: TitleBarProps) { className={['flex h-full w-11 items-center justify-center hover:bg-[#ff0000] hover:text-white transition-colors', iconColorClass].join( ' ', )} - title="Close" + title={t('window.close')} onClick={() => { (window as any).api?.windowClose?.(); }} diff --git a/src/i18n/constants.ts b/src/i18n/constants.ts index 7df4514..2268c51 100644 --- a/src/i18n/constants.ts +++ b/src/i18n/constants.ts @@ -10,6 +10,6 @@ export const SUPPORTED_LANGUAGES = [ { code: 'ja', label: '日本語' }, ] as const; -export type Namespace = 'common' | 'conversation' | 'setting' | 'window'; +export type Namespace = 'common' | 'conversation' | 'setting' | 'window' | 'sidebar' | 'login'; -export const NAMESPACES = ['common', 'conversation', 'setting', 'window'] as const satisfies readonly Namespace[]; +export const NAMESPACES = ['common', 'conversation', 'setting', 'window', 'sidebar', 'login'] as const satisfies readonly Namespace[]; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index e2beefc..59a3cc9 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,3 +1,4 @@ +import { useSyncExternalStore } from 'react'; import { DEFAULT_LANGUAGE } from '../lib/constants'; import type { LanguageCode } from '../types/runtime'; import type { Namespace } from './constants'; @@ -103,5 +104,25 @@ export function getMessages(locale?: LanguageCode): MessageTree { return i18n.getMessages(locale); } +function subscribeToLocale(listener: () => void): () => void { + return i18n.subscribe(() => { + listener(); + }); +} + +export function useLocale(): LanguageCode { + return useSyncExternalStore(subscribeToLocale, getLocale, getLocale); +} + +export function useI18n() { + const locale = useLocale(); + + return { + locale, + t, + hasMessage, + }; +} + export { SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES, NAMESPACES } from './constants'; export type { LanguageCode, Namespace }; diff --git a/src/i18n/messages.ts b/src/i18n/messages.ts index aa3b25c..812aaae 100644 --- a/src/i18n/messages.ts +++ b/src/i18n/messages.ts @@ -40,6 +40,72 @@ export const messages: I18nMessages = { newConversation: 'New conversation', emptyState: 'No messages yet', }, + sidebar: { + home: 'Home', + knowledge: 'Knowledge', + models: 'Models', + skills: 'Skills', + cron: 'Cron', + scripts: 'Scripts', + settings: 'Settings', + }, + settings: { + menu: { + systemSettings: 'System Settings', + account: 'Account', + general: 'General', + }, + account: { + title: 'Account Settings', + description: 'Manage your account details and sign-in security.', + accountLabel: 'Account', + passwordLabel: 'Login Password', + passwordHelp: 'Used for investor login operations, last login time: {time}', + configured: 'Configured', + changePassword: 'Change Password', + }, + general: { + title: 'Basic Settings', + description: 'Customize the look and feel of the application.', + themeSection: 'Theme Settings', + languageSection: 'Language', + updatesTitle: 'Updates', + currentVersion: 'Current Version', + checkForUpdates: 'Check for Updates', + checkingForUpdates: 'Checking for updates...', + latestVersion: 'You have the latest version', + newVersionAvailable: 'New version available: v{version}', + downloadingVersion: 'Downloading new version... {percent}%', + downloadComplete: 'Download complete, ready to install', + updateError: 'Update error: {error}', + updateHint: 'Check for updates to get the latest features.', + downloadUpdate: 'Download Update', + restartAndInstall: 'Restart and Install', + autoCheckTitle: 'Auto check for updates', + autoCheckDescription: 'Check for updates on startup', + autoDownloadTitle: 'Auto download updates', + autoDownloadDescription: 'Automatically download and install updates', + autoUpdateHint: 'When auto-update is enabled, updates will be downloaded and installed automatically.', + }, + }, + login: { + title: 'Welcome Back', + subtitle: 'A digital teammate on duty 24/7, always ready to help.', + username: 'Username', + password: 'Password', + verificationCode: 'Verification Code', + usernamePlaceholder: 'Enter username', + passwordPlaceholder: 'Enter password', + verificationCodePlaceholder: 'Enter verification code', + usernameRequired: 'Please enter a username', + passwordRequired: 'Please enter a password', + codeRequired: 'Please enter the verification code', + submitFailed: 'Login failed, please try again later', + submit: 'Sign In', + submitting: 'Signing In...', + loadingCaptcha: 'Loading...', + captchaAlt: 'Verification code', + }, }, zh: { app: { @@ -74,6 +140,72 @@ export const messages: I18nMessages = { newConversation: '新建对话', emptyState: '暂无消息', }, + sidebar: { + home: '首页', + knowledge: '知识库', + models: '模型', + skills: '技能', + cron: '定时任务', + scripts: '脚本', + settings: '设置', + }, + settings: { + menu: { + systemSettings: '系统设置', + account: '账号', + general: '通用', + }, + account: { + title: '账号设置', + description: '管理账号信息与登录安全。', + accountLabel: '账号', + passwordLabel: '登录密码', + passwordHelp: '用于投资人登录操作,最近登录时间:{time}', + configured: '已配置', + changePassword: '修改密码', + }, + general: { + title: '基础设置', + description: '自定义应用的外观与使用体验。', + themeSection: '主题设置', + languageSection: '语言', + updatesTitle: '版本更新', + currentVersion: '当前版本', + checkForUpdates: '检查更新', + checkingForUpdates: '正在检查更新...', + latestVersion: '当前已是最新版本', + newVersionAvailable: '发现新版本:v{version}', + downloadingVersion: '正在下载新版本... {percent}%', + downloadComplete: '下载完成,可立即安装', + updateError: '更新失败:{error}', + updateHint: '检查更新以获取最新功能。', + downloadUpdate: '下载更新', + restartAndInstall: '重启并安装', + autoCheckTitle: '自动检查更新', + autoCheckDescription: '应用启动时自动检查更新', + autoDownloadTitle: '自动下载更新', + autoDownloadDescription: '自动下载并安装更新', + autoUpdateHint: '开启自动更新后,更新包会自动下载并安装。', + }, + }, + login: { + title: '欢迎回来', + subtitle: '24 小时在岗,随时待命的数字员工。', + username: '账号', + password: '密码', + verificationCode: '验证码', + usernamePlaceholder: '请输入账号', + passwordPlaceholder: '请输入密码', + verificationCodePlaceholder: '请输入验证码', + usernameRequired: '请输入用户名', + passwordRequired: '请输入密码', + codeRequired: '请输入验证码', + submitFailed: '登录失败,请稍后重试', + submit: '登录', + submitting: '登录中...', + loadingCaptcha: '加载中...', + captchaAlt: '验证码', + }, }, ja: { app: { @@ -82,7 +214,7 @@ export const messages: I18nMessages = { window: { minimize: '最小化', maximize: '最大化', - restore: '復元', + restore: '元に戻す', close: '閉じる', }, dialog: { @@ -92,7 +224,7 @@ export const messages: I18nMessages = { theme: { light: 'ライト', dark: 'ダーク', - system: 'システムに従う', + system: 'システム', }, language: { zh: '中国語', @@ -106,7 +238,73 @@ export const messages: I18nMessages = { }, conversation: { newConversation: '新しい会話', - emptyState: 'メッセージはまだありません', + emptyState: 'まだメッセージがありません', + }, + sidebar: { + home: 'ホーム', + knowledge: 'ナレッジ', + models: 'モデル', + skills: 'スキル', + cron: '定時タスク', + scripts: 'スクリプト', + settings: '設定', + }, + settings: { + menu: { + systemSettings: 'システム設定', + account: 'アカウント', + general: '一般', + }, + account: { + title: 'アカウント設定', + description: 'アカウント情報とサインインの安全性を管理します。', + accountLabel: 'アカウント', + passwordLabel: 'ログインパスワード', + passwordHelp: '投資家ログインに使用します。最終ログイン時刻:{time}', + configured: '設定済み', + changePassword: 'パスワードを変更', + }, + general: { + title: '基本設定', + description: 'アプリの見た目と操作感をカスタマイズします。', + themeSection: 'テーマ設定', + languageSection: '言語', + updatesTitle: 'アップデート', + currentVersion: '現在のバージョン', + checkForUpdates: '更新を確認', + checkingForUpdates: '更新を確認しています...', + latestVersion: '最新バージョンを使用しています', + newVersionAvailable: '新しいバージョンがあります:v{version}', + downloadingVersion: '新しいバージョンをダウンロード中... {percent}%', + downloadComplete: 'ダウンロードが完了しました。インストールできます', + updateError: 'アップデートエラー:{error}', + updateHint: '最新機能を利用するには更新を確認してください。', + downloadUpdate: '更新をダウンロード', + restartAndInstall: '再起動してインストール', + autoCheckTitle: '更新を自動確認', + autoCheckDescription: '起動時に更新を確認します', + autoDownloadTitle: '更新を自動ダウンロード', + autoDownloadDescription: '更新を自動でダウンロードしてインストールします', + autoUpdateHint: '自動更新を有効にすると、更新が自動でダウンロードされてインストールされます。', + }, + }, + login: { + title: 'おかえりなさい', + subtitle: '24時間待機し、いつでも支援できるデジタルチームメイトです。', + username: 'アカウント', + password: 'パスワード', + verificationCode: '認証コード', + usernamePlaceholder: 'アカウントを入力してください', + passwordPlaceholder: 'パスワードを入力してください', + verificationCodePlaceholder: '認証コードを入力してください', + usernameRequired: 'ユーザー名を入力してください', + passwordRequired: 'パスワードを入力してください', + codeRequired: '認証コードを入力してください', + submitFailed: 'ログインに失敗しました。しばらくしてからもう一度お試しください', + submit: 'ログイン', + submitting: 'ログイン中...', + loadingCaptcha: '読み込み中...', + captchaAlt: '認証コード', }, }, }; diff --git a/src/pages/Knowledge/copy.ts b/src/pages/Knowledge/copy.ts index a0b5d8b..29f960e 100644 --- a/src/pages/Knowledge/copy.ts +++ b/src/pages/Knowledge/copy.ts @@ -1,5 +1,4 @@ -import { useEffect, useState } from 'react'; -import { getLocale, i18n } from '../../i18n'; +import { useLocale } from '../../i18n'; import type { LanguageCode } from '../../types/runtime'; type Primitive = string | number; @@ -216,9 +215,7 @@ function createKnowledgeTranslate(locale: LanguageCode): KnowledgeTranslate { } export function useKnowledgeCopy(): KnowledgeTranslate { - const [locale, setLocale] = useState(getLocale()); - - useEffect(() => i18n.subscribe(setLocale), []); + const locale = useLocale(); return createKnowledgeTranslate(locale); } diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index d128dcc..060c68c 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -1,12 +1,13 @@ import { type ChangeEvent, type FormEvent, useEffect, useMemo, useState } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; import { Lock, User } from 'lucide-react'; +import { useLocation, useNavigate } from 'react-router-dom'; import blueLogo from '../../assets/images/login/blue_logo.png'; import loginBackground from '../../assets/images/login/login_bg.png'; import loginIllustration from '../../assets/images/login/logo.png'; import userIcon from '../../assets/images/login/user_icon.png'; +import { useI18n } from '../../i18n'; import TitleBar from '../../components/layout/TitleBar'; import { resolvePostLoginPath } from '../../router/auth'; import { @@ -35,6 +36,7 @@ const credentialFieldInputClass = 'h-full min-w-0 flex-1 border-0 bg-transparent pr-4 text-[14px] text-gray-800 outline-none placeholder:text-[#99A0AE] disabled:cursor-not-allowed dark:text-gray-100 dark:placeholder:text-gray-500'; export default function LoginPage() { + const { t } = useI18n(); const navigate = useNavigate(); const location = useLocation(); const platform = (window as any).api?.platform ?? ''; @@ -72,9 +74,9 @@ export default function LoginPage() { function validate(values: LoginFormValues): FormErrors { const nextErrors: FormErrors = {}; - if (!values.username.trim()) nextErrors.username = '请输入用户名'; - if (!values.password.trim()) nextErrors.password = '请输入密码'; - if (!values.code.trim()) nextErrors.code = '请输入验证码'; + if (!values.username.trim()) nextErrors.username = t('login.usernameRequired'); + if (!values.password.trim()) nextErrors.password = t('login.passwordRequired'); + if (!values.code.trim()) nextErrors.code = t('login.codeRequired'); return nextErrors; } @@ -104,7 +106,7 @@ export default function LoginPage() { navigate(resolvePostLoginPath(location.state as { from?: string } | null), { replace: true }); } catch (error) { setErrors({ - submit: error instanceof Error ? error.message : '登录失败,请稍后重试', + submit: error instanceof Error ? error.message : t('login.submitFailed'), }); refreshCaptcha(false); } finally { @@ -121,7 +123,7 @@ export default function LoginPage() { return (
-
+
- zn-ai + zn-ai
- 欢迎回到登录 + {t('login.title')}
- 24小时在岗,从不打烊的数字员工 + {t('login.subtitle')}
- {errors.username ?
{errors.username}
: null} + {errors.username ?
{errors.username}
: null}
{errors.password ?
{errors.password}
: null}
handleInputChange('code', event)} /> @@ -210,9 +212,13 @@ export default function LoginPage() { onClick={refreshCaptcha} > {captchaUrl ? ( - 验证码 + {t('login.captchaAlt')} ) : ( - 加载中 + {t('login.loadingCaptcha')} )}
@@ -230,7 +236,7 @@ export default function LoginPage() { className="mt-4 w-full rounded-lg bg-blue-600 py-2 text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-blue-300" disabled={submitting} > - {submitting ? '登录中...' : '登录'} + {submitting ? t('login.submitting') : t('login.submit')}
diff --git a/src/pages/Setting/components/AccountSettingsPanel.tsx b/src/pages/Setting/components/AccountSettingsPanel.tsx index 55a11e8..048b1a9 100644 --- a/src/pages/Setting/components/AccountSettingsPanel.tsx +++ b/src/pages/Setting/components/AccountSettingsPanel.tsx @@ -1,3 +1,4 @@ +import { useI18n } from '../../../i18n'; import SectionHeader from './SectionHeader'; import { CheckCircleIcon } from './SettingIcons'; @@ -5,40 +6,42 @@ const ACCOUNT_ID = '1234567890'; const LAST_LOGIN_TIME = '2022-11-09 16:24:30'; export default function AccountSettingsPanel() { + const { t } = useI18n(); + return (
-
-
- Account +
+
+ {t('settings.account.accountLabel')}
{ACCOUNT_ID}
-
-
- Login Password +
+
+ {t('settings.account.passwordLabel')}
-
- Used for investor login operations, last login time: {LAST_LOGIN_TIME} +
+ {t('settings.account.passwordHelp', { time: LAST_LOGIN_TIME })}
-
- - - Configured +
+ + + {t('settings.account.configured')}
diff --git a/src/pages/Setting/components/GeneralSettingsPanel.tsx b/src/pages/Setting/components/GeneralSettingsPanel.tsx index 3cd5668..d61acdd 100644 --- a/src/pages/Setting/components/GeneralSettingsPanel.tsx +++ b/src/pages/Setting/components/GeneralSettingsPanel.tsx @@ -1,4 +1,4 @@ -import { t } from '../../../i18n'; +import { useI18n } from '../../../i18n'; import { SUPPORTED_LANGUAGE_CODES } from '../../../i18n/constants'; import type { LanguageCode, ThemeMode } from '../../../types/runtime'; import type { SettingUpdateState } from '../useSettingUpdateState'; @@ -24,22 +24,28 @@ const THEME_OPTIONS: Array<{ { value: 'system', icon: ComputerIcon, labelPath: 'theme.system' }, ]; -function getUpdateStatusText(updateState: SettingUpdateState) { +function getUpdateStatusText(t: ReturnType['t'], updateState: SettingUpdateState) { switch (updateState.status) { case 'checking': - return 'Checking for updates...'; + return t('settings.general.checkingForUpdates'); case 'not-available': - return 'You have the latest version'; + return t('settings.general.latestVersion'); case 'available': - return `New version available: v${updateState.updateInfo?.version ?? ''}`; + return t('settings.general.newVersionAvailable', { + version: updateState.updateInfo?.version ?? '', + }); case 'downloading': - return `Downloading new version... ${Math.round(updateState.progress?.percent ?? 0)}%`; + return t('settings.general.downloadingVersion', { + percent: Math.round(updateState.progress?.percent ?? 0), + }); case 'downloaded': - return 'Download complete, ready to install'; + return t('settings.general.downloadComplete'); case 'error': - return `Update error: ${updateState.error ?? 'Unknown error'}`; + return t('settings.general.updateError', { + error: updateState.error ?? t('common.unknownError'), + }); default: - return 'Check for updates to get latest features'; + return t('settings.general.updateHint'); } } @@ -50,16 +56,18 @@ export default function GeneralSettingsPanel({ onLanguageChange, updateState, }: GeneralSettingsPanelProps) { + const { t } = useI18n(); + return (
-
-
- Theme Settings +
+
+ {t('settings.general.themeSection')}
@@ -74,13 +82,13 @@ export default function GeneralSettingsPanel({ void onThemeChange(value); }} className={[ - 'px-5 py-1.5 rounded-full border text-[14px] font-medium transition-all duration-200 flex items-center gap-2', + 'flex items-center gap-2 rounded-full border px-5 py-1.5 text-[14px] font-medium transition-all duration-200', active - ? 'bg-blue-50 dark:bg-blue-900/20 border-blue-500 dark:border-blue-400 text-blue-700 dark:text-blue-300' - : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700', + ? 'border-blue-500 bg-blue-50 text-blue-700 dark:border-blue-400 dark:bg-blue-900/20 dark:text-blue-300' + : 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700', ].join(' ')} > - + {t(labelPath)} ); @@ -89,11 +97,11 @@ export default function GeneralSettingsPanel({
-
-
- Language +
+
+ {t('settings.general.languageSection')}
-
+
{SUPPORTED_LANGUAGE_CODES.map((code) => { const active = language === code; @@ -105,10 +113,10 @@ export default function GeneralSettingsPanel({ void onLanguageChange(code); }} className={[ - 'px-5 py-1.5 rounded-full border text-[14px] font-medium transition-all duration-200 flex items-center gap-2', + 'flex items-center gap-2 rounded-full border px-5 py-1.5 text-[14px] font-medium transition-all duration-200', active - ? 'bg-blue-50 dark:bg-blue-900/20 border-blue-500 dark:border-blue-400 text-blue-700 dark:text-blue-300' - : 'bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700', + ? 'border-blue-500 bg-blue-50 text-blue-700 dark:border-blue-400 dark:bg-blue-900/20 dark:text-blue-300' + : 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700', ].join(' ')} > {t(`language.${code}`)} @@ -119,11 +127,15 @@ export default function GeneralSettingsPanel({
-
Updates
+
+ {t('settings.general.updatesTitle')} +
-
+
-
Current Version
+
+ {t('settings.general.currentVersion')} +
v{updateState.currentVersion}
@@ -131,31 +143,29 @@ export default function GeneralSettingsPanel({
-
+
- {getUpdateStatusText(updateState)} + {getUpdateStatusText(t, updateState)}
@@ -165,9 +175,9 @@ export default function GeneralSettingsPanel({ onClick={() => { void updateState.downloadUpdate(); }} - className="px-[14px] py-[8px] rounded-[8px] bg-[#2B7FFF] text-white text-[14px] font-medium hover:bg-[#1F6AE5] transition-colors" + className="rounded-[8px] bg-[#2B7FFF] px-[14px] py-[8px] text-[14px] font-medium text-white transition-colors hover:bg-[#1F6AE5]" > - Download Update + {t('settings.general.downloadUpdate')} ) : updateState.status === 'downloaded' ? ( ) : ( )}
-
- When auto-update is enabled, updates will be downloaded and installed automatically. +
+ {t('settings.general.autoUpdateHint')}
-
+
-
- Auto check for updates +
+ {t('settings.general.autoCheckTitle')}
- Check for updates on startup + {t('settings.general.autoCheckDescription')}
@@ -219,13 +229,13 @@ export default function GeneralSettingsPanel({ />
-
+
-
- Auto download updates +
+ {t('settings.general.autoDownloadTitle')}
- Automatically download and install updates + {t('settings.general.autoDownloadDescription')}
diff --git a/src/pages/Setting/components/SettingMenu.tsx b/src/pages/Setting/components/SettingMenu.tsx index 43ed510..c116896 100644 --- a/src/pages/Setting/components/SettingMenu.tsx +++ b/src/pages/Setting/components/SettingMenu.tsx @@ -1,3 +1,4 @@ +import { useI18n } from '../../../i18n'; import { SettingsIcon, UserIcon } from './SettingIcons'; export type SettingView = 'account' | 'general'; @@ -9,27 +10,31 @@ type SettingMenuProps = { const MENU_ITEMS: Array<{ id: SettingView; - label: string; + labelKey: 'settings.menu.account' | 'settings.menu.general'; Icon: typeof UserIcon; }> = [ { id: 'account', - label: 'Account', + labelKey: 'settings.menu.account', Icon: UserIcon, }, { id: 'general', - label: 'General', + labelKey: 'settings.menu.general', Icon: SettingsIcon, }, ]; export default function SettingMenu({ currentView, onChange }: SettingMenuProps) { - return ( -