diff --git a/ui/web/src/components/shared/error-boundary.tsx b/ui/web/src/components/shared/error-boundary.tsx index 7b6b7946..6683b1d7 100644 --- a/ui/web/src/components/shared/error-boundary.tsx +++ b/ui/web/src/components/shared/error-boundary.tsx @@ -1,6 +1,7 @@ import { Component, type ErrorInfo, type ReactNode } from "react"; import { AlertTriangle, RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; +import i18n from "@/i18n"; interface Props { children: ReactNode; @@ -34,10 +35,10 @@ export class ErrorBoundary extends Component { return (
-

Something went wrong rendering this section.

+

{i18n.t("common:errorBoundary")}

); diff --git a/ui/web/src/components/shared/file-browser.tsx b/ui/web/src/components/shared/file-browser.tsx index b9d95957..4f743820 100644 --- a/ui/web/src/components/shared/file-browser.tsx +++ b/ui/web/src/components/shared/file-browser.tsx @@ -25,13 +25,14 @@ function FileActions({ size: number; onDownload?: () => void; }) { + const { t } = useTranslation("common"); return (
{formatSize(size)} {onDownload && ( - )} diff --git a/ui/web/src/components/shared/file-tree.tsx b/ui/web/src/components/shared/file-tree.tsx index d84661df..4c8b1859 100644 --- a/ui/web/src/components/shared/file-tree.tsx +++ b/ui/web/src/components/shared/file-tree.tsx @@ -71,6 +71,7 @@ export function TreeItem({ onLoadMore?: (path: string) => void; showSize?: boolean; }) { + const { t } = useTranslation("common"); const [expanded, setExpanded] = useState(depth === 0); const isActive = activePath === node.path; @@ -87,7 +88,7 @@ export function TreeItem({
)} diff --git a/ui/web/src/components/shared/file-viewers.tsx b/ui/web/src/components/shared/file-viewers.tsx index b892cf42..90cd1d5b 100644 --- a/ui/web/src/components/shared/file-viewers.tsx +++ b/ui/web/src/components/shared/file-viewers.tsx @@ -55,6 +55,7 @@ hljs.registerLanguage("lua", lua); function CodeViewer({ content, language }: { content: string; language: string }) { const { copied, copy } = useClipboard(); + const { t } = useTranslation("common"); const codeRef = useRef(null); const highlighted = useMemo(() => { @@ -74,7 +75,7 @@ function CodeViewer({ content, language }: { content: string; language: string } type="button" onClick={() => copy(content)} className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100" - title="Copy" + title={t("copy")} > {copied ? : } @@ -92,6 +93,7 @@ function CodeViewer({ content, language }: { content: string; language: string } function CsvViewer({ content }: { content: string }) { const { copied, copy } = useClipboard(); + const { t } = useTranslation("common"); const rows = useMemo(() => { return content.split("\n").filter(Boolean).map((line) => { const cols: string[] = []; @@ -115,12 +117,12 @@ function CsvViewer({ content }: { content: string }) { return (
- csv ({body.length} rows) + {t("csvRows", { count: body.length })} @@ -154,6 +156,7 @@ function CsvViewer({ content }: { content: string }) { } function ImageViewer({ path, fetchBlob }: { path: string; fetchBlob: (path: string) => Promise }) { + const { t } = useTranslation("common"); const [src, setSrc] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); @@ -187,7 +190,7 @@ function ImageViewer({ path, fetchBlob }: { path: string; fetchBlob: (path: stri if (error || !src) { return (
- Failed to load image + {t("failedToLoadImage")}
); } diff --git a/ui/web/src/components/shared/markdown-renderer.tsx b/ui/web/src/components/shared/markdown-renderer.tsx index a1d5959b..4330c0ff 100644 --- a/ui/web/src/components/shared/markdown-renderer.tsx +++ b/ui/web/src/components/shared/markdown-renderer.tsx @@ -1,4 +1,5 @@ import { useState, useCallback } from "react"; +import { useTranslation } from "react-i18next"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import rehypeHighlight from "rehype-highlight"; @@ -14,6 +15,7 @@ function CodeBlock({ children?: React.ReactNode; }) { const { copied, copy } = useClipboard(); + const { t } = useTranslation("common"); const text = String(children).replace(/\n$/, ""); const lang = className?.replace("language-", "") ?? ""; @@ -25,7 +27,7 @@ function CodeBlock({ type="button" onClick={() => copy(text)} className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100" - title="Copy code" + title={t("copyCode")} > {copied ? : } diff --git a/ui/web/src/components/shared/sticky-save-bar.tsx b/ui/web/src/components/shared/sticky-save-bar.tsx index aba95566..a09930d8 100644 --- a/ui/web/src/components/shared/sticky-save-bar.tsx +++ b/ui/web/src/components/shared/sticky-save-bar.tsx @@ -1,5 +1,6 @@ import { Save, Check, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { useTranslation } from "react-i18next"; interface StickySaveBarProps { onSave: () => void; @@ -17,10 +18,14 @@ export function StickySaveBar({ saving, saved, error, - label = "Save", - savingLabel = "Saving...", - savedLabel = "Saved", + label, + savingLabel, + savedLabel, }: StickySaveBarProps) { + const { t } = useTranslation("common"); + const resolvedLabel = label ?? t("save"); + const resolvedSavingLabel = savingLabel ?? t("saving"); + const resolvedSavedLabel = savedLabel ?? t("saved"); return (
{error && ( @@ -32,12 +37,12 @@ export function StickySaveBar({
{saved && ( - {savedLabel} + {resolvedSavedLabel} )}
diff --git a/ui/web/src/components/ui/dialog.tsx b/ui/web/src/components/ui/dialog.tsx index 6f373ad6..ce535822 100644 --- a/ui/web/src/components/ui/dialog.tsx +++ b/ui/web/src/components/ui/dialog.tsx @@ -3,6 +3,7 @@ import * as React from "react" import { XIcon } from "lucide-react" import { Dialog as DialogPrimitive } from "radix-ui" +import { useTranslation } from "react-i18next" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" @@ -57,6 +58,7 @@ function DialogContent({ showCloseButton?: boolean overlayTransparent?: boolean }) { + const { t } = useTranslation("common") return ( @@ -77,7 +79,7 @@ function DialogContent({ className="cursor-pointer ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" > - Close + {t("close")} )} @@ -103,6 +105,7 @@ function DialogFooter({ }: React.ComponentProps<"div"> & { showCloseButton?: boolean }) { + const { t } = useTranslation("common") return (
- + )}
diff --git a/ui/web/src/i18n/index.ts b/ui/web/src/i18n/index.ts index 3527dc8d..52407d8b 100644 --- a/ui/web/src/i18n/index.ts +++ b/ui/web/src/i18n/index.ts @@ -33,6 +33,7 @@ import enPendingMessages from "./locales/en/pending-messages.json"; import enContacts from "./locales/en/contacts.json"; import enActivity from "./locales/en/activity.json"; import enApiKeys from "./locales/en/api-keys.json"; +import enCliCredentials from "./locales/en/cli-credentials.json"; // --- VI namespaces --- import viCommon from "./locales/vi/common.json"; @@ -66,6 +67,7 @@ import viPendingMessages from "./locales/vi/pending-messages.json"; import viContacts from "./locales/vi/contacts.json"; import viActivity from "./locales/vi/activity.json"; import viApiKeys from "./locales/vi/api-keys.json"; +import viCliCredentials from "./locales/vi/cli-credentials.json"; // --- ZH namespaces --- import zhCommon from "./locales/zh/common.json"; @@ -99,6 +101,7 @@ import zhPendingMessages from "./locales/zh/pending-messages.json"; import zhContacts from "./locales/zh/contacts.json"; import zhActivity from "./locales/zh/activity.json"; import zhApiKeys from "./locales/zh/api-keys.json"; +import zhCliCredentials from "./locales/zh/cli-credentials.json"; const STORAGE_KEY = "goclaw:language"; @@ -117,6 +120,7 @@ const ns = [ "channels", "providers", "traces", "events", "delegations", "usage", "approvals", "nodes", "logs", "tools", "mcp", "tts", "setup", "memory", "storage", "pending-messages", "contacts", "activity", "api-keys", + "cli-credentials", ] as const; i18n.use(initReactI18next).init({ @@ -131,6 +135,7 @@ i18n.use(initReactI18next).init({ mcp: enMcp, tts: enTts, setup: enSetup, memory: enMemory, storage: enStorage, "pending-messages": enPendingMessages, contacts: enContacts, activity: enActivity, "api-keys": enApiKeys, + "cli-credentials": enCliCredentials, }, vi: { common: viCommon, sidebar: viSidebar, topbar: viTopbar, login: viLogin, @@ -142,6 +147,7 @@ i18n.use(initReactI18next).init({ mcp: viMcp, tts: viTts, setup: viSetup, memory: viMemory, storage: viStorage, "pending-messages": viPendingMessages, contacts: viContacts, activity: viActivity, "api-keys": viApiKeys, + "cli-credentials": viCliCredentials, }, zh: { common: zhCommon, sidebar: zhSidebar, topbar: zhTopbar, login: zhLogin, @@ -153,6 +159,7 @@ i18n.use(initReactI18next).init({ mcp: zhMcp, tts: zhTts, setup: zhSetup, memory: zhMemory, storage: zhStorage, "pending-messages": zhPendingMessages, contacts: zhContacts, activity: zhActivity, "api-keys": zhApiKeys, + "cli-credentials": zhCliCredentials, }, }, ns: [...ns], diff --git a/ui/web/src/i18n/locales/en/agents.json b/ui/web/src/i18n/locales/en/agents.json index 7aa2d95b..f2cc4508 100644 --- a/ui/web/src/i18n/locales/en/agents.json +++ b/ui/web/src/i18n/locales/en/agents.json @@ -172,7 +172,8 @@ "perUser": "per-user", "emptyFile": "empty", "estTokens": "Est {{tokens}} tokens", - "insertContact": "Search contact to insert..." + "insertContact": "Search contact to insert...", + "contentPlaceholder": "File content..." }, "fileEditor": { "editWithAi": "Edit with AI", @@ -194,7 +195,11 @@ "user": "User", "revokeTitle": "Revoke Share", "revokeDesc": "Revoke access for user \"{{userId}}\"? They will no longer be able to use this agent.", - "revoke": "Revoke" + "revoke": "Revoke", + "role.user": "User", + "role.viewer": "Viewer", + "roleDesc.user": "Can use the agent and chat", + "roleDesc.viewer": "Read-only access" }, "links": { "teamsHint": "Agent Links will be removed soon. Please use Agent Teams for multi-agent collaboration — it provides task tracking, shared workspace, and quality control in a simpler setup.", @@ -263,7 +268,10 @@ "continue": "Continue", "retry": "Retry", "retrying": "Retrying...", - "done": "done" + "done": "done", + "fileLabelSOUL": "Soul & Personality", + "fileLabelIDENTITY": "Identity Card", + "fileLabelUSER_PREDEFINED": "Default User Context" }, "configGroups": { "core": "Core", @@ -396,5 +404,14 @@ "formatJson": "Format JSON", "invalidJson": "Invalid JSON syntax" } + }, + "toast": { + "created": "Agent created", + "createFailed": "Failed to create agent", + "updated": "Agent updated", + "updateFailed": "Failed to update agent", + "deleted": "Agent deleted", + "deleteFailed": "Failed to delete agent", + "unknownError": "Unknown error" } } diff --git a/ui/web/src/i18n/locales/en/approvals.json b/ui/web/src/i18n/locales/en/approvals.json index a090932b..ae01456f 100644 --- a/ui/web/src/i18n/locales/en/approvals.json +++ b/ui/web/src/i18n/locales/en/approvals.json @@ -19,5 +19,15 @@ "title": "Deny Execution", "description": "Deny execution request \"{{command}}\" from agent \"{{agentId}}\"?", "confirmLabel": "Deny" + }, + "toast": { + "approved": "Approved", + "approvedAlways": "Command always allowed", + "approvedOnce": "Command approved", + "approveFailed": "Failed to approve", + "denied": "Denied", + "deniedDesc": "Command denied", + "denyFailed": "Failed to deny", + "unknownError": "Unknown error" } } diff --git a/ui/web/src/i18n/locales/en/channels.json b/ui/web/src/i18n/locales/en/channels.json index 6e07a1c8..5e46bcbd 100644 --- a/ui/web/src/i18n/locales/en/channels.json +++ b/ui/web/src/i18n/locales/en/channels.json @@ -154,7 +154,7 @@ } }, "fieldConfig": { - "token": { "label": "Bot Token" }, + "token": { "label": "Bot Token", "help": "From @BotFather" }, "bot_token": { "label": "Bot Token" }, "app_token": { "label": "App-Level Token", "help": "App-Level Token with connections:write scope (required for Socket Mode)" }, "user_token": { "label": "User Token (Optional)", "help": "Optional: User OAuth Token for custom bot identity. Leave empty to use default bot identity." }, @@ -190,7 +190,10 @@ "group_allow_from": { "label": "Group Allowed Users", "help": "Separate allowlist for group senders" }, "native_stream": { "label": "Native Streaming (Agents & AI Apps)", "help": "Use Slack's ChatStreamer API for native streaming. Falls back to edit-in-place if unavailable." }, "debounce_delay": { "label": "Debounce Delay (ms)", "help": "Milliseconds to wait before dispatching rapid messages. Set 0 to disable." }, - "thread_ttl": { "label": "Thread Participation TTL (hours)", "help": "Hours before bot stops auto-replying in threads it participated in. 0 = always require @mention." } + "thread_ttl": { "label": "Thread Participation TTL (hours)", "help": "Hours before bot stops auto-replying in threads it participated in. 0 = always require @mention." }, + "skills": { "label": "Skills Filter", "help": "Limit available skills for this group" }, + "tools": { "label": "Tool Allowlist", "help": "Restrict which tools the agent can use in this group" }, + "system_prompt": { "label": "System Prompt" } }, "fieldOptions": { "block_reply": { @@ -269,6 +272,12 @@ "no": "No" } }, + "wizard": { + "zaloPersonal": { + "createLabel": "Create & Authenticate", + "formBanner": "After creating, you'll authenticate via QR code and configure allowed users." + } + }, "toast": { "created": "Channel created", "createdDesc": "{{name}} has been added", diff --git a/ui/web/src/i18n/locales/en/cli-credentials.json b/ui/web/src/i18n/locales/en/cli-credentials.json new file mode 100644 index 00000000..5a5176a9 --- /dev/null +++ b/ui/web/src/i18n/locales/en/cli-credentials.json @@ -0,0 +1,56 @@ +{ + "title": "CLI Credentials", + "description": "Manage secure CLI binaries available to agents with injected credentials.", + "addCredential": "Add Credential", + "emptyTitle": "No CLI credentials yet", + "emptyDescription": "Add a CLI credential to allow agents to run authenticated CLI commands.", + "columns": { + "binary": "Binary", + "scope": "Scope", + "timeout": "Timeout" + }, + "delete": { + "title": "Delete CLI Credential", + "description": "Remove \"{{name}}\"? Agents that depend on it will lose access.", + "confirm": "Delete" + }, + "form": { + "createTitle": "Add CLI Credential", + "editTitle": "Edit CLI Credential", + "preset": "Preset", + "presetPlaceholder": "Select a preset (optional)", + "noPreset": "No preset (manual)", + "presetHint": "Selecting a preset auto-fills fields and shows required env vars.", + "encryptedHint": "Credentials are encrypted and cannot be displayed. Leave environment variables empty to keep existing values, or re-enter to update.", + "envVars": "Environment Variables", + "binaryName": "Binary Name", + "binaryPath": "Binary Path", + "denyArgs": "Deny Args", + "denyVerbose": "Deny Verbose Args", + "timeout": "Timeout (s)", + "tips": "Tips", + "agentId": "Agent ID", + "agentIdHint": "optional — leave blank for global", + "commaSeparated": "comma-separated", + "binaryNameRequired": "Binary name is required.", + "failedToSave": "Failed to save." + }, + "placeholders": { + "binaryName": "e.g. gh", + "binaryPath": "/usr/local/bin/gh", + "description": "What this CLI tool does", + "denyArgs": "--admin, --force", + "denyVerbose": "-v, --verbose", + "tips": "Usage tips for the agent", + "agentId": "agent-id" + }, + "toast": { + "created": "CLI credential created", + "createdDesc": "\"{{name}}\" added successfully.", + "createFailed": "Failed to create credential", + "updated": "CLI credential updated", + "updateFailed": "Failed to update credential", + "deleted": "CLI credential deleted", + "deleteFailed": "Failed to delete credential" + } +} diff --git a/ui/web/src/i18n/locales/en/common.json b/ui/web/src/i18n/locales/en/common.json index fb6e1127..f9b8aeaa 100644 --- a/ui/web/src/i18n/locales/en/common.json +++ b/ui/web/src/i18n/locales/en/common.json @@ -106,5 +106,14 @@ "customTools": "Custom", "selectOrTypeTools": "Select or type tool names...", "selectOrTypeSkills": "Select or type skill names...", - "typeAndPressEnter": "Type and press Enter..." + "typeAndPressEnter": "Type and press Enter...", + "copyCode": "Copy code", + "copy": "Copy", + "download": "Download", + "errorBoundary": "Something went wrong rendering this section.", + "csvRows": "csv ({{count}} rows)", + "failedToLoadImage": "Failed to load image", + "deleteFolder": "Delete folder", + "deleteFile": "Delete file", + "loadMore": "Load more..." } diff --git a/ui/web/src/i18n/locales/en/config.json b/ui/web/src/i18n/locales/en/config.json index b543ada7..afca46a4 100644 --- a/ui/web/src/i18n/locales/en/config.json +++ b/ui/web/src/i18n/locales/en/config.json @@ -291,5 +291,17 @@ "channels.reasoningStream": "Show Reasoning", "channels.reasoningStreamHint": "Display AI thinking process as a separate message", "channels.reactionLevel": "Reaction Level", - "channels.connectionMode": "Connection Mode" + "channels.connectionMode": "Connection Mode", + "channels.reactionOff": "Off", + "channels.reactionMinimal": "Minimal", + "channels.reactionFull": "Full", + "channels.connectionWebsocket": "WebSocket", + "channels.connectionWebhook": "Webhook", + + "bindings.any": "Any", + + "toast": { + "saved": "Config saved", + "saveFailed": "Failed to save config" + } } diff --git a/ui/web/src/i18n/locales/en/events.json b/ui/web/src/i18n/locales/en/events.json index 0afcaf11..eac2eca6 100644 --- a/ui/web/src/i18n/locales/en/events.json +++ b/ui/web/src/i18n/locales/en/events.json @@ -30,5 +30,8 @@ "detail": { "copy": "Copy", "copied": "Copied" - } + }, + "toolBadge": { "error": "error", "activated": "activated", "ok": "ok" }, + "moreArgs": "...{{count}} more", + "parentLabel": "parent:" } diff --git a/ui/web/src/i18n/locales/en/mcp.json b/ui/web/src/i18n/locales/en/mcp.json index 9ff88426..087560c7 100644 --- a/ui/web/src/i18n/locales/en/mcp.json +++ b/ui/web/src/i18n/locales/en/mcp.json @@ -29,6 +29,7 @@ "name": "Name *", "nameHint": "Lowercase letters, numbers, and hyphens only", "displayName": "Display Name", + "displayNamePlaceholder": "My MCP Server", "transport": "Transport *", "command": "Command *", "args": "Args (comma-separated)", diff --git a/ui/web/src/i18n/locales/en/memory.json b/ui/web/src/i18n/locales/en/memory.json index 6370b407..2eec6732 100644 --- a/ui/web/src/i18n/locales/en/memory.json +++ b/ui/web/src/i18n/locales/en/memory.json @@ -166,6 +166,25 @@ "indexed": "All documents indexed", "failedCreate": "Failed to create document", "failedDelete": "Failed to delete document", - "failedIndex": "Failed to index documents" + "failedIndex": "Failed to index documents", + "entitySaved": "Entity saved", + "entitySaveFailed": "Failed to save entity", + "entityDeleted": "Entity deleted", + "entityDeleteFailed": "Failed to delete entity", + "extractionComplete": "Extraction complete", + "extractionFailed": "Extraction failed", + "traversalFailed": "Traversal failed", + "unknownError": "Unknown error", + "docCreated": "Document created", + "docCreateFailed": "Failed to create document", + "docUpdated": "Document updated", + "docUpdateFailed": "Failed to update document", + "docDeleted": "Document deleted", + "docDeleteFailed": "Failed to delete document", + "docIndexed": "Document indexed", + "docIndexFailed": "Failed to index document", + "allIndexed": "All documents indexed", + "allIndexFailed": "Failed to index all", + "searchFailed": "Search failed" } } diff --git a/ui/web/src/i18n/locales/en/providers.json b/ui/web/src/i18n/locales/en/providers.json index 990d379c..b960fd89 100644 --- a/ui/web/src/i18n/locales/en/providers.json +++ b/ui/web/src/i18n/locales/en/providers.json @@ -108,7 +108,8 @@ "loggedOut": "Logged out", "loggedOutDesc": "OpenAI OAuth token removed", "oauthFailed": "OAuth failed", - "exchangeFailed": "Exchange failed" + "exchangeFailed": "Exchange failed", + "logoutFailed": "Logout failed" }, "cli": { "description": "Claude CLI uses your local", diff --git a/ui/web/src/i18n/locales/vi/agents.json b/ui/web/src/i18n/locales/vi/agents.json index 3d8df3b6..7b6f0b17 100644 --- a/ui/web/src/i18n/locales/vi/agents.json +++ b/ui/web/src/i18n/locales/vi/agents.json @@ -172,7 +172,8 @@ "perUser": "theo người dùng", "emptyFile": "trống", "estTokens": "Ước {{tokens}} token", - "insertContact": "Tìm liên hệ để chèn..." + "insertContact": "Tìm liên hệ để chèn...", + "contentPlaceholder": "Nội dung tệp..." }, "fileEditor": { "editWithAi": "Chỉnh sửa với AI", @@ -194,7 +195,11 @@ "user": "Người dùng", "revokeTitle": "Thu hồi chia sẻ", "revokeDesc": "Thu hồi quyền truy cập của người dùng \"{{userId}}\"? Họ sẽ không thể sử dụng agent này nữa.", - "revoke": "Thu hồi" + "revoke": "Thu hồi", + "role.user": "Người dùng", + "role.viewer": "Người xem", + "roleDesc.user": "Có thể sử dụng agent và trò chuyện", + "roleDesc.viewer": "Chỉ đọc" }, "links": { "teamsHint": "Agent Links sẽ sớm bị gỡ bỏ. Vui lòng sử dụng Agent Teams để cộng tác đa agent — hỗ trợ quản lý nhiệm vụ, workspace chung và kiểm soát chất lượng trong một hệ thống đơn giản hơn.", @@ -263,7 +268,10 @@ "continue": "Tiếp tục", "retry": "Thử lại", "retrying": "Đang thử lại...", - "done": "xong" + "done": "xong", + "fileLabelSOUL": "Linh hồn & Tính cách", + "fileLabelIDENTITY": "Thẻ danh tính", + "fileLabelUSER_PREDEFINED": "Ngữ cảnh người dùng mặc định" }, "configGroups": { "core": "Cốt lõi", @@ -396,5 +404,14 @@ "formatJson": "Định dạng JSON", "invalidJson": "Cú pháp JSON không hợp lệ" } + }, + "toast": { + "created": "Đã tạo agent", + "createFailed": "Không thể tạo agent", + "updated": "Đã cập nhật agent", + "updateFailed": "Không thể cập nhật agent", + "deleted": "Đã xóa agent", + "deleteFailed": "Không thể xóa agent", + "unknownError": "Lỗi không xác định" } } diff --git a/ui/web/src/i18n/locales/vi/approvals.json b/ui/web/src/i18n/locales/vi/approvals.json index b3ee67fb..7684c743 100644 --- a/ui/web/src/i18n/locales/vi/approvals.json +++ b/ui/web/src/i18n/locales/vi/approvals.json @@ -19,5 +19,15 @@ "title": "Từ chối thực thi", "description": "Từ chối yêu cầu thực thi \"{{command}}\" từ agent \"{{agentId}}\"?", "confirmLabel": "Từ chối" + }, + "toast": { + "approved": "Đã phê duyệt", + "approvedAlways": "Lệnh luôn được phép", + "approvedOnce": "Lệnh đã được phê duyệt", + "approveFailed": "Không thể phê duyệt", + "denied": "Đã từ chối", + "deniedDesc": "Lệnh bị từ chối", + "denyFailed": "Không thể từ chối", + "unknownError": "Lỗi không xác định" } } diff --git a/ui/web/src/i18n/locales/vi/channels.json b/ui/web/src/i18n/locales/vi/channels.json index e6a368ae..ac1e172d 100644 --- a/ui/web/src/i18n/locales/vi/channels.json +++ b/ui/web/src/i18n/locales/vi/channels.json @@ -154,7 +154,7 @@ } }, "fieldConfig": { - "token": { "label": "Bot Token" }, + "token": { "label": "Bot Token", "help": "Từ @BotFather" }, "bot_token": { "label": "Bot Token" }, "app_token": { "label": "Token cấp ứng dụng", "help": "Token cấp ứng dụng với scope connections:write (bắt buộc cho Socket Mode)" }, "user_token": { "label": "Token người dùng (Tùy chọn)", "help": "Tùy chọn: Token OAuth người dùng cho danh tính bot tùy chỉnh. Để trống để dùng danh tính bot mặc định." }, @@ -190,7 +190,10 @@ "group_allow_from": { "label": "Người dùng nhóm được phép", "help": "Danh sách cho phép riêng cho người gửi nhóm" }, "native_stream": { "label": "Stream gốc (Agents & AI Apps)", "help": "Dùng API ChatStreamer của Slack cho stream gốc. Quay về chỉnh sửa tại chỗ nếu không khả dụng." }, "debounce_delay": { "label": "Độ trễ debounce (ms)", "help": "Mili giây chờ trước khi gửi tin nhắn nhanh. Đặt 0 để tắt." }, - "thread_ttl": { "label": "TTL tham gia luồng (giờ)", "help": "Số giờ trước khi bot ngừng tự động trả lời trong các luồng đã tham gia. 0 = luôn yêu cầu @đề cập." } + "thread_ttl": { "label": "TTL tham gia luồng (giờ)", "help": "Số giờ trước khi bot ngừng tự động trả lời trong các luồng đã tham gia. 0 = luôn yêu cầu @đề cập." }, + "skills": { "label": "Bộ lọc skill", "help": "Giới hạn skill khả dụng cho nhóm này" }, + "tools": { "label": "Danh sách công cụ được phép", "help": "Giới hạn công cụ agent có thể dùng trong nhóm này" }, + "system_prompt": { "label": "Prompt hệ thống" } }, "fieldOptions": { "block_reply": { @@ -269,6 +272,12 @@ "no": "Không" } }, + "wizard": { + "zaloPersonal": { + "createLabel": "Tạo & Xác thực", + "formBanner": "Sau khi tạo, bạn sẽ xác thực bằng mã QR và cấu hình người dùng được phép." + } + }, "toast": { "created": "Đã tạo channel", "createdDesc": "{{name}} đã được thêm", diff --git a/ui/web/src/i18n/locales/vi/cli-credentials.json b/ui/web/src/i18n/locales/vi/cli-credentials.json new file mode 100644 index 00000000..1cbf5462 --- /dev/null +++ b/ui/web/src/i18n/locales/vi/cli-credentials.json @@ -0,0 +1,56 @@ +{ + "title": "Thông tin CLI", + "description": "Quản lý các CLI binary bảo mật có sẵn cho agent với thông tin xác thực được inject.", + "addCredential": "Thêm thông tin", + "emptyTitle": "Chưa có thông tin CLI nào", + "emptyDescription": "Thêm thông tin CLI để cho phép agent chạy các lệnh CLI đã xác thực.", + "columns": { + "binary": "Binary", + "scope": "Phạm vi", + "timeout": "Thời gian chờ" + }, + "delete": { + "title": "Xóa thông tin CLI", + "description": "Xóa \"{{name}}\"? Các agent phụ thuộc vào nó sẽ mất quyền truy cập.", + "confirm": "Xóa" + }, + "form": { + "createTitle": "Thêm thông tin CLI", + "editTitle": "Sửa thông tin CLI", + "preset": "Mẫu có sẵn", + "presetPlaceholder": "Chọn mẫu (tùy chọn)", + "noPreset": "Không dùng mẫu (nhập tay)", + "presetHint": "Chọn mẫu sẽ tự động điền các trường và hiển thị biến môi trường cần thiết.", + "encryptedHint": "Thông tin xác thực đã được mã hóa và không thể hiển thị. Để trống biến môi trường để giữ giá trị hiện tại, hoặc nhập lại để cập nhật.", + "envVars": "Biến môi trường", + "binaryName": "Tên Binary", + "binaryPath": "Đường dẫn Binary", + "denyArgs": "Args bị chặn", + "denyVerbose": "Verbose Args bị chặn", + "timeout": "Thời gian chờ (s)", + "tips": "Mẹo sử dụng", + "agentId": "ID Agent", + "agentIdHint": "tùy chọn — để trống cho toàn cục", + "commaSeparated": "phân cách bằng dấu phẩy", + "binaryNameRequired": "Tên binary là bắt buộc.", + "failedToSave": "Không thể lưu." + }, + "placeholders": { + "binaryName": "vd: gh", + "binaryPath": "/usr/local/bin/gh", + "description": "CLI tool này dùng để làm gì", + "denyArgs": "--admin, --force", + "denyVerbose": "-v, --verbose", + "tips": "Mẹo sử dụng cho agent", + "agentId": "agent-id" + }, + "toast": { + "created": "Đã tạo thông tin CLI", + "createdDesc": "\"{{name}}\" đã được thêm thành công.", + "createFailed": "Không thể tạo thông tin", + "updated": "Đã cập nhật thông tin CLI", + "updateFailed": "Không thể cập nhật thông tin", + "deleted": "Đã xóa thông tin CLI", + "deleteFailed": "Không thể xóa thông tin" + } +} diff --git a/ui/web/src/i18n/locales/vi/common.json b/ui/web/src/i18n/locales/vi/common.json index 9458a36c..b12f83a9 100644 --- a/ui/web/src/i18n/locales/vi/common.json +++ b/ui/web/src/i18n/locales/vi/common.json @@ -106,5 +106,14 @@ "usingGlobalDefaults": "Đang dùng giá trị mặc định toàn cục từ config.json", "verificationFailed": "Xác minh thất bại", "view": "Xem", - "yes": "Có" + "yes": "Có", + "copyCode": "Sao chép mã", + "copy": "Sao chép", + "download": "Tải xuống", + "errorBoundary": "Đã xảy ra lỗi khi hiển thị phần này.", + "csvRows": "csv ({{count}} dòng)", + "failedToLoadImage": "Không thể tải hình ảnh", + "deleteFolder": "Xóa thư mục", + "deleteFile": "Xóa tệp", + "loadMore": "Tải thêm..." } diff --git a/ui/web/src/i18n/locales/vi/config.json b/ui/web/src/i18n/locales/vi/config.json index d58ae7d2..e05fbc72 100644 --- a/ui/web/src/i18n/locales/vi/config.json +++ b/ui/web/src/i18n/locales/vi/config.json @@ -291,5 +291,17 @@ "channels.reasoningStream": "Hiện suy luận", "channels.reasoningStreamHint": "Hiển thị quá trình suy nghĩ của AI thành tin nhắn riêng", "channels.reactionLevel": "Mức phản ứng", - "channels.connectionMode": "Chế độ kết nối" + "channels.connectionMode": "Chế độ kết nối", + "channels.reactionOff": "Tắt", + "channels.reactionMinimal": "Tối thiểu", + "channels.reactionFull": "Đầy đủ", + "channels.connectionWebsocket": "WebSocket", + "channels.connectionWebhook": "Webhook", + + "bindings.any": "Bất kỳ", + + "toast": { + "saved": "Đã lưu cấu hình", + "saveFailed": "Không thể lưu cấu hình" + } } diff --git a/ui/web/src/i18n/locales/vi/events.json b/ui/web/src/i18n/locales/vi/events.json index 1a8f59bd..897e3c5c 100644 --- a/ui/web/src/i18n/locales/vi/events.json +++ b/ui/web/src/i18n/locales/vi/events.json @@ -30,5 +30,8 @@ "detail": { "copy": "Sao chép", "copied": "Đã sao chép" - } + }, + "toolBadge": { "error": "lỗi", "activated": "đã kích hoạt", "ok": "ok" }, + "moreArgs": "...{{count}} nữa", + "parentLabel": "cha:" } diff --git a/ui/web/src/i18n/locales/vi/mcp.json b/ui/web/src/i18n/locales/vi/mcp.json index 2ae9069b..5f4e83ba 100644 --- a/ui/web/src/i18n/locales/vi/mcp.json +++ b/ui/web/src/i18n/locales/vi/mcp.json @@ -29,6 +29,7 @@ "name": "Tên *", "nameHint": "Chỉ dùng chữ thường, số và dấu gạch ngang", "displayName": "Tên hiển thị", + "displayNamePlaceholder": "Máy chủ MCP của tôi", "transport": "Giao thức *", "command": "Lệnh *", "args": "Tham số (phân cách bằng dấu phẩy)", diff --git a/ui/web/src/i18n/locales/vi/memory.json b/ui/web/src/i18n/locales/vi/memory.json index 7dc0a353..3261cda6 100644 --- a/ui/web/src/i18n/locales/vi/memory.json +++ b/ui/web/src/i18n/locales/vi/memory.json @@ -166,6 +166,25 @@ "indexed": "Đã lập chỉ mục tất cả tài liệu", "failedCreate": "Không thể tạo tài liệu", "failedDelete": "Không thể xóa tài liệu", - "failedIndex": "Không thể lập chỉ mục tài liệu" + "failedIndex": "Không thể lập chỉ mục tài liệu", + "entitySaved": "Đã lưu thực thể", + "entitySaveFailed": "Không thể lưu thực thể", + "entityDeleted": "Đã xóa thực thể", + "entityDeleteFailed": "Không thể xóa thực thể", + "extractionComplete": "Trích xuất hoàn tất", + "extractionFailed": "Trích xuất thất bại", + "traversalFailed": "Duyệt thất bại", + "unknownError": "Lỗi không xác định", + "docCreated": "Đã tạo tài liệu", + "docCreateFailed": "Không thể tạo tài liệu", + "docUpdated": "Đã cập nhật tài liệu", + "docUpdateFailed": "Không thể cập nhật tài liệu", + "docDeleted": "Đã xóa tài liệu", + "docDeleteFailed": "Không thể xóa tài liệu", + "docIndexed": "Đã lập chỉ mục tài liệu", + "docIndexFailed": "Không thể lập chỉ mục tài liệu", + "allIndexed": "Đã lập chỉ mục tất cả tài liệu", + "allIndexFailed": "Không thể lập chỉ mục tất cả", + "searchFailed": "Tìm kiếm thất bại" } } diff --git a/ui/web/src/i18n/locales/vi/providers.json b/ui/web/src/i18n/locales/vi/providers.json index 8aac9019..be935a9c 100644 --- a/ui/web/src/i18n/locales/vi/providers.json +++ b/ui/web/src/i18n/locales/vi/providers.json @@ -108,7 +108,8 @@ "loggedOut": "Đã đăng xuất", "loggedOutDesc": "Đã xóa OAuth token OpenAI", "oauthFailed": "OAuth thất bại", - "exchangeFailed": "Trao đổi thất bại" + "exchangeFailed": "Trao đổi thất bại", + "logoutFailed": "Đăng xuất thất bại" }, "cli": { "description": "Claude CLI sử dụng tệp nhị phân", diff --git a/ui/web/src/i18n/locales/zh/agents.json b/ui/web/src/i18n/locales/zh/agents.json index d97b3a32..9c64547f 100644 --- a/ui/web/src/i18n/locales/zh/agents.json +++ b/ui/web/src/i18n/locales/zh/agents.json @@ -172,7 +172,8 @@ "perUser": "每用户", "emptyFile": "空", "estTokens": "估计 {{tokens}} token", - "insertContact": "搜索联系人以插入..." + "insertContact": "搜索联系人以插入...", + "contentPlaceholder": "文件内容..." }, "fileEditor": { "editWithAi": "AI 编辑", @@ -194,7 +195,11 @@ "user": "用户", "revokeTitle": "撤销共享", "revokeDesc": "撤销用户「{{userId}}」的访问权限?他们将无法再使用此Agent。", - "revoke": "撤销" + "revoke": "撤销", + "role.user": "用户", + "role.viewer": "查看者", + "roleDesc.user": "可以使用代理和聊天", + "roleDesc.viewer": "只读访问" }, "links": { "teamsHint": "Agent Links 即将移除。请使用 Agent Teams 进行多代理协作——它提供任务跟踪、共享工作区和质量控制,且设置更简单。", @@ -263,7 +268,10 @@ "continue": "继续", "retry": "重试", "retrying": "重试中...", - "done": "完成" + "done": "完成", + "fileLabelSOUL": "灵魂与个性", + "fileLabelIDENTITY": "身份卡", + "fileLabelUSER_PREDEFINED": "默认用户上下文" }, "configGroups": { "core": "核心", @@ -396,5 +404,14 @@ "formatJson": "格式化 JSON", "invalidJson": "JSON 语法无效" } + }, + "toast": { + "created": "代理已创建", + "createFailed": "创建代理失败", + "updated": "代理已更新", + "updateFailed": "更新代理失败", + "deleted": "代理已删除", + "deleteFailed": "删除代理失败", + "unknownError": "未知错误" } } diff --git a/ui/web/src/i18n/locales/zh/approvals.json b/ui/web/src/i18n/locales/zh/approvals.json index 4d8d193b..c14c5bb8 100644 --- a/ui/web/src/i18n/locales/zh/approvals.json +++ b/ui/web/src/i18n/locales/zh/approvals.json @@ -19,5 +19,15 @@ "title": "拒绝执行", "description": "拒绝来自Agent 「{{agentId}}」 的 「{{command}}」 执行请求?", "confirmLabel": "拒绝" + }, + "toast": { + "approved": "已批准", + "approvedAlways": "命令始终允许", + "approvedOnce": "命令已批准", + "approveFailed": "批准失败", + "denied": "已拒绝", + "deniedDesc": "命令已拒绝", + "denyFailed": "拒绝失败", + "unknownError": "未知错误" } } diff --git a/ui/web/src/i18n/locales/zh/channels.json b/ui/web/src/i18n/locales/zh/channels.json index 0dcae9b5..bbcd85c2 100644 --- a/ui/web/src/i18n/locales/zh/channels.json +++ b/ui/web/src/i18n/locales/zh/channels.json @@ -154,7 +154,7 @@ } }, "fieldConfig": { - "token": { "label": "Bot Token" }, + "token": { "label": "Bot Token", "help": "来自 @BotFather" }, "bot_token": { "label": "Bot Token" }, "app_token": { "label": "应用级Token", "help": "具有 connections:write 权限的应用级Token(Socket Mode 必需)" }, "user_token": { "label": "用户Token(可选)", "help": "可选:用于自定义Bot身份的用户OAuth Token。留空使用默认Bot身份。" }, @@ -190,7 +190,10 @@ "group_allow_from": { "label": "群组允许的用户", "help": "群组发送者的单独白名单" }, "native_stream": { "label": "原生流式传输 (Agents & AI Apps)", "help": "使用 Slack 的 ChatStreamer API 进行原生流式传输。不可用时回退到原地编辑。" }, "debounce_delay": { "label": "防抖延迟 (ms)", "help": "发送快速消息前等待的毫秒数。设为 0 禁用。" }, - "thread_ttl": { "label": "线程参与 TTL(小时)", "help": "Bot 停止自动回复已参与线程的小时数。0 = 始终需要 @提及。" } + "thread_ttl": { "label": "线程参与 TTL(小时)", "help": "Bot 停止自动回复已参与线程的小时数。0 = 始终需要 @提及。" }, + "skills": { "label": "Skill过滤", "help": "限制此群组可用的Skill" }, + "tools": { "label": "工具白名单", "help": "限制Agent在此群组中可使用的工具" }, + "system_prompt": { "label": "系统提示词" } }, "fieldOptions": { "block_reply": { @@ -269,6 +272,12 @@ "no": "否" } }, + "wizard": { + "zaloPersonal": { + "createLabel": "创建并认证", + "formBanner": "创建后,您将通过二维码进行认证并配置允许的用户。" + } + }, "toast": { "created": "Channel已创建", "createdDesc": "{{name}} 已添加", diff --git a/ui/web/src/i18n/locales/zh/cli-credentials.json b/ui/web/src/i18n/locales/zh/cli-credentials.json new file mode 100644 index 00000000..0e7cdd2b --- /dev/null +++ b/ui/web/src/i18n/locales/zh/cli-credentials.json @@ -0,0 +1,56 @@ +{ + "title": "CLI 凭证", + "description": "管理代理可用的安全 CLI 二进制文件及注入的凭证。", + "addCredential": "添加凭证", + "emptyTitle": "暂无 CLI 凭证", + "emptyDescription": "添加 CLI 凭证以允许代理运行已认证的 CLI 命令。", + "columns": { + "binary": "二进制文件", + "scope": "范围", + "timeout": "超时" + }, + "delete": { + "title": "删除 CLI 凭证", + "description": "删除 \"{{name}}\"?依赖它的代理将失去访问权限。", + "confirm": "删除" + }, + "form": { + "createTitle": "添加 CLI 凭证", + "editTitle": "编辑 CLI 凭证", + "preset": "预设", + "presetPlaceholder": "选择预设(可选)", + "noPreset": "不使用预设(手动)", + "presetHint": "选择预设会自动填充字段并显示所需的环境变量。", + "encryptedHint": "凭证已加密,无法显示。留空环境变量以保留现有值,或重新输入以更新。", + "envVars": "环境变量", + "binaryName": "二进制名称", + "binaryPath": "二进制路径", + "denyArgs": "禁止参数", + "denyVerbose": "禁止详细参数", + "timeout": "超时 (秒)", + "tips": "提示", + "agentId": "代理 ID", + "agentIdHint": "可选 — 留空表示全局", + "commaSeparated": "逗号分隔", + "binaryNameRequired": "二进制名称为必填项。", + "failedToSave": "保存失败。" + }, + "placeholders": { + "binaryName": "例如 gh", + "binaryPath": "/usr/local/bin/gh", + "description": "此 CLI 工具的用途", + "denyArgs": "--admin, --force", + "denyVerbose": "-v, --verbose", + "tips": "给代理的使用提示", + "agentId": "agent-id" + }, + "toast": { + "created": "CLI 凭证已创建", + "createdDesc": "\"{{name}}\" 已成功添加。", + "createFailed": "创建凭证失败", + "updated": "CLI 凭证已更新", + "updateFailed": "更新凭证失败", + "deleted": "CLI 凭证已删除", + "deleteFailed": "删除凭证失败" + } +} diff --git a/ui/web/src/i18n/locales/zh/common.json b/ui/web/src/i18n/locales/zh/common.json index 97f348ab..be6adcb1 100644 --- a/ui/web/src/i18n/locales/zh/common.json +++ b/ui/web/src/i18n/locales/zh/common.json @@ -106,5 +106,14 @@ "usingGlobalDefaults": "使用 config.json 中的全局默认值", "verificationFailed": "验证失败", "view": "查看", - "yes": "是" + "yes": "是", + "copyCode": "复制代码", + "copy": "复制", + "download": "下载", + "errorBoundary": "此部分渲染出错。", + "csvRows": "csv ({{count}} 行)", + "failedToLoadImage": "图片加载失败", + "deleteFolder": "删除文件夹", + "deleteFile": "删除文件", + "loadMore": "加载更多..." } diff --git a/ui/web/src/i18n/locales/zh/config.json b/ui/web/src/i18n/locales/zh/config.json index 045eae5b..df3bde4c 100644 --- a/ui/web/src/i18n/locales/zh/config.json +++ b/ui/web/src/i18n/locales/zh/config.json @@ -291,5 +291,17 @@ "channels.reasoningStream": "显示推理", "channels.reasoningStreamHint": "将AI思考过程显示为单独的消息", "channels.reactionLevel": "反应级别", - "channels.connectionMode": "连接模式" + "channels.connectionMode": "连接模式", + "channels.reactionOff": "关闭", + "channels.reactionMinimal": "最小", + "channels.reactionFull": "完整", + "channels.connectionWebsocket": "WebSocket", + "channels.connectionWebhook": "Webhook", + + "bindings.any": "任意", + + "toast": { + "saved": "配置已保存", + "saveFailed": "保存配置失败" + } } diff --git a/ui/web/src/i18n/locales/zh/events.json b/ui/web/src/i18n/locales/zh/events.json index 3adc4131..580976eb 100644 --- a/ui/web/src/i18n/locales/zh/events.json +++ b/ui/web/src/i18n/locales/zh/events.json @@ -30,5 +30,8 @@ "detail": { "copy": "复制", "copied": "已复制" - } + }, + "toolBadge": { "error": "错误", "activated": "已激活", "ok": "成功" }, + "moreArgs": "...还有{{count}}个", + "parentLabel": "父级:" } diff --git a/ui/web/src/i18n/locales/zh/mcp.json b/ui/web/src/i18n/locales/zh/mcp.json index 07213471..8bd3f5c3 100644 --- a/ui/web/src/i18n/locales/zh/mcp.json +++ b/ui/web/src/i18n/locales/zh/mcp.json @@ -29,6 +29,7 @@ "name": "名称 *", "nameHint": "仅小写字母、数字和连字符", "displayName": "显示名称", + "displayNamePlaceholder": "我的 MCP 服务器", "transport": "传输协议 *", "command": "命令 *", "args": "参数(逗号分隔)", diff --git a/ui/web/src/i18n/locales/zh/memory.json b/ui/web/src/i18n/locales/zh/memory.json index ab1c1474..272ad45c 100644 --- a/ui/web/src/i18n/locales/zh/memory.json +++ b/ui/web/src/i18n/locales/zh/memory.json @@ -166,6 +166,25 @@ "indexed": "所有文档已索引", "failedCreate": "创建文档失败", "failedDelete": "删除文档失败", - "failedIndex": "索引文档失败" + "failedIndex": "索引文档失败", + "entitySaved": "实体已保存", + "entitySaveFailed": "保存实体失败", + "entityDeleted": "实体已删除", + "entityDeleteFailed": "删除实体失败", + "extractionComplete": "提取完成", + "extractionFailed": "提取失败", + "traversalFailed": "遍历失败", + "unknownError": "未知错误", + "docCreated": "文档已创建", + "docCreateFailed": "创建文档失败", + "docUpdated": "文档已更新", + "docUpdateFailed": "更新文档失败", + "docDeleted": "文档已删除", + "docDeleteFailed": "删除文档失败", + "docIndexed": "文档已索引", + "docIndexFailed": "索引文档失败", + "allIndexed": "所有文档已索引", + "allIndexFailed": "索引所有文档失败", + "searchFailed": "搜索失败" } } diff --git a/ui/web/src/i18n/locales/zh/providers.json b/ui/web/src/i18n/locales/zh/providers.json index cf42911e..bb45c52f 100644 --- a/ui/web/src/i18n/locales/zh/providers.json +++ b/ui/web/src/i18n/locales/zh/providers.json @@ -108,7 +108,8 @@ "loggedOut": "已登出", "loggedOutDesc": "OpenAI OAuth 令牌已移除", "oauthFailed": "OAuth 失败", - "exchangeFailed": "交换失败" + "exchangeFailed": "交换失败", + "logoutFailed": "登出失败" }, "cli": { "description": "Claude CLI 使用本地", diff --git a/ui/web/src/pages/agents/agent-detail/agent-shares-tab.tsx b/ui/web/src/pages/agents/agent-detail/agent-shares-tab.tsx index 77b83b41..66b309fc 100644 --- a/ui/web/src/pages/agents/agent-detail/agent-shares-tab.tsx +++ b/ui/web/src/pages/agents/agent-detail/agent-shares-tab.tsx @@ -24,8 +24,8 @@ interface AgentSharesTabProps { } const ROLE_OPTIONS = [ - { value: "user", label: "User", description: "Can use the agent and chat" }, - { value: "viewer", label: "Viewer", description: "Read-only access" }, + { value: "user" }, + { value: "viewer" }, ] as const; function roleBadgeVariant(role: string) { @@ -101,7 +101,7 @@ export function AgentSharesTab({ agentId }: AgentSharesTabProps) { {ROLE_OPTIONS.map((opt) => ( - {opt.label} + {t(`shares.role.${opt.value}`)} ))} diff --git a/ui/web/src/pages/agents/agent-detail/file-sections/file-editor.tsx b/ui/web/src/pages/agents/agent-detail/file-sections/file-editor.tsx index 50395091..1dd89697 100644 --- a/ui/web/src/pages/agents/agent-detail/file-sections/file-editor.tsx +++ b/ui/web/src/pages/agents/agent-detail/file-sections/file-editor.tsx @@ -115,7 +115,7 @@ export function FileEditor({ }} readOnly={!canEdit} className={`flex-1 resize-none font-mono text-sm ${!canEdit ? "opacity-70" : ""}`} - placeholder="File content..." + placeholder={t("files.contentPlaceholder")} /> )}
diff --git a/ui/web/src/pages/agents/hooks/use-agents.ts b/ui/web/src/pages/agents/hooks/use-agents.ts index c5203a53..0d43bda1 100644 --- a/ui/web/src/pages/agents/hooks/use-agents.ts +++ b/ui/web/src/pages/agents/hooks/use-agents.ts @@ -5,6 +5,7 @@ import { useAuthStore } from "@/stores/use-auth-store"; import { Methods } from "@/api/protocol"; import { queryKeys } from "@/lib/query-keys"; import { toast } from "@/stores/use-toast-store"; +import i18n from "@/i18n"; import type { AgentData } from "@/types/agent"; interface AgentInfoWs { @@ -65,10 +66,10 @@ export function useAgents() { try { const res = await http.post("/v1/agents", data); await invalidate(); - toast.success("Agent created", `${data.display_name || data.agent_key || "Agent"} has been added`); + toast.success(i18n.t("agents:toast.created"), `${data.display_name || data.agent_key || "Agent"} has been added`); return res; } catch (err) { - toast.error("Failed to create agent", err instanceof Error ? err.message : "Unknown error"); + toast.error(i18n.t("agents:toast.createFailed"), err instanceof Error ? err.message : i18n.t("agents:toast.unknownError")); throw err; } }, @@ -80,9 +81,9 @@ export function useAgents() { try { await http.put(`/v1/agents/${id}`, data); await invalidate(); - toast.success("Agent updated", `${data.display_name || data.agent_key || "Agent"} has been updated`); + toast.success(i18n.t("agents:toast.updated"), `${data.display_name || data.agent_key || "Agent"} has been updated`); } catch (err) { - toast.error("Failed to update agent", err instanceof Error ? err.message : "Unknown error"); + toast.error(i18n.t("agents:toast.updateFailed"), err instanceof Error ? err.message : i18n.t("agents:toast.unknownError")); throw err; } }, @@ -94,9 +95,9 @@ export function useAgents() { try { await http.delete(`/v1/agents/${id}`); await invalidate(); - toast.success("Agent deleted"); + toast.success(i18n.t("agents:toast.deleted")); } catch (err) { - toast.error("Failed to delete agent", err instanceof Error ? err.message : "Unknown error"); + toast.error(i18n.t("agents:toast.deleteFailed"), err instanceof Error ? err.message : i18n.t("agents:toast.unknownError")); throw err; } }, diff --git a/ui/web/src/pages/agents/summoning-modal.tsx b/ui/web/src/pages/agents/summoning-modal.tsx index a336505b..338df3bb 100644 --- a/ui/web/src/pages/agents/summoning-modal.tsx +++ b/ui/web/src/pages/agents/summoning-modal.tsx @@ -22,9 +22,9 @@ interface SummoningModalProps { } const SUMMONING_FILES = [ - { name: "SOUL.md", label: "Soul & Personality", required: true }, - { name: "IDENTITY.md", label: "Identity Card", required: true }, - { name: "USER_PREDEFINED.md", label: "Default User Context", required: false }, + { name: "SOUL.md", required: true }, + { name: "IDENTITY.md", required: true }, + { name: "USER_PREDEFINED.md", required: false }, ]; export function SummoningModal({ @@ -194,7 +194,7 @@ export function SummoningModal({ {file.name} - {file.label} + {t(`summoning.fileLabel${file.name.replace(".md", "")}`)}
{done && ( prev.filter((a) => a.id !== id)); - toast.success("Approved", always ? "Command always allowed" : "Command approved"); + toast.success(i18n.t("approvals:toast.approved"), always ? i18n.t("approvals:toast.approvedAlways") : i18n.t("approvals:toast.approvedOnce")); } catch (err) { - toast.error("Failed to approve", err instanceof Error ? err.message : "Unknown error"); + toast.error(i18n.t("approvals:toast.approveFailed"), err instanceof Error ? err.message : i18n.t("approvals:toast.unknownError")); throw err; } }, @@ -62,9 +63,9 @@ export function useApprovals() { try { await ws.call(Methods.APPROVALS_DENY, { id }); setPending((prev) => prev.filter((a) => a.id !== id)); - toast.success("Denied", "Command denied"); + toast.success(i18n.t("approvals:toast.denied"), i18n.t("approvals:toast.deniedDesc")); } catch (err) { - toast.error("Failed to deny", err instanceof Error ? err.message : "Unknown error"); + toast.error(i18n.t("approvals:toast.denyFailed"), err instanceof Error ? err.message : i18n.t("approvals:toast.unknownError")); throw err; } }, diff --git a/ui/web/src/pages/channels/channel-instance-form-dialog.tsx b/ui/web/src/pages/channels/channel-instance-form-dialog.tsx index 6d383962..655ad09b 100644 --- a/ui/web/src/pages/channels/channel-instance-form-dialog.tsx +++ b/ui/web/src/pages/channels/channel-instance-form-dialog.tsx @@ -327,7 +327,7 @@ export function ChannelInstanceFormDialog({ {/* Wizard info banner (create mode) */} {hasWizard && wizard?.formBanner && (
-

{wizard.formBanner}

+

{t(wizard.formBanner)}

)} @@ -362,7 +362,7 @@ export function ChannelInstanceFormDialog({ diff --git a/ui/web/src/pages/channels/channel-schemas.ts b/ui/web/src/pages/channels/channel-schemas.ts index e6dfe889..cf724742 100644 --- a/ui/web/src/pages/channels/channel-schemas.ts +++ b/ui/web/src/pages/channels/channel-schemas.ts @@ -179,8 +179,8 @@ export interface WizardConfig { export const wizardConfig: Partial> = { zalo_personal: { steps: ["auth", "config"], - createLabel: "Create & Authenticate", - formBanner: "After creating, you'll authenticate via QR code and configure allowed users.", + createLabel: "wizard.zaloPersonal.createLabel", + formBanner: "wizard.zaloPersonal.formBanner", excludeConfigFields: ["allow_from"], }, }; diff --git a/ui/web/src/pages/cli-credentials/cli-credential-form-dialog.tsx b/ui/web/src/pages/cli-credentials/cli-credential-form-dialog.tsx index 7a9ae8ed..8e39d431 100644 --- a/ui/web/src/pages/cli-credentials/cli-credential-form-dialog.tsx +++ b/ui/web/src/pages/cli-credentials/cli-credential-form-dialog.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; @@ -23,6 +24,9 @@ interface Props { const NONE_PRESET = "__none__"; export function CliCredentialFormDialog({ open, onOpenChange, credential, presets, onSubmit }: Props) { + const { t } = useTranslation("cli-credentials"); + const { t: tc } = useTranslation("common"); + const [selectedPreset, setSelectedPreset] = useState(NONE_PRESET); const [binaryName, setBinaryName] = useState(""); const [binaryPath, setBinaryPath] = useState(""); @@ -82,7 +86,7 @@ export function CliCredentialFormDialog({ open, onOpenChange, credential, preset const handleSubmit = async () => { if (!binaryName.trim()) { - setError("Binary name is required."); + setError(t("form.binaryNameRequired")); return; } setLoading(true); @@ -104,7 +108,7 @@ export function CliCredentialFormDialog({ open, onOpenChange, credential, preset await onSubmit(payload); onOpenChange(false); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to save."); + setError(err instanceof Error ? err.message : t("form.failedToSave")); } finally { setLoading(false); } @@ -112,22 +116,22 @@ export function CliCredentialFormDialog({ open, onOpenChange, credential, preset return ( !loading && onOpenChange(v)}> - + - {isEdit ? "Edit CLI Credential" : "Add CLI Credential"} + {isEdit ? t("form.editTitle") : t("form.createTitle")} -
+
{/* Preset selector — only on create */} {!isEdit && presetEntries.length > 0 && (
- +

- Selecting a preset auto-fills fields and shows required env vars. + {t("form.presetHint")}

)} - {/* Existing credentials indicator (edit mode) */} {isEdit && (

- Credentials are encrypted and cannot be displayed. Leave environment variables empty to keep existing values, or re-enter to update. + {t("form.encryptedHint")}

)} + {/* Env var inputs from preset */} {activePreset && activePreset.env_vars.length > 0 && (
-

Environment Variables

+

{t("form.envVars")}

{activePreset.env_vars.map((ev) => (
- + setBinaryName(e.target.value)} - placeholder="e.g. gh" + placeholder={t("placeholders.binaryName")} className="text-base md:text-sm" />
- + setBinaryPath(e.target.value)} - placeholder="/usr/local/bin/gh" + placeholder={t("placeholders.binaryPath")} className="text-base md:text-sm" />
- +