diff --git a/bun.lock b/bun.lock index 0152c93a..68660521 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "opcode", @@ -33,11 +34,14 @@ "diff": "^8.0.2", "framer-motion": "^12.0.0-alpha.1", "html2canvas": "^1.4.1", + "i18next": "^25.7.3", + "i18next-browser-languagedetector": "^8.2.0", "lucide-react": "^0.468.0", "posthog-js": "^1.258.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", + "react-i18next": "^16.5.1", "react-markdown": "^9.0.3", "react-syntax-highlighter": "^15.6.1", "recharts": "^2.14.1", @@ -58,6 +62,10 @@ "typescript": "~5.6.2", "vite": "^6.0.3", }, + "optionalDependencies": { + "@esbuild/linux-x64": "^0.25.6", + "@rollup/rollup-linux-x64-gnu": "^4.45.1", + }, }, }, "trustedDependencies": [ @@ -139,7 +147,7 @@ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="], @@ -357,7 +365,7 @@ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.43.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.54.0", "", { "os": "linux", "cpu": "x64" }, "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ=="], "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ=="], @@ -677,12 +685,18 @@ "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + "html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="], + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="], + "i18next": ["i18next@25.7.3", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA=="], + + "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="], + "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], @@ -891,6 +905,8 @@ "react-hook-form": ["react-hook-form@7.58.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-Lml/KZYEEFfPhUVgE0RdCVpnC4yhW+PndRhbiTtdvSlQTL8IfVR+iQkBjLIvmmc6+GGoVeM11z37ktKFPAb0FA=="], + "react-i18next": ["react-i18next@16.5.1", "", { "dependencies": { "@babel/runtime": "^7.28.4", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-Hks6UIRZWW4c+qDAnx1csVsCGYeIR4MoBGQgJ+NUoNnO6qLxXuf8zu0xdcinyXUORgGzCdRsexxO1Xzv3sTdnw=="], + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], "react-markdown": ["react-markdown@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw=="], @@ -1011,6 +1027,8 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + "utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="], "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], @@ -1023,6 +1041,8 @@ "vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], + "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], @@ -1061,6 +1081,8 @@ "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + "esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="], + "hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], @@ -1073,6 +1095,10 @@ "hastscript/space-separated-tokens": ["space-separated-tokens@1.1.5", "", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="], + "i18next/@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + + "i18next-browser-languagedetector/@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], @@ -1083,10 +1109,14 @@ "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-i18next/@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], "rehype-prism-plus/refractor": ["refractor@4.9.0", "", { "dependencies": { "@types/hast": "^2.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^7.0.0", "parse-entities": "^4.0.0" } }, "sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og=="], + "rollup/@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ=="], + "stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], "@uiw/react-markdown-preview/rehype-prism-plus/refractor": ["refractor@4.9.0", "", { "dependencies": { "@types/hast": "^2.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^7.0.0", "parse-entities": "^4.0.0" } }, "sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og=="], diff --git a/package.json b/package.json index c072d265..af938260 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,14 @@ "diff": "^8.0.2", "framer-motion": "^12.0.0-alpha.1", "html2canvas": "^1.4.1", + "i18next": "^25.7.3", + "i18next-browser-languagedetector": "^8.2.0", "lucide-react": "^0.468.0", "posthog-js": "^1.258.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", + "react-i18next": "^16.5.1", "react-markdown": "^9.0.3", "react-syntax-highlighter": "^15.6.1", "recharts": "^2.14.1", diff --git a/src/components/AgentExecution.tsx b/src/components/AgentExecution.tsx index 90f089cc..8c5bf450 100644 --- a/src/components/AgentExecution.tsx +++ b/src/components/AgentExecution.tsx @@ -1,9 +1,10 @@ import React, { useState, useEffect, useRef } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; -import { - ArrowLeft, - Play, - StopCircle, +import { + ArrowLeft, + Play, + StopCircle, Terminal, AlertCircle, Loader2, @@ -88,11 +89,12 @@ export const AgentExecution: React.FC = ({ onBack, className, }) => { + const { t } = useTranslation(); const [projectPath] = useState(initialProjectPath || ""); const [task, setTask] = useState(agent.default_task || ""); const [model, setModel] = useState(agent.model || "sonnet"); const [isRunning, setIsRunning] = useState(false); - + // Get tab state functions const { updateTabStatus } = useTabState(); const [messages, setMessages] = useState([]); @@ -444,18 +446,16 @@ export const AgentExecution: React.FC = ({ const handleBackWithConfirmation = () => { if (isRunning) { // Show confirmation dialog before navigating away during execution - const shouldLeave = window.confirm( - "An agent is currently running. If you navigate away, the agent will continue running in the background. You can view running sessions in the 'Running Sessions' tab within CC Agents.\n\nDo you want to continue?" - ); + const shouldLeave = window.confirm(t("execution.runningConfirmLeave")); if (!shouldLeave) { return; } } - + // Clean up listeners but don't stop the actual agent process unlistenRefs.current.forEach(unlisten => unlisten()); unlistenRefs.current = []; - + // Navigate back onBack(); }; @@ -546,14 +546,14 @@ export const AgentExecution: React.FC = ({ size="icon" onClick={handleBackWithConfirmation} className="h-9 w-9 -ml-2" - title="Back" + title={t("common.back")} >

{agent.name}

- {isRunning ? 'Running' : messages.length > 0 ? 'Complete' : 'Ready'} • {model === 'opus' ? 'Claude 4 Opus' : 'Claude 4 Sonnet'} + {isRunning ? t("execution.running") : messages.length > 0 ? t("execution.complete") : t("execution.ready")} • {model === 'opus' ? t("agents.opus") : t("agents.sonnet")}

@@ -565,7 +565,7 @@ export const AgentExecution: React.FC = ({ onClick={() => setIsFullscreenModalOpen(true)} > - Fullscreen + {t("execution.fullscreen")} )} @@ -591,7 +591,7 @@ export const AgentExecution: React.FC = ({ {/* Model Selection */}
- +
= ({ transition={{ duration: 0.15 }} className={cn( "flex-1 px-4 py-3 rounded-md border transition-all", - model === "sonnet" - ? "border-primary bg-primary/10 text-primary" + model === "sonnet" + ? "border-primary bg-primary/10 text-primary" : "border-border hover:border-primary/50 hover:bg-accent", isRunning && "opacity-50 cursor-not-allowed" )} @@ -617,12 +617,12 @@ export const AgentExecution: React.FC = ({ )}
-
Claude 4 Sonnet
-
Faster, efficient
+
{t("agents.sonnet")}
+
{t("execution.fasterEfficient")}
- + !isRunning && setModel("opus")} @@ -630,8 +630,8 @@ export const AgentExecution: React.FC = ({ transition={{ duration: 0.15 }} className={cn( "flex-1 px-4 py-3 rounded-md border transition-all", - model === "opus" - ? "border-primary bg-primary/10 text-primary" + model === "opus" + ? "border-primary bg-primary/10 text-primary" : "border-border hover:border-primary/50 hover:bg-accent", isRunning && "opacity-50 cursor-not-allowed" )} @@ -647,8 +647,8 @@ export const AgentExecution: React.FC = ({ )}
-
Claude 4 Opus
-
More capable
+
{t("agents.opus")}
+
{t("execution.moreCapable")}
@@ -658,7 +658,7 @@ export const AgentExecution: React.FC = ({ {/* Task Input */}
- + {projectPath && ( )}
@@ -676,7 +676,7 @@ export const AgentExecution: React.FC = ({ setTask(e.target.value)} - placeholder="What would you like the agent to do?" + placeholder={t("execution.taskPlaceholder")} disabled={isRunning} className="flex-1 h-9" onKeyDown={(e) => { @@ -703,12 +703,12 @@ export const AgentExecution: React.FC = ({ {isRunning ? ( <> - Stop + {t("execution.stop")} ) : ( <> - Execute + {t("execution.execute")} )} @@ -716,7 +716,7 @@ export const AgentExecution: React.FC = ({
{projectPath && (

- Working in: {projectPath.split('/').pop() || projectPath} + {t("execution.workingIn")}: {projectPath.split('/').pop() || projectPath}

)} @@ -745,9 +745,9 @@ export const AgentExecution: React.FC = ({ {messages.length === 0 && !isRunning && (
-

Ready to Execute

+

{t("execution.readyToExecute")}

- Enter a task to run the agent + {t("execution.enterTask")}

)} @@ -756,7 +756,7 @@ export const AgentExecution: React.FC = ({
- Initializing agent... + {t("execution.initializing")}
)} @@ -809,11 +809,11 @@ export const AgentExecution: React.FC = ({ {/* Modal Header */}
-

{agent.name} - Output

+

{agent.name} - {t("execution.output")}

{isRunning && (
- Running + {t("execution.running")}
)}
@@ -826,7 +826,7 @@ export const AgentExecution: React.FC = ({ className="flex items-center gap-2" > - Copy Output + {t("execution.copyOutput")} } @@ -838,7 +838,7 @@ export const AgentExecution: React.FC = ({ className="w-full justify-start" onClick={handleCopyAsJsonl} > - Copy as JSONL + {t("execution.copyAsJsonl")}
} @@ -861,7 +861,7 @@ export const AgentExecution: React.FC = ({ className="flex items-center gap-2" > - Close + {t("common.close")} @@ -886,9 +886,9 @@ export const AgentExecution: React.FC = ({ {messages.length === 0 && !isRunning && (
-

Ready to Execute

+

{t("execution.readyToExecute")}

- Enter a task to run the agent + {t("execution.enterTask")}

)} @@ -897,7 +897,7 @@ export const AgentExecution: React.FC = ({
- Initializing agent... + {t("execution.initializing")}
)} @@ -928,7 +928,7 @@ export const AgentExecution: React.FC = ({ })} - +
@@ -936,15 +936,15 @@ export const AgentExecution: React.FC = ({ )} {/* Hooks Configuration Dialog */} -
- Configure Hooks + {t("execution.configureHooks")} - Configure hooks that run before, during, and after tool executions + {t("execution.hooksDescription")}
@@ -952,20 +952,19 @@ export const AgentExecution: React.FC = ({
- Project Settings + {t("execution.projectSettings")} - Local Settings + {t("execution.localSettings")}
- +

- Project hooks are stored in .claude/settings.json and - are committed to version control, allowing team members to share configurations. + {t("execution.projectHooksDesc")}

= ({ />
- +

- Local hooks are stored in .claude/settings.local.json and - are not committed to version control, perfect for personal preferences. + {t("execution.localHooksDesc")}

{ + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('agents'); const [showCreateAgent, setShowCreateAgent] = useState(false); const [editingAgent, setEditingAgent] = useState(null); @@ -54,7 +56,7 @@ export const Agents: React.FC = () => { setAgents(agents); } catch (error) { console.error('Failed to load agents:', error); - setToast({ message: 'Failed to load agents', type: 'error' }); + setToast({ message: t('agents.failedLoad'), type: 'error' }); } finally { setLoading(false); } @@ -71,19 +73,19 @@ export const Agents: React.FC = () => { const handleRunAgent = async (agent: Agent) => { if (!agent.id) { - setToast({ message: 'Agent ID is missing', type: 'error' }); + setToast({ message: t('agents.failedOpen', { name: agent.name }), type: 'error' }); return; } - + // Import the dialog function const { open } = await import('@tauri-apps/plugin-dialog'); - + try { // Prompt user to select a project directory const projectPath = await open({ directory: true, multiple: false, - title: `Select project directory for ${agent.name}` + title: t('agents.selectProject', { name: agent.name }) }); if (!projectPath) { @@ -97,25 +99,25 @@ export const Agents: React.FC = () => { detail: { agent, tabId, projectPath } })); - setToast({ message: `Opening agent: ${agent.name}`, type: 'success' }); + setToast({ message: t('agents.openAgent', { name: agent.name }), type: 'success' }); } catch (error) { console.error('Failed to open agent:', error); - setToast({ message: `Failed to open agent: ${agent.name}`, type: 'error' }); + setToast({ message: t('agents.failedOpen', { name: agent.name }), type: 'error' }); } }; const handleDeleteAgent = async () => { if (!agentToDelete || !agentToDelete.id) return; - + try { await api.deleteAgent(agentToDelete.id); - setToast({ message: `Deleted agent: ${agentToDelete.name}`, type: 'success' }); + setToast({ message: t('agents.deleted', { name: agentToDelete.name }), type: 'success' }); setAgents(prev => prev.filter(a => a.id !== agentToDelete.id)); setShowDeleteDialog(false); setAgentToDelete(null); } catch (error) { console.error('Failed to delete agent:', error); - setToast({ message: `Failed to delete agent: ${agentToDelete.name}`, type: 'error' }); + setToast({ message: t('agents.failedDelete', { name: agentToDelete.name }), type: 'error' }); } }; @@ -131,12 +133,12 @@ export const Agents: React.FC = () => { if (selected) { const importedAgent = await api.importAgentFromFile(selected as string); - setToast({ message: `Imported agent: ${importedAgent.name}`, type: 'success' }); + setToast({ message: t('agents.imported', { name: importedAgent.name }), type: 'success' }); loadAgents(); } } catch (error) { console.error('Failed to import agent:', error); - setToast({ message: 'Failed to import agent', type: 'error' }); + setToast({ message: t('agents.failedImport'), type: 'error' }); } }; @@ -151,11 +153,11 @@ export const Agents: React.FC = () => { if (path && agent.id) { await invoke('export_agent_to_file', { id: agent.id, filePath: path }); - setToast({ message: `Exported agent: ${agent.name}`, type: 'success' }); + setToast({ message: t('agents.exported', { name: agent.name }), type: 'success' }); } } catch (error) { console.error('Failed to export agent:', error); - setToast({ message: 'Failed to export agent', type: 'error' }); + setToast({ message: t('agents.failedExport'), type: 'error' }); } }; @@ -206,9 +208,9 @@ export const Agents: React.FC = () => {
-

Agents

+

{t('agents.title')}

- Manage your Claude Code agents + {t('agents.subtitle')}

@@ -216,25 +218,25 @@ export const Agents: React.FC = () => { - From File + {t('agents.importFromFile')} setShowGitHubBrowser(true)}> - From GitHub + {t('agents.importFromGitHub')}
@@ -265,7 +267,7 @@ export const Agents: React.FC = () => { onImportSuccess={() => { loadAgents(); setShowGitHubBrowser(false); - setToast({ message: 'Agent imported successfully', type: 'success' }); + setToast({ message: t('agents.importSuccess'), type: 'success' }); }} /> )} @@ -286,22 +288,22 @@ export const Agents: React.FC = () => { className="bg-card p-6 rounded-lg shadow-lg max-w-md w-full mx-4" onClick={(e) => e.stopPropagation()} > -

Delete Agent

+

{t('agents.deleteConfirm')}

- Are you sure you want to delete "{agentToDelete.name}"? This action cannot be undone. + {t('agents.deleteConfirmDesc', { name: agentToDelete.name })}

@@ -315,11 +317,11 @@ export const Agents: React.FC = () => { - Agents ({agents.length}) + {t('agents.tabs.agents')} ({agents.length}) - History ({runningAgents.length}) + {t('agents.tabs.history')} ({runningAgents.length}) @@ -331,13 +333,13 @@ export const Agents: React.FC = () => { ) : agents.length === 0 ? (
-

No Agents Yet

+

{t('agents.noAgents')}

- Create your first agent to get started + {t('agents.noAgentsDesc')}

) : ( @@ -361,17 +363,17 @@ export const Agents: React.FC = () => { setEditingAgent(agent)}> - Edit + {t('agents.edit')} handleRunAgent(agent)}> - Run + {t('agents.run')} handleExportAgent(agent)}> - Export + {t('agents.export')} - { setAgentToDelete(agent); setShowDeleteDialog(true); @@ -379,14 +381,14 @@ export const Agents: React.FC = () => { className="text-destructive" > - Delete + {t('agents.delete')}

- No description provided + {t('agents.noDescription')}

@@ -398,7 +400,7 @@ export const Agents: React.FC = () => { onClick={() => handleRunAgent(agent)} > - Run + {t('agents.run')}
@@ -412,9 +414,9 @@ export const Agents: React.FC = () => {
-

No Agent History

+

{t('agents.noHistory')}

- Run an agent to see it here + {t('agents.noHistoryDesc')}

@@ -445,22 +447,22 @@ export const Agents: React.FC = () => {
- Started: + {t('agents.started')}:

{new Date(run.created_at).toLocaleString()}

- Duration: + {t('agents.duration')}:

{run.metrics?.duration_ms ? `${(run.metrics.duration_ms / 1000).toFixed(1)}s` : run.duration_ms ? `${(run.duration_ms / 1000).toFixed(1)}s` : '—'}

- Tokens: + {t('agents.tokens')}:

{run.metrics?.total_tokens ? run.metrics.total_tokens.toLocaleString() : run.total_tokens ? run.total_tokens.toLocaleString() : '—'}

{run.status === 'failed' && (
- Agent execution failed + {t('agents.executionFailed')}
)} diff --git a/src/components/CCAgents.tsx b/src/components/CCAgents.tsx index f72b154e..4aec78dd 100644 --- a/src/components/CCAgents.tsx +++ b/src/components/CCAgents.tsx @@ -1,9 +1,10 @@ import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; -import { - Plus, - Edit, - Trash2, +import { + Plus, + Edit, + Trash2, Play, Bot, ArrowLeft, @@ -64,6 +65,7 @@ export type AgentIconName = keyof typeof AGENT_ICONS; * setView('home')} /> */ export const CCAgents: React.FC = ({ onBack, className }) => { + const { t } = useTranslation(); const [agents, setAgents] = useState([]); const [runs, setRuns] = useState([]); const [loading, setLoading] = useState(true); @@ -94,8 +96,8 @@ export const CCAgents: React.FC = ({ onBack, className }) => { setAgents(agentsList); } catch (err) { console.error("Failed to load agents:", err); - setError("Failed to load agents"); - setToast({ message: "Failed to load agents", type: "error" }); + setError(t("agents.failedLoad")); + setToast({ message: t("agents.failedLoad"), type: "error" }); } finally { setLoading(false); } @@ -132,12 +134,12 @@ export const CCAgents: React.FC = ({ onBack, className }) => { try { setIsDeleting(true); await api.deleteAgent(agentToDelete.id); - setToast({ message: "Agent deleted successfully", type: "success" }); + setToast({ message: t("toast.agentDeleted"), type: "success" }); await loadAgents(); await loadRuns(); // Reload runs as they might be affected } catch (err) { console.error("Failed to delete agent:", err); - setToast({ message: "Failed to delete agent", type: "error" }); + setToast({ message: t("toast.agentDeleteFailed"), type: "error" }); } finally { setIsDeleting(false); setShowDeleteDialog(false); @@ -166,13 +168,13 @@ export const CCAgents: React.FC = ({ onBack, className }) => { const handleAgentCreated = async () => { setView("list"); await loadAgents(); - setToast({ message: "Agent created successfully", type: "success" }); + setToast({ message: t("toast.agentCreated"), type: "success" }); }; const handleAgentUpdated = async () => { setView("list"); await loadAgents(); - setToast({ message: "Agent updated successfully", type: "success" }); + setToast({ message: t("toast.agentUpdated"), type: "success" }); }; // const handleRunClick = (run: AgentRunWithMetrics) => { @@ -209,10 +211,10 @@ export const CCAgents: React.FC = ({ onBack, className }) => { filePath }); - setToast({ message: `Agent "${agent.name}" exported successfully`, type: "success" }); + setToast({ message: t("toast.agentExported", { name: agent.name }), type: "success" }); } catch (err) { console.error("Failed to export agent:", err); - setToast({ message: "Failed to export agent", type: "error" }); + setToast({ message: t("toast.agentExportFailed"), type: "error" }); } }; @@ -234,12 +236,12 @@ export const CCAgents: React.FC = ({ onBack, className }) => { // Import the agent from the selected file await api.importAgentFromFile(filePath as string); - - setToast({ message: "Agent imported successfully", type: "success" }); + + setToast({ message: t("toast.agentImported"), type: "success" }); await loadAgents(); } catch (err) { console.error("Failed to import agent:", err); - const errorMessage = err instanceof Error ? err.message : "Failed to import agent"; + const errorMessage = err instanceof Error ? err.message : t("toast.agentImportFailed"); setToast({ message: errorMessage, type: "error" }); } }; @@ -308,9 +310,9 @@ export const CCAgents: React.FC = ({ onBack, className }) => {
-

CC Agents

+

{t("agents.title")}

- Manage your Claude Code agents + {t("agents.subtitle")}

@@ -323,18 +325,18 @@ export const CCAgents: React.FC = ({ onBack, className }) => { className="flex items-center gap-2" > - Import + {t("agents.import")} - From File + {t("agents.importFromFile")} setShowGitHubBrowser(true)}> - From GitHub + {t("agents.importFromGitHub")} @@ -344,7 +346,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { className="flex items-center gap-2" > - Create CC Agent + {t("agents.create")} @@ -380,13 +382,13 @@ export const CCAgents: React.FC = ({ onBack, className }) => { ) : agents.length === 0 ? (
-

No agents yet

+

{t("agents.noAgents")}

- Create your first CC Agent to get started + {t("agents.noAgentsDesc")}

) : ( @@ -410,7 +412,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { {agent.name}

- Created: {new Date(agent.created_at).toLocaleDateString()} + {t("agents.created")}: {new Date(agent.created_at).toLocaleDateString()}

@@ -419,40 +421,40 @@ export const CCAgents: React.FC = ({ onBack, className }) => { variant="ghost" onClick={() => handleExecuteAgent(agent)} className="flex items-center gap-1" - title="Execute agent" + title={t("agents.execute")} > - Execute + {t("agents.execute")} @@ -470,10 +472,10 @@ export const CCAgents: React.FC = ({ onBack, className }) => { onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} > - Previous + {t("agents.previous")} - Page {currentPage} of {totalPages} + {t("agents.page", { current: currentPage, total: totalPages })} )} @@ -494,7 +496,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => {
-

Recent Executions

+

{t("agents.recentExecutions")}

{runsLoading ? (
@@ -530,7 +532,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { onImportSuccess={async () => { setShowGitHubBrowser(false); await loadAgents(); - setToast({ message: "Agent imported successfully from GitHub", type: "success" }); + setToast({ message: t("toast.agentImportedFromGitHub"), type: "success" }); }} /> @@ -540,11 +542,10 @@ export const CCAgents: React.FC = ({ onBack, className }) => { - Delete Agent + {t("agents.deleteConfirm")} - Are you sure you want to delete the agent "{agentToDelete?.name}"? - This action cannot be undone and will permanently remove the agent and all its associated data. + {t("agents.deleteConfirmDesc", { name: agentToDelete?.name })} @@ -554,7 +555,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { disabled={isDeleting} className="w-full sm:w-auto" > - Cancel + {t("agents.cancel")} diff --git a/src/components/CreateAgent.tsx b/src/components/CreateAgent.tsx index 96861e8e..216848be 100644 --- a/src/components/CreateAgent.tsx +++ b/src/components/CreateAgent.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { motion } from "framer-motion"; import { ArrowLeft, Save, Loader2, ChevronDown, Zap, AlertCircle } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -44,6 +45,7 @@ export const CreateAgent: React.FC = ({ onAgentCreated, className, }) => { + const { t } = useTranslation(); const [name, setName] = useState(agent?.name || ""); const [selectedIcon, setSelectedIcon] = useState((agent?.icon as AgentIconName) || "bot"); const [systemPrompt, setSystemPrompt] = useState(agent?.system_prompt || ""); @@ -58,45 +60,45 @@ export const CreateAgent: React.FC = ({ const handleSave = async () => { if (!name.trim()) { - setError("Agent name is required"); + setError(t("agents.nameRequired")); return; } if (!systemPrompt.trim()) { - setError("System prompt is required"); + setError(t("agents.promptRequired")); return; } try { setSaving(true); setError(null); - + if (isEditMode && agent.id) { await api.updateAgent( - agent.id, - name, - selectedIcon, - systemPrompt, - defaultTask || undefined, + agent.id, + name, + selectedIcon, + systemPrompt, + defaultTask || undefined, model ); } else { await api.createAgent( - name, - selectedIcon, - systemPrompt, - defaultTask || undefined, + name, + selectedIcon, + systemPrompt, + defaultTask || undefined, model ); } - + onAgentCreated(); } catch (err) { console.error("Failed to save agent:", err); - setError(isEditMode ? "Failed to update agent" : "Failed to create agent"); - setToast({ - message: isEditMode ? "Failed to update agent" : "Failed to create agent", - type: "error" + setError(isEditMode ? t("agents.failedUpdate") : t("agents.failedCreate")); + setToast({ + message: isEditMode ? t("agents.failedUpdate") : t("agents.failedCreate"), + type: "error" }); } finally { setSaving(false); @@ -104,12 +106,12 @@ export const CreateAgent: React.FC = ({ }; const handleBack = () => { - if ((name !== (agent?.name || "") || - selectedIcon !== (agent?.icon || "bot") || + if ((name !== (agent?.name || "") || + selectedIcon !== (agent?.icon || "bot") || systemPrompt !== (agent?.system_prompt || "") || defaultTask !== (agent?.default_task || "") || - model !== (agent?.model || "sonnet")) && - !confirm("You have unsaved changes. Are you sure you want to leave?")) { + model !== (agent?.model || "sonnet")) && + !confirm(t("agents.unsavedChanges"))) { return; } onBack(); @@ -136,21 +138,21 @@ export const CreateAgent: React.FC = ({ size="icon" onClick={handleBack} className="h-9 w-9 -ml-2" - title="Back to Agents" + title={t("agents.backToAgents")} >

- {isEditMode ? "Edit Agent" : "Create New Agent"} + {isEditMode ? t("agents.editAgent") : t("agents.createNewAgent")}

- {isEditMode ? "Update your Claude Code agent configuration" : "Configure a new Claude Code agent"} + {isEditMode ? t("agents.editAgentDesc") : t("agents.createAgentDesc")}

- + = ({ {saving ? ( <> - Saving... + {t("settings.saving")} ) : ( <> - Save Agent + {t("agents.saveAgent")} )} @@ -196,23 +198,23 @@ export const CreateAgent: React.FC = ({ {/* Basic Information */}
-

Basic Information

+

{t("agents.basicInfo")}

- + setName(e.target.value)} - placeholder="e.g., Code Assistant" + placeholder={t("agents.agentNamePlaceholder")} required className="h-9" />
- +
- + = ({ {/* Model Selection */}
- +
= ({ transition={{ duration: 0.15 }} className={cn( "flex-1 px-4 py-3 rounded-md border transition-all", - model === "sonnet" - ? "border-primary bg-primary/10 text-primary" + model === "sonnet" + ? "border-primary bg-primary/10 text-primary" : "border-border hover:border-primary/50 hover:bg-accent" )} > @@ -257,12 +259,12 @@ export const CreateAgent: React.FC = ({ model === "sonnet" ? "text-primary" : "text-muted-foreground" )} />
-
Claude 4 Sonnet
-
Faster, efficient for most tasks
+
{t("agents.sonnet")}
+
{t("agents.sonnetDesc")}
- + setModel("opus")} @@ -270,8 +272,8 @@ export const CreateAgent: React.FC = ({ transition={{ duration: 0.15 }} className={cn( "flex-1 px-4 py-3 rounded-md border transition-all", - model === "opus" - ? "border-primary bg-primary/10 text-primary" + model === "opus" + ? "border-primary bg-primary/10 text-primary" : "border-border hover:border-primary/50 hover:bg-accent" )} > @@ -281,8 +283,8 @@ export const CreateAgent: React.FC = ({ model === "opus" ? "text-primary" : "text-muted-foreground" )} />
-
Claude 4 Opus
-
More capable, better for complex tasks
+
{t("agents.opus")}
+
{t("agents.opusDesc")}
@@ -292,19 +294,19 @@ export const CreateAgent: React.FC = ({ {/* Configuration */} -

Configuration

+

{t("agents.configuration")}

- + setDefaultTask(e.target.value)} className="h-9" />

- This will be used as the default task placeholder when executing the agent + {t("agents.defaultTaskHint")}

@@ -312,9 +314,9 @@ export const CreateAgent: React.FC = ({ {/* System Prompt */}
-

System Prompt

+

{t("agents.systemPrompt")}

- Define the behavior and capabilities of your Claude Code agent + {t("agents.systemPromptDesc")}

diff --git a/src/components/CustomTitlebar.tsx b/src/components/CustomTitlebar.tsx index 3342959b..00328f2d 100644 --- a/src/components/CustomTitlebar.tsx +++ b/src/components/CustomTitlebar.tsx @@ -1,4 +1,5 @@ import React, { useState, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { motion } from 'framer-motion'; import { Settings, Minus, Square, X, Bot, BarChart3, FileText, Network, Info, MoreVertical } from 'lucide-react'; import { getCurrentWindow } from '@tauri-apps/api/window'; @@ -21,6 +22,7 @@ export const CustomTitlebar: React.FC = ({ onMCPClick, onInfoClick }) => { + const { t } = useTranslation(); const [isHovered, setIsHovered] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const dropdownRef = useRef(null); @@ -90,7 +92,7 @@ export const CustomTitlebar: React.FC = ({ handleClose(); }} className="group relative w-3 h-3 rounded-full bg-red-500 hover:bg-red-600 transition-all duration-200 flex items-center justify-center tauri-no-drag" - title="Close" + title={t('nav.close')} > {isHovered && ( @@ -104,7 +106,7 @@ export const CustomTitlebar: React.FC = ({ handleMinimize(); }} className="group relative w-3 h-3 rounded-full bg-yellow-500 hover:bg-yellow-600 transition-all duration-200 flex items-center justify-center tauri-no-drag" - title="Minimize" + title={t('nav.minimize')} > {isHovered && ( @@ -118,7 +120,7 @@ export const CustomTitlebar: React.FC = ({ handleMaximize(); }} className="group relative w-3 h-3 rounded-full bg-green-500 hover:bg-green-600 transition-all duration-200 flex items-center justify-center tauri-no-drag" - title="Maximize" + title={t('nav.maximize')} > {isHovered && ( @@ -127,20 +129,12 @@ export const CustomTitlebar: React.FC = ({
- {/* Center - Title (hidden) */} - {/*
- {title} -
*/} - {/* Right side - Navigation icons with improved spacing */}
{/* Primary actions group */}
{onAgentsClick && ( - + = ({ )} {onUsageClick && ( - + = ({ {/* Secondary actions group */}
{onSettingsClick && ( - + = ({ {/* Dropdown menu for additional options */}
- + setIsDropdownOpen(!isDropdownOpen)} whileTap={{ scale: 0.97 }} @@ -209,7 +203,7 @@ export const CustomTitlebar: React.FC = ({ className="w-full px-4 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground transition-colors flex items-center gap-3" > - CLAUDE.md + {t('nav.claudeMd')} )} @@ -222,7 +216,7 @@ export const CustomTitlebar: React.FC = ({ className="w-full px-4 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground transition-colors flex items-center gap-3" > - MCP Servers + {t('nav.mcpServers')} )} @@ -235,7 +229,7 @@ export const CustomTitlebar: React.FC = ({ className="w-full px-4 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground transition-colors flex items-center gap-3" > - About + {t('nav.about')} )}
diff --git a/src/components/ExecutionControlBar.tsx b/src/components/ExecutionControlBar.tsx index f7c75d9f..7f3cd527 100644 --- a/src/components/ExecutionControlBar.tsx +++ b/src/components/ExecutionControlBar.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; import { StopCircle, Clock, Hash } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -16,13 +17,15 @@ interface ExecutionControlBarProps { * Floating control bar shown during agent execution * Provides stop functionality and real-time statistics */ -export const ExecutionControlBar: React.FC = ({ - isExecuting, - onStop, +export const ExecutionControlBar: React.FC = ({ + isExecuting, + onStop, totalTokens = 0, elapsedTime = 0, - className + className }) => { + const { t } = useTranslation(); + // Format elapsed time const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); @@ -62,7 +65,7 @@ export const ExecutionControlBar: React.FC = ({
{/* Status text */} - Executing... + {t("execution.executing")} {/* Divider */}
@@ -78,7 +81,7 @@ export const ExecutionControlBar: React.FC = ({ {/* Tokens */}
- {formatTokens(totalTokens)} tokens + {formatTokens(totalTokens)} {t("usage.tokens")}
@@ -93,7 +96,7 @@ export const ExecutionControlBar: React.FC = ({ className="gap-2" > - Stop + {t("execution.stop")} )} diff --git a/src/components/HooksEditor.tsx b/src/components/HooksEditor.tsx index 142b5292..d294260d 100644 --- a/src/components/HooksEditor.tsx +++ b/src/components/HooksEditor.tsx @@ -3,6 +3,7 @@ */ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { motion, AnimatePresence } from 'framer-motion'; import { Plus, @@ -116,6 +117,7 @@ export const HooksEditor: React.FC = ({ onChange, hideActions = false }) => { + const { t } = useTranslation(); const [selectedEvent, setSelectedEvent] = useState('PreToolUse'); const [showTemplateDialog, setShowTemplateDialog] = useState(false); const [validationErrors, setValidationErrors] = useState([]); @@ -736,7 +738,7 @@ export const HooksEditor: React.FC = ({ {isLoading && (
- Loading hooks configuration... + {t("common.loading")}
)} @@ -754,10 +756,10 @@ export const HooksEditor: React.FC = ({ {/* Header */}
-

Hooks Configuration

+

{t('settings.hooks.configuration')}

- {scope === 'project' ? 'Project' : scope === 'local' ? 'Local' : 'User'} Scope + {scope === 'project' ? t('settings.hooks.scopeProject') : scope === 'local' ? t('settings.hooks.scopeLocal') : t('settings.hooks.scopeUser')} {!readOnly && ( <> @@ -767,7 +769,7 @@ export const HooksEditor: React.FC = ({ onClick={() => setShowTemplateDialog(true)} > - Templates + {t('settings.hooks.templates')} {!hideActions && ( )} @@ -789,12 +791,12 @@ export const HooksEditor: React.FC = ({

- Configure shell commands to execute at various points in Claude Code's lifecycle. - {scope === 'local' && ' These settings are not committed to version control.'} + {t('settings.hooks.description')} + {scope === 'local' && ` ${t('settings.hooks.localNote')}`}

{hasUnsavedChanges && !readOnly && (

- You have unsaved changes. Click Save to persist them. + {t('settings.hooks.unsavedChanges')}

)}
@@ -802,7 +804,7 @@ export const HooksEditor: React.FC = ({ {/* Validation Messages */} {validationErrors.length > 0 && (
-

Validation Errors:

+

{t('settings.hooks.validationErrors')}

{validationErrors.map((error, i) => (

• {error}

))} @@ -811,7 +813,7 @@ export const HooksEditor: React.FC = ({ {validationWarnings.length > 0 && (
-

Security Warnings:

+

{t('settings.hooks.securityWarnings')}

{validationWarnings.map((warning, i) => (

• {warning}

))} @@ -857,21 +859,21 @@ export const HooksEditor: React.FC = ({ {items.length === 0 ? ( -

No hooks configured for this event

+

{t('settings.hooks.noHooksConfigured')}

{!readOnly && ( )}
) : (
- {isMatcherEvent + {isMatcherEvent ? (items as EditableHookMatcher[]).map(matcher => renderMatcher(event, matcher)) : (items as EditableHookCommand[]).map(command => renderDirectCommand(event, command)) } - + {!readOnly && ( )}
@@ -893,9 +895,9 @@ export const HooksEditor: React.FC = ({ - Hook Templates + {t('settings.hooks.templateDialog.title')} - Choose a pre-configured hook template to get started quickly + {t('settings.hooks.templateDialog.description')} diff --git a/src/components/LanguageSelector.tsx b/src/components/LanguageSelector.tsx new file mode 100644 index 00000000..af5d5699 --- /dev/null +++ b/src/components/LanguageSelector.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Check, Globe } from 'lucide-react'; +import { Label } from '@/components/ui/label'; +import { cn } from '@/lib/utils'; +import { supportedLanguages } from '@/i18n'; + +interface LanguageSelectorProps { + className?: string; +} + +export const LanguageSelector: React.FC = ({ className }) => { + const { t, i18n } = useTranslation(); + + const changeLanguage = (langCode: string) => { + i18n.changeLanguage(langCode); + localStorage.setItem('app_language', langCode); + }; + + return ( +
+
+ +

+ {t('settings.general.languageDesc')} +

+
+
+ {supportedLanguages.map((lang) => ( + + ))} +
+
+ ); +}; diff --git a/src/components/MCPAddServer.tsx b/src/components/MCPAddServer.tsx index e4b6c30a..1db27ab2 100644 --- a/src/components/MCPAddServer.tsx +++ b/src/components/MCPAddServer.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { Plus, Terminal, Globe, Trash2, Info, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -34,9 +35,10 @@ export const MCPAddServer: React.FC = ({ onServerAdded, onError, }) => { + const { t } = useTranslation(); const [transport, setTransport] = useState<"stdio" | "sse">("stdio"); const [saving, setSaving] = useState(false); - + // Analytics tracking const trackEvent = useTrackEvent(); @@ -101,12 +103,12 @@ export const MCPAddServer: React.FC = ({ */ const handleAddStdioServer = async () => { if (!stdioName.trim()) { - onError("Server name is required"); + onError(t("mcp.add.nameRequired")); return; } - + if (!stdioCommand.trim()) { - onError("Command is required"); + onError(t("mcp.add.commandRequired")); return; } @@ -152,7 +154,7 @@ export const MCPAddServer: React.FC = ({ onError(result.message); } } catch (error) { - onError("Failed to add server"); + onError(t("mcp.add.failedAdd")); console.error("Failed to add stdio server:", error); } finally { setSaving(false); @@ -164,12 +166,12 @@ export const MCPAddServer: React.FC = ({ */ const handleAddSseServer = async () => { if (!sseName.trim()) { - onError("Server name is required"); + onError(t("mcp.add.nameRequired")); return; } - + if (!sseUrl.trim()) { - onError("URL is required"); + onError(t("mcp.add.urlRequired")); return; } @@ -211,7 +213,7 @@ export const MCPAddServer: React.FC = ({ onError(result.message); } } catch (error) { - onError("Failed to add server"); + onError(t("mcp.add.failedAdd")); console.error("Failed to add SSE server:", error); } finally { setSaving(false); @@ -225,7 +227,7 @@ export const MCPAddServer: React.FC = ({ return (
- +
@@ -273,9 +275,9 @@ export const MCPAddServer: React.FC = ({ return (
-

Add MCP Server

+

{t("mcp.add.title")}

- Configure a new Model Context Protocol server + {t("mcp.add.subtitle")}

@@ -283,11 +285,11 @@ export const MCPAddServer: React.FC = ({ - Stdio + {t("mcp.add.stdio")} - SSE + {t("mcp.add.sse")} @@ -296,55 +298,55 @@ export const MCPAddServer: React.FC = ({
- + setStdioName(e.target.value)} />

- A unique name to identify this server + {t("mcp.add.serverNameDesc")}

- + setStdioCommand(e.target.value)} className="font-mono" />

- The command to execute the server + {t("mcp.add.commandDesc")}

- + setStdioArgs(e.target.value)} className="font-mono" />

- Space-separated command arguments + {t("mcp.add.argumentsDesc")}

- + setStdioScope(value)} options={[ - { value: "local", label: "Local (this project only)" }, - { value: "project", label: "Project (shared via .mcp.json)" }, - { value: "user", label: "User (all projects)" }, + { value: "local", label: t("mcp.import.scopeLocal") }, + { value: "project", label: t("mcp.import.scopeProject") }, + { value: "user", label: t("mcp.import.scopeUser") }, ]} />
@@ -361,12 +363,12 @@ export const MCPAddServer: React.FC = ({ {saving ? ( <> - Adding Server... + {t("mcp.add.adding")} ) : ( <> - Add Stdio Server + {t("mcp.add.addStdioServer")} )} @@ -379,41 +381,41 @@ export const MCPAddServer: React.FC = ({
- + setSseName(e.target.value)} />

- A unique name to identify this server + {t("mcp.add.serverNameDesc")}

- + setSseUrl(e.target.value)} className="font-mono" />

- The SSE endpoint URL + {t("mcp.add.urlDesc")}

- + setSseScope(value)} options={[ - { value: "local", label: "Local (this project only)" }, - { value: "project", label: "Project (shared via .mcp.json)" }, - { value: "user", label: "User (all projects)" }, + { value: "local", label: t("mcp.import.scopeLocal") }, + { value: "project", label: t("mcp.import.scopeProject") }, + { value: "user", label: t("mcp.import.scopeUser") }, ]} />
@@ -430,12 +432,12 @@ export const MCPAddServer: React.FC = ({ {saving ? ( <> - Adding Server... + {t("mcp.add.adding")} ) : ( <> - Add SSE Server + {t("mcp.add.addSseServer")} )} @@ -449,7 +451,7 @@ export const MCPAddServer: React.FC = ({
- Example Commands + {t("mcp.add.exampleCommands")}
@@ -462,4 +464,4 @@ export const MCPAddServer: React.FC = ({
); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/MCPImportExport.tsx b/src/components/MCPImportExport.tsx index b63ee1c9..e4011b13 100644 --- a/src/components/MCPImportExport.tsx +++ b/src/components/MCPImportExport.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { Download, Upload, FileText, Loader2, Info, Network, Settings2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; @@ -24,6 +25,7 @@ export const MCPImportExport: React.FC = ({ onImportCompleted, onError, }) => { + const { t } = useTranslation(); const [importingDesktop, setImportingDesktop] = useState(false); const [importingJson, setImportingJson] = useState(false); const [importScope, setImportScope] = useState("local"); @@ -62,7 +64,7 @@ export const MCPImportExport: React.FC = ({ } } catch (error: any) { console.error("Failed to import from Claude Desktop:", error); - onError(error.toString() || "Failed to import from Claude Desktop"); + onError(error.toString() || t("mcp.import.failedImportDesktop")); } finally { setImportingDesktop(false); } @@ -84,7 +86,7 @@ export const MCPImportExport: React.FC = ({ try { jsonData = JSON.parse(content); } catch (e) { - onError("Invalid JSON file. Please check the format."); + onError(t("mcp.import.invalidJson")); return; } @@ -117,7 +119,7 @@ export const MCPImportExport: React.FC = ({ onImportCompleted(imported, failed); } else if (jsonData.type && jsonData.command) { // Single server format - const name = prompt("Enter a name for this server:"); + const name = prompt(t("mcp.import.enterServerName")); if (!name) return; const result = await api.mcpAddJson(name, content, importScope); @@ -127,11 +129,11 @@ export const MCPImportExport: React.FC = ({ onError(result.message); } } else { - onError("Unrecognized JSON format. Expected MCP server configuration."); + onError(t("mcp.import.unrecognizedFormat")); } } catch (error) { console.error("Failed to import JSON:", error); - onError("Failed to import JSON file"); + onError(t("mcp.import.failedImportJson")); } finally { setImportingJson(false); // Reset the input @@ -144,7 +146,7 @@ export const MCPImportExport: React.FC = ({ */ const handleExport = () => { // TODO: Implement export functionality - onError("Export functionality coming soon!"); + onError(t("mcp.import.exportComingSoon")); }; /** @@ -153,19 +155,19 @@ export const MCPImportExport: React.FC = ({ const handleStartMCPServer = async () => { try { await api.mcpServe(); - onError("Claude Code MCP server started. You can now connect to it from other applications."); + onError(t("mcp.import.mcpServerStarted")); } catch (error) { console.error("Failed to start MCP server:", error); - onError("Failed to start Claude Code as MCP server"); + onError(t("mcp.import.failedStartMcpServer")); } }; return (
-

Import & Export

+

{t("mcp.import.title")}

- Import MCP servers from other sources or export your configuration + {t("mcp.import.subtitle")}

@@ -175,19 +177,19 @@ export const MCPImportExport: React.FC = ({
- +
setImportScope(value)} options={[ - { value: "local", label: "Local (this project only)" }, - { value: "project", label: "Project (shared via .mcp.json)" }, - { value: "user", label: "User (all projects)" }, + { value: "local", label: t("mcp.import.scopeLocal") }, + { value: "project", label: t("mcp.import.scopeProject") }, + { value: "user", label: t("mcp.import.scopeUser") }, ]} />

- Choose where to save imported servers from JSON files + {t("mcp.import.importScopeDesc")}

@@ -200,9 +202,9 @@ export const MCPImportExport: React.FC = ({
-

Import from Claude Desktop

+

{t("mcp.import.importFromDesktop")}

- Automatically imports all MCP servers from Claude Desktop. Installs to user scope (available across all projects). + {t("mcp.import.importFromDesktopDesc")}

@@ -214,12 +216,12 @@ export const MCPImportExport: React.FC = ({ {importingDesktop ? ( <> - Importing... + {t("mcp.import.importing")} ) : ( <> - Import from Claude Desktop + {t("mcp.import.importFromDesktop")} )} @@ -234,9 +236,9 @@ export const MCPImportExport: React.FC = ({
-

Import from JSON

+

{t("mcp.import.importFromJson")}

- Import server configuration from a JSON file + {t("mcp.import.importFromJsonDesc")}

@@ -258,12 +260,12 @@ export const MCPImportExport: React.FC = ({ {importingJson ? ( <> - Importing... + {t("mcp.import.importing")} ) : ( <> - Choose JSON File + {t("mcp.import.chooseJsonFile")} )} @@ -279,9 +281,9 @@ export const MCPImportExport: React.FC = ({
-

Export Configuration

+

{t("mcp.import.exportConfig")}

- Export your MCP server configuration + {t("mcp.import.exportConfigDesc")}

@@ -292,7 +294,7 @@ export const MCPImportExport: React.FC = ({ className="w-full gap-2" > - Export (Coming Soon) + {t("mcp.import.exportComingSoon")}
@@ -305,9 +307,9 @@ export const MCPImportExport: React.FC = ({
-

Use Claude Code as MCP Server

+

{t("mcp.import.useAsMcpServer")}

- Start Claude Code as an MCP server that other applications can connect to + {t("mcp.import.useAsMcpServerDesc")}

@@ -317,7 +319,7 @@ export const MCPImportExport: React.FC = ({ className="w-full gap-2 border-green-500/20 hover:bg-green-500/10 hover:text-green-600 hover:border-green-500/50" > - Start MCP Server + {t("mcp.import.startMcpServer")}
@@ -328,11 +330,11 @@ export const MCPImportExport: React.FC = ({
- JSON Format Examples + {t("mcp.import.jsonFormatExamples")}
-

Single server:

+

{t("mcp.import.singleServer")}

 {`{
   "type": "stdio",
@@ -343,7 +345,7 @@ export const MCPImportExport: React.FC = ({
               
-

Multiple servers (.mcp.json format):

+

{t("mcp.import.multipleServers")}

 {`{
   "mcpServers": {
@@ -366,4 +368,4 @@ export const MCPImportExport: React.FC = ({
       
     
); -}; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/MCPManager.tsx b/src/components/MCPManager.tsx index 7bef9e11..9dee0dd1 100644 --- a/src/components/MCPManager.tsx +++ b/src/components/MCPManager.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; import { AlertCircle, Loader2 } from "lucide-react"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; @@ -27,6 +28,7 @@ interface MCPManagerProps { export const MCPManager: React.FC = ({ className: _className, }) => { + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState("servers"); const [servers, setServers] = useState([]); const [loading, setLoading] = useState(true); @@ -53,7 +55,7 @@ export const MCPManager: React.FC = ({ setServers(serverList); } catch (err) { console.error("MCPManager: Failed to load MCP servers:", err); - setError("Failed to load MCP servers. Make sure Claude Code is installed."); + setError(t("mcp.failedLoad")); } finally { setLoading(false); } @@ -64,7 +66,7 @@ export const MCPManager: React.FC = ({ */ const handleServerAdded = () => { loadServers(); - setToast({ message: "MCP server added successfully!", type: "success" }); + setToast({ message: t("mcp.serverAdded"), type: "success" }); setActiveTab("servers"); }; @@ -73,7 +75,7 @@ export const MCPManager: React.FC = ({ */ const handleServerRemoved = (name: string) => { setServers(prev => prev.filter(s => s.name !== name)); - setToast({ message: `Server "${name}" removed successfully!`, type: "success" }); + setToast({ message: t("mcp.serverRemoved", { name }), type: "success" }); }; /** @@ -82,14 +84,14 @@ export const MCPManager: React.FC = ({ const handleImportCompleted = (imported: number, failed: number) => { loadServers(); if (failed === 0) { - setToast({ - message: `Successfully imported ${imported} server${imported > 1 ? 's' : ''}!`, - type: "success" + setToast({ + message: t("mcp.importSuccess", { count: imported }), + type: "success" }); } else { - setToast({ - message: `Imported ${imported} server${imported > 1 ? 's' : ''}, ${failed} failed`, - type: "error" + setToast({ + message: t("mcp.importPartial", { imported, failed }), + type: "error" }); } }; @@ -101,9 +103,9 @@ export const MCPManager: React.FC = ({
-

MCP Servers

+

{t("mcp.title")}

- Manage Model Context Protocol servers + {t("mcp.subtitle")}

@@ -134,13 +136,13 @@ export const MCPManager: React.FC = ({ - Servers + {t("mcp.tabs.servers")} - Add Server + {t("mcp.tabs.add")} - Import/Export + {t("mcp.tabs.import")} diff --git a/src/components/MCPServerList.tsx b/src/components/MCPServerList.tsx index b4936928..b473dc9c 100644 --- a/src/components/MCPServerList.tsx +++ b/src/components/MCPServerList.tsx @@ -1,11 +1,12 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; -import { - Network, - Globe, - Terminal, - Trash2, - Play, +import { + Network, + Globe, + Terminal, + Trash2, + Play, CheckCircle, Loader2, RefreshCw, @@ -50,12 +51,13 @@ export const MCPServerList: React.FC = ({ onServerRemoved, onRefresh, }) => { + const { t } = useTranslation(); const [removingServer, setRemovingServer] = useState(null); const [testingServer, setTestingServer] = useState(null); const [expandedServers, setExpandedServers] = useState>(new Set()); const [copiedServer, setCopiedServer] = useState(null); const [connectedServers] = useState([]); - + // Analytics tracking const trackEvent = useTrackEvent(); @@ -184,11 +186,11 @@ export const MCPServerList: React.FC = ({ const getScopeDisplayName = (scope: string) => { switch (scope) { case "local": - return "Local (Project-specific)"; + return t("mcp.scopes.local"); case "project": - return "Project (Shared via .mcp.json)"; + return t("mcp.scopes.project"); case "user": - return "User (All projects)"; + return t("mcp.scopes.user"); default: return scope; } @@ -220,11 +222,11 @@ export const MCPServerList: React.FC = ({ {server.status?.running && ( - Running + {t("mcp.running")} )}
- + {server.command && !isExpanded && (

@@ -237,11 +239,11 @@ export const MCPServerList: React.FC = ({ className="h-6 px-2 text-xs hover:bg-primary/10" > - Show full + {t("mcp.showFull")}

)} - + {server.transport === "sse" && server.url && !isExpanded && (

@@ -249,10 +251,10 @@ export const MCPServerList: React.FC = ({

)} - + {Object.keys(server.env).length > 0 && !isExpanded && (
- Environment variables: {Object.keys(server.env).length} + {t("mcp.envVarsCount", { count: Object.keys(server.env).length })}
)}
@@ -299,7 +301,7 @@ export const MCPServerList: React.FC = ({ {server.command && (
-

Command

+

{t("mcp.command")}

@@ -326,10 +328,10 @@ export const MCPServerList: React.FC = ({

)} - + {server.args && server.args.length > 0 && (
-

Arguments

+

{t("mcp.arguments")}

{server.args.map((arg, idx) => (
@@ -340,19 +342,19 @@ export const MCPServerList: React.FC = ({
)} - + {server.transport === "sse" && server.url && (
-

URL

+

{t("mcp.url")}

{server.url}

)} - + {Object.keys(server.env).length > 0 && (
-

Environment Variables

+

{t("mcp.envVars")}

{Object.entries(server.env).map(([key, value]) => (
@@ -384,9 +386,9 @@ export const MCPServerList: React.FC = ({ {/* Header */}
-

Configured Servers

+

{t("mcp.configuredServers")}

- {servers.length} server{servers.length !== 1 ? "s" : ""} configured + {t("mcp.serversConfigured", { count: servers.length })}

@@ -406,9 +408,9 @@ export const MCPServerList: React.FC = ({
-

No MCP servers configured

+

{t("mcp.noServers")}

- Add a server to get started with Model Context Protocol + {t("mcp.noServersDesc")}

) : ( diff --git a/src/components/NFOCredits.tsx b/src/components/NFOCredits.tsx index af9202f8..10581bf1 100644 --- a/src/components/NFOCredits.tsx +++ b/src/components/NFOCredits.tsx @@ -100,6 +100,9 @@ export const NFOCredits: React.FC = ({ onClose }) => { { type: "credit", role: "BUILD TOOL", name: "Vite" }, { type: "credit", role: "PACKAGE MANAGER", name: "Bun" }, { type: "spacer" }, + { type: "section", title: "━━━ LOCALIZATION ━━━" }, + { type: "credit", role: "CHINESE (中文)", name: "Legna (github.com/LegnaOS)" }, + { type: "spacer" }, { type: "section", title: "━━━ SPECIAL THANKS ━━━" }, { type: "text", content: "To the open source community" }, { type: "text", content: "To all the beta testers" }, diff --git a/src/components/ProjectList.tsx b/src/components/ProjectList.tsx index 8d8ab799..aafeaa6e 100644 --- a/src/components/ProjectList.tsx +++ b/src/components/ProjectList.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { motion } from "framer-motion"; -import { +import { FolderOpen, ChevronLeft, ChevronRight @@ -89,6 +90,7 @@ export const ProjectList: React.FC = ({ onOpenProject, className, }) => { + const { t } = useTranslation(); const [showAll, setShowAll] = useState(false); const [currentPage, setCurrentPage] = useState(1); @@ -118,9 +120,9 @@ export const ProjectList: React.FC = ({
-

Projects

+

{t('projects.title')}

- Select a project to start working with Claude Code + {t('projects.subtitle')}

= ({ className="flex items-center gap-2" > - Open Project + {t('projects.openProject')}
@@ -145,20 +147,20 @@ export const ProjectList: React.FC = ({ {displayedProjects.length > 0 ? (
-

Recent Projects

+

{t('projects.recentProjects')}

{!showAll ? ( - ) : ( - )}
@@ -245,9 +247,9 @@ export const ProjectList: React.FC = ({
-

No recent projects

+

{t('projects.noRecentProjects')}

- Open a project to get started with Claude Code + {t('projects.noRecentProjectsDesc')}

= ({ className="flex items-center gap-2" > - Open Your First Project + {t('projects.openFirstProject')}
diff --git a/src/components/ProjectSettings.tsx b/src/components/ProjectSettings.tsx index bbc82831..696c3d50 100644 --- a/src/components/ProjectSettings.tsx +++ b/src/components/ProjectSettings.tsx @@ -3,12 +3,13 @@ */ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { HooksEditor } from '@/components/HooksEditor'; import { SlashCommandsManager } from '@/components/SlashCommandsManager'; import { api } from '@/lib/api'; -import { - AlertTriangle, - ArrowLeft, +import { + AlertTriangle, + ArrowLeft, Settings, FolderOpen, GitBranch, @@ -33,9 +34,10 @@ export const ProjectSettings: React.FC = ({ onBack, className }) => { + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState('commands'); const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null); - + // Other hooks settings const [gitIgnoreLocal, setGitIgnoreLocal] = useState(true); @@ -70,11 +72,11 @@ export const ProjectSettings: React.FC = ({ content += '\n# Claude local settings (machine-specific)\n.claude/settings.local.json\n'; await api.saveClaudeMdFile(gitignorePath, content); setGitIgnoreLocal(true); - setToast({ message: 'Added to .gitignore', type: 'success' }); + setToast({ message: t('projectSettings.localHooks.addedToGitignore'), type: 'success' }); } } catch (err) { console.error('Failed to update .gitignore:', err); - setToast({ message: 'Failed to update .gitignore', type: 'error' }); + setToast({ message: t('projectSettings.localHooks.failedUpdateGitignore'), type: 'error' }); } }; @@ -86,15 +88,15 @@ export const ProjectSettings: React.FC = ({
-

Project Settings

+

{t('projectSettings.title')}

- +
@@ -110,15 +112,15 @@ export const ProjectSettings: React.FC = ({ - Slash Commands + {t('projectSettings.tabs.commands')} - Project Hooks + {t('projectSettings.tabs.projectHooks')} - Local Hooks + {t('projectSettings.tabs.localHooks')} @@ -126,14 +128,14 @@ export const ProjectSettings: React.FC = ({
-

Project Slash Commands

+

{t('projectSettings.slashCommands.title')}

- Custom commands that are specific to this project. These commands are stored in + {t('projectSettings.slashCommands.description')} .claude/slash-commands/ - and can be committed to version control. + {t('projectSettings.slashCommands.pathDesc')}

- + = ({
-

Project Hooks

+

{t('projectSettings.projectHooks.title')}

- These hooks apply to all users working on this project. They are stored in + {t('projectSettings.projectHooks.description')} .claude/settings.json - and should be committed to version control. + {t('projectSettings.projectHooks.pathDesc')}

- + = ({
-

Local Hooks

+

{t('projectSettings.localHooks.title')}

- These hooks only apply to your machine. They are stored in + {t('projectSettings.localHooks.description')} .claude/settings.local.json - and should NOT be committed to version control. + {t('projectSettings.localHooks.pathDesc')}

- + {!gitIgnoreLocal && (

- Local settings file is not in .gitignore + {t('projectSettings.localHooks.notInGitignore')}

)}
- + ({ http_proxy: null, https_proxy: null, @@ -43,13 +45,13 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { await invoke('save_proxy_settings', { settings }); setOriginalSettings(settings); setToast({ - message: 'Proxy settings saved and applied successfully.', + message: t('settings.proxy.saved'), type: 'success', }); } catch (error) { console.error('Failed to save proxy settings:', error); setToast({ - message: 'Failed to save proxy settings', + message: t('settings.proxy.failedSave'), type: 'error', }); throw error; // Re-throw to let parent handle the error @@ -72,7 +74,7 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { } catch (error) { console.error('Failed to load proxy settings:', error); setToast({ - message: 'Failed to load proxy settings', + message: t('settings.proxy.failedLoad'), type: 'error', }); } @@ -89,18 +91,18 @@ export function ProxySettings({ setToast, onChange }: ProxySettingsProps) { return (
-

Proxy Settings

+

{t('settings.proxy.title')}

- Configure proxy settings for Claude API requests + {t('settings.proxy.subtitle')}

- +

- Use proxy for all Claude API requests + {t('settings.proxy.enableProxyDesc')}

- +
- +
- +

- Comma-separated list of hosts that should bypass the proxy + {t('settings.proxy.noProxyDesc')}

- +

- Proxy URL to use for all protocols if protocol-specific proxies are not set + {t('settings.proxy.allProxyDesc')}

diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 06d338a0..d5b79287 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; import { Plus, @@ -27,6 +28,7 @@ import { StorageTab } from "./StorageTab"; import { HooksEditor } from "./HooksEditor"; import { SlashCommandsManager } from "./SlashCommandsManager"; import { ProxySettings } from "./ProxySettings"; +import { LanguageSelector } from "./LanguageSelector"; import { useTheme, useTrackEvent } from "@/hooks"; import { analytics } from "@/lib/analytics"; import { TabPersistenceService } from "@/services/tabPersistence"; @@ -60,6 +62,7 @@ interface EnvironmentVariable { export const Settings: React.FC = ({ className, }) => { + const { t } = useTranslation(); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -237,11 +240,11 @@ export const Settings: React.FC = ({ setProxySettingsChanged(false); } - setToast({ message: "Settings saved successfully!", type: "success" }); + setToast({ message: t("toast.settingsSaved"), type: "success" }); } catch (err) { console.error("Failed to save settings:", err); - setError("Failed to save settings."); - setToast({ message: "Failed to save settings", type: "error" }); + setError(t("toast.settingsFailed")); + setToast({ message: t("toast.settingsFailed"), type: "error" }); } finally { setSaving(false); } @@ -339,9 +342,9 @@ export const Settings: React.FC = ({
-

Settings

+

{t("settings.title")}

- Configure Claude Code preferences + {t("settings.subtitle")}

= ({ {saving ? ( <> - Saving... + {t("settings.saving")} ) : ( <> - Save Settings + {t("settings.save")} )} @@ -394,29 +397,29 @@ export const Settings: React.FC = ({
- General - Permissions - Environment - Advanced - Hooks - Commands - Storage - Proxy + {t("settings.tabs.general")} + {t("settings.tabs.permissions")} + {t("settings.tabs.environment")} + {t("settings.tabs.advanced")} + {t("settings.tabs.hooks")} + {t("settings.tabs.commands")} + {t("settings.tabs.storage")} + {t("settings.tabs.proxy")} {/* General Settings */}
-

General Settings

- +

{t("settings.general.title")}

+
{/* Theme Selector */}
- +

- Choose your preferred color theme + {t("settings.general.themeDesc")}

@@ -424,62 +427,65 @@ export const Settings: React.FC = ({ onClick={() => setTheme('dark')} className={cn( "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-all", - theme === 'dark' - ? "bg-background shadow-sm" + theme === 'dark' + ? "bg-background shadow-sm" : "hover:bg-background/50" )} > {theme === 'dark' && } - Dark + {t("settings.general.dark")}
+ {/* Language Selector */} + + {/* Custom Color Editor */} {theme === 'custom' && (
-

Custom Theme Colors

- +

{t("settings.general.customTheme")}

+
{/* Background Color */}
- +
= ({ {/* Foreground Color */}
- +
= ({ {/* Primary Color */}
- +
= ({ {/* Card Color */}
- +
= ({ {/* Accent Color */}
- +
= ({ {/* Destructive Color */}
- +
= ({

- Use CSS color values (hex, rgb, oklch, etc.). Changes apply immediately. + {t("settings.general.customThemeHint")}

)} - + {/* Include Co-authored By */}
- +

- Add Claude attribution to git commits and pull requests + {t("settings.general.coauthoredDesc")}

= ({ {/* Verbose Output */}
- +

- Show full bash and command outputs + {t("settings.general.verboseDesc")}

= ({
- +

- How long to retain chat transcripts locally (default: 30 days) + {t("settings.general.retentionDesc")}

= ({ {binaryPathChanged && (

- Changes will be applied when you save settings. + {t("settings.general.binaryPathChanged")}

)}
{/* Separator */}
- + {/* Analytics Toggle */}
- +

- Help improve opcode by sharing anonymous usage data + {t("settings.general.analyticsDesc")}

= ({ await analytics.enable(); setAnalyticsEnabled(true); trackEvent.settingsChanged('analytics_enabled', true); - setToast({ message: "Analytics enabled", type: "success" }); + setToast({ message: t("settings.general.analyticsEnabled"), type: "success" }); } else { await analytics.disable(); setAnalyticsEnabled(false); trackEvent.settingsChanged('analytics_enabled', false); - setToast({ message: "Analytics disabled", type: "success" }); + setToast({ message: t("settings.general.analyticsDisabled"), type: "success" }); } }} /> @@ -703,23 +709,23 @@ export const Settings: React.FC = ({
-

Your privacy is protected

+

{t("settings.general.privacyTitle")}

    -
  • • No personal information or file contents collected
  • -
  • • All data is anonymous with random IDs
  • -
  • • You can disable analytics at any time
  • +
  • • {t("settings.general.privacyPoints.noPersonal")}
  • +
  • • {t("settings.general.privacyPoints.anonymous")}
  • +
  • • {t("settings.general.privacyPoints.disable")}
)} - + {/* Tab Persistence Toggle */}
- +

- Restore your tabs when you restart the app + {t("settings.general.tabPersistenceDesc")}

= ({ TabPersistenceService.setEnabled(checked); setTabPersistenceEnabled(checked); trackEvent.settingsChanged('tab_persistence_enabled', checked); - setToast({ - message: checked - ? "Tab persistence enabled - your tabs will be restored on restart" - : "Tab persistence disabled - tabs will not be saved", - type: "success" + setToast({ + message: checked + ? t("settings.general.tabPersistenceEnabled") + : t("settings.general.tabPersistenceDisabled"), + type: "success" }); }} /> @@ -742,9 +748,9 @@ export const Settings: React.FC = ({ {/* Startup Intro Toggle */}
- +

- Display a brief welcome animation when the app launches + {t("settings.general.startupIntroDesc")}

= ({ try { await api.saveSetting('startup_intro_enabled', checked ? 'true' : 'false'); trackEvent.settingsChanged('startup_intro_enabled', checked); - setToast({ - message: checked - ? 'Welcome intro enabled' - : 'Welcome intro disabled', - type: 'success' + setToast({ + message: checked + ? t("settings.general.startupIntroEnabled") + : t("settings.general.startupIntroDisabled"), + type: 'success' }); } catch (e) { - setToast({ message: 'Failed to update preference', type: 'error' }); + setToast({ message: t("settings.failedUpdatePreference"), type: 'error' }); } }} /> @@ -777,16 +783,16 @@ export const Settings: React.FC = ({
-

Permission Rules

+

{t("settings.permissions.title")}

- Control which tools Claude Code can use without manual approval + {t("settings.permissions.subtitle")}

- + {/* Allow Rules */}
- +
{allowRules.length === 0 ? (

- No allow rules configured. Claude will ask for approval for all tools. + {t("settings.permissions.noAllowRules")}

) : ( allowRules.map((rule) => ( @@ -834,7 +840,7 @@ export const Settings: React.FC = ({ {/* Deny Rules */}
- +
{denyRules.length === 0 ? (

- No deny rules configured. + {t("settings.permissions.noDenyRules")}

) : ( denyRules.map((rule) => ( @@ -881,14 +887,14 @@ export const Settings: React.FC = ({

- Examples: + {t("settings.permissions.examples")}

    -
  • Bash - Allow all bash commands
  • -
  • Bash(npm run build) - Allow exact command
  • -
  • Bash(npm run test:*) - Allow commands with prefix
  • -
  • Read(~/.zshrc) - Allow reading specific file
  • -
  • Edit(docs/**) - Allow editing files in docs directory
  • +
  • Bash - {t("settings.permissions.examplesList.bash")}
  • +
  • Bash(npm run build) - {t("settings.permissions.examplesList.bashExact")}
  • +
  • Bash(npm run test:*) - {t("settings.permissions.examplesList.bashPrefix")}
  • +
  • Read(~/.zshrc) - {t("settings.permissions.examplesList.read")}
  • +
  • Edit(docs/**) - {t("settings.permissions.examplesList.edit")}
@@ -901,9 +907,9 @@ export const Settings: React.FC = ({
-

Environment Variables

+

{t("settings.environment.title")}

- Environment variables applied to every Claude Code session + {t("settings.environment.subtitle")}

- +
{envVars.length === 0 ? (

- No environment variables configured. + {t("settings.environment.noVariables")}

) : ( envVars.map((envVar) => ( @@ -958,12 +964,12 @@ export const Settings: React.FC = ({

- Common variables: + {t("settings.environment.common")}

    -
  • CLAUDE_CODE_ENABLE_TELEMETRY - Enable/disable telemetry (0 or 1)
  • -
  • ANTHROPIC_MODEL - Custom model name
  • -
  • DISABLE_COST_WARNINGS - Disable cost warnings (1)
  • +
  • CLAUDE_CODE_ENABLE_TELEMETRY - {t("settings.environment.commonList.telemetry")}
  • +
  • ANTHROPIC_MODEL - {t("settings.environment.commonList.model")}
  • +
  • DISABLE_COST_WARNINGS - {t("settings.environment.commonList.costWarnings")}
@@ -974,15 +980,15 @@ export const Settings: React.FC = ({
-

Advanced Settings

+

{t("settings.advanced.title")}

- Additional configuration options for advanced users + {t("settings.advanced.subtitle")}

- + {/* API Key Helper */}
- + = ({ onChange={(e) => updateSetting("apiKeyHelper", e.target.value || undefined)} />

- Custom script to generate auth values for API requests + {t("settings.advanced.apiKeyHelperDesc")}

- + {/* Raw JSON Editor */}
- +
{JSON.stringify(settings, null, 2)}

- This shows the raw JSON that will be saved to ~/.claude/settings.json + {t("settings.advanced.rawJsonDesc")}

@@ -1013,10 +1019,10 @@ export const Settings: React.FC = ({
-

User Hooks

+

{t("settings.hooks.title")}

- Configure hooks that apply to all Claude Code sessions for your user account. - These are stored in ~/.claude/settings.json + {t("settings.hooks.subtitle")} + {t("settings.hooks.storedIn")} ~/.claude/settings.json

diff --git a/src/components/SlashCommandsManager.tsx b/src/components/SlashCommandsManager.tsx index 631f1744..052689db 100644 --- a/src/components/SlashCommandsManager.tsx +++ b/src/components/SlashCommandsManager.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; import { Plus, @@ -92,6 +93,7 @@ export const SlashCommandsManager: React.FC = ({ className, scopeFilter = 'all', }) => { + const { t } = useTranslation(); const [commands, setCommands] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -133,7 +135,7 @@ export const SlashCommandsManager: React.FC = ({ setCommands(loadedCommands); } catch (err) { console.error("Failed to load slash commands:", err); - setError("Failed to load commands"); + setError(t('settings.commands.failedLoad')); } finally { setLoading(false); } @@ -190,7 +192,7 @@ export const SlashCommandsManager: React.FC = ({ await loadCommands(); } catch (err) { console.error("Failed to save command:", err); - setError(err instanceof Error ? err.message : "Failed to save command"); + setError(err instanceof Error ? err.message : t('settings.commands.failedSave')); } finally { setSaving(false); } @@ -213,7 +215,7 @@ export const SlashCommandsManager: React.FC = ({ await loadCommands(); } catch (err) { console.error("Failed to delete command:", err); - const errorMessage = err instanceof Error ? err.message : "Failed to delete command"; + const errorMessage = err instanceof Error ? err.message : t('settings.commands.failedDelete'); setError(errorMessage); } finally { setDeleting(false); @@ -289,9 +291,9 @@ export const SlashCommandsManager: React.FC = ({ // Group commands by namespace and scope const groupedCommands = filteredCommands.reduce((acc, cmd) => { - const key = cmd.namespace - ? `${cmd.namespace} (${cmd.scope})` - : `${cmd.scope === 'project' ? 'Project' : 'User'} Commands`; + const key = cmd.namespace + ? `${cmd.namespace} (${cmd.scope})` + : `${cmd.scope === 'project' ? t('settings.commands.projectCommands') : t('settings.commands.userCommands')}`; if (!acc[key]) { acc[key] = []; } @@ -305,17 +307,17 @@ export const SlashCommandsManager: React.FC = ({

- {scopeFilter === 'project' ? 'Project Slash Commands' : 'Slash Commands'} + {scopeFilter === 'project' ? t('settings.commands.projectTitle') : t('settings.commands.title')}

- {scopeFilter === 'project' - ? 'Create custom commands for this project' - : 'Create custom commands to streamline your workflow'} + {scopeFilter === 'project' + ? t('settings.commands.projectSubtitle') + : t('settings.commands.subtitle')}

@@ -325,7 +327,7 @@ export const SlashCommandsManager: React.FC = ({
setSearchQuery(e.target.value)} className="pl-9" @@ -338,9 +340,9 @@ export const SlashCommandsManager: React.FC = ({ - All Commands - Project - User + {t('settings.commands.allCommands')} + {t('settings.commands.scopeProject')} + {t('settings.commands.scopeUser')} )} @@ -364,17 +366,17 @@ export const SlashCommandsManager: React.FC = ({

- {searchQuery - ? "No commands found" - : scopeFilter === 'project' - ? "No project commands created yet" - : "No commands created yet"} + {searchQuery + ? t('settings.commands.noCommandsFound') + : scopeFilter === 'project' + ? t('settings.commands.noProjectCommands') + : t('settings.commands.noCommands')}

{!searchQuery && ( )}
@@ -407,7 +409,7 @@ export const SlashCommandsManager: React.FC = ({ {command.accepts_arguments && ( - Arguments + {t('settings.commands.arguments')} )}
@@ -427,16 +429,16 @@ export const SlashCommandsManager: React.FC = ({ {command.has_bash_commands && ( - Bash + {t('settings.commands.bash')} )} - + {command.has_file_references && ( - Files + {t('settings.commands.files')} )} - + @@ -508,16 +510,16 @@ export const SlashCommandsManager: React.FC = ({ - {editingCommand ? "Edit Command" : "Create New Command"} + {editingCommand ? t('settings.commands.editCommand') : t('settings.commands.createCommand')}
{/* Scope */}
- - setCommandForm(prev => ({ ...prev, scope: value }))} disabled={scopeFilter !== 'all' || (!projectPath && commandForm.scope === 'project')} > @@ -529,7 +531,7 @@ export const SlashCommandsManager: React.FC = ({
- User (Global) + {t('settings.commands.scopeUser')}
)} @@ -537,16 +539,16 @@ export const SlashCommandsManager: React.FC = ({
- Project + {t('settings.commands.scopeProject')}
)}

- {commandForm.scope === 'user' - ? "Available across all projects" - : "Only available in this project"} + {commandForm.scope === 'user' + ? t('settings.commands.scopeUserDesc') + : t('settings.commands.scopeProjectDesc')}

diff --git a/src/components/StorageTab.tsx b/src/components/StorageTab.tsx index aa4071cb..59f47471 100644 --- a/src/components/StorageTab.tsx +++ b/src/components/StorageTab.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback } from "react"; +import { useTranslation } from "react-i18next"; import { motion, AnimatePresence } from "framer-motion"; import { Database, @@ -81,6 +82,7 @@ interface QueryResult { * StorageTab component - A beautiful SQLite database viewer/editor */ export const StorageTab: React.FC = () => { + const { t } = useTranslation(); const [tables, setTables] = useState([]); const [selectedTable, setSelectedTable] = useState(""); const [tableData, setTableData] = useState(null); @@ -131,7 +133,7 @@ export const StorageTab: React.FC = () => { } } catch (err) { console.error("Failed to load tables:", err); - setError("Failed to load tables"); + setError(t("storage.failedLoadTables")); } finally { setLoading(false); } @@ -156,7 +158,7 @@ export const StorageTab: React.FC = () => { setCurrentPage(page); } catch (err) { console.error("Failed to load table data:", err); - setError("Failed to load table data"); + setError(t("storage.failedLoadData")); } finally { setLoading(false); } @@ -203,7 +205,7 @@ export const StorageTab: React.FC = () => { setEditingRow(null); } catch (err) { console.error("Failed to update row:", err); - setError("Failed to update row"); + setError(t("storage.failedUpdate")); } finally { setLoading(false); } @@ -223,7 +225,7 @@ export const StorageTab: React.FC = () => { setDeletingRow(null); } catch (err) { console.error("Failed to delete row:", err); - setError("Failed to delete row"); + setError(t("storage.failedDelete")); } finally { setLoading(false); } @@ -242,7 +244,7 @@ export const StorageTab: React.FC = () => { setNewRow(null); } catch (err) { console.error("Failed to insert row:", err); - setError("Failed to insert row"); + setError(t("storage.failedInsert")); } finally { setLoading(false); } @@ -285,14 +287,14 @@ export const StorageTab: React.FC = () => { setTableData(null); setShowResetConfirm(false); setToast({ - message: "Database Reset Complete: The database has been restored to its default state with empty tables (agents, agent_runs, app_settings).", + message: t("storage.resetSuccess"), type: "success", }); } catch (err) { console.error("Failed to reset database:", err); - setError("Failed to reset database"); + setError(t("storage.failedReset")); setToast({ - message: "Reset Failed: Failed to reset the database. Please try again.", + message: t("storage.resetFailed"), type: "error", }); } finally { @@ -335,7 +337,7 @@ export const StorageTab: React.FC = () => {
-

Database Storage

+

{t("storage.title")}

@@ -363,7 +365,7 @@ export const StorageTab: React.FC = () => {
handleSearch(e.target.value)} className="pl-8 h-8 text-xs" @@ -404,7 +406,7 @@ export const StorageTab: React.FC = () => { className="gap-2 h-8 text-xs" > - New Row + {t("storage.newRow")} )}
@@ -435,7 +437,7 @@ export const StorageTab: React.FC = () => { ))} - Actions + {t("storage.actions")} @@ -518,9 +520,11 @@ export const StorageTab: React.FC = () => { {tableData.total_pages > 1 && (
- Showing {(currentPage - 1) * pageSize + 1} to{" "} - {Math.min(currentPage * pageSize, tableData.total_rows)} of{" "} - {tableData.total_rows} rows + {t("storage.pagination.showing", { + start: (currentPage - 1) * pageSize + 1, + end: Math.min(currentPage * pageSize, tableData.total_rows), + total: tableData.total_rows + })}
- Page {currentPage} of {tableData.total_pages} + {t("storage.pagination.pageOf", { current: currentPage, total: tableData.total_pages })}
@@ -573,9 +577,9 @@ export const StorageTab: React.FC = () => { setEditingRow(null)}> - Edit Row + {t("storage.editRow")} - Update the values for this row in the {selectedTable} table. + {t("storage.editRowDesc", { table: selectedTable })} {editingRow && tableData && ( @@ -586,7 +590,7 @@ export const StorageTab: React.FC = () => { {column.name} {column.pk && ( - (Primary Key) + ({t("storage.primaryKey")}) )} @@ -620,9 +624,9 @@ export const StorageTab: React.FC = () => { /> )}

- Type: {column.type_name} - {column.notnull && ", NOT NULL"} - {column.dflt_value && `, Default: ${column.dflt_value}`} + {t("storage.type")}: {column.type_name} + {column.notnull && `, ${t("storage.notNull")}`} + {column.dflt_value && `, ${t("storage.default")}: ${column.dflt_value}`}

))} @@ -630,7 +634,7 @@ export const StorageTab: React.FC = () => { )} @@ -650,9 +654,9 @@ export const StorageTab: React.FC = () => { setNewRow(null)}> - New Row + {t("storage.newRowTitle")} - Add a new row to the {selectedTable} table. + {t("storage.newRowDesc", { table: selectedTable })} {newRow && tableData && ( @@ -663,7 +667,7 @@ export const StorageTab: React.FC = () => { {column.name} {column.notnull && ( - (Required) + ({t("storage.required")}) )} @@ -695,8 +699,8 @@ export const StorageTab: React.FC = () => { /> )}

- Type: {column.type_name} - {column.dflt_value && `, Default: ${column.dflt_value}`} + {t("storage.type")}: {column.type_name} + {column.dflt_value && `, ${t("storage.default")}: ${column.dflt_value}`}

))} @@ -704,7 +708,7 @@ export const StorageTab: React.FC = () => { )} @@ -724,10 +728,9 @@ export const StorageTab: React.FC = () => { setDeletingRow(null)}> - Delete Row + {t("storage.deleteRow")} - Are you sure you want to delete this row? This action cannot be - undone. + {t("storage.deleteRowDesc")} {deletingRow && ( @@ -750,7 +753,7 @@ export const StorageTab: React.FC = () => { )} @@ -771,18 +774,15 @@ export const StorageTab: React.FC = () => { - Reset Database + {t("storage.resetDatabase")} - This will delete all data and recreate the database with its default structure - (empty tables for agents, agent_runs, and app_settings). The database will be - restored to the same state as when you first installed the app. This action - cannot be undone. + {t("storage.resetDatabaseDesc")}
- All your agents, runs, and settings will be permanently deleted! + {t("storage.resetWarning")}
@@ -790,7 +790,7 @@ export const StorageTab: React.FC = () => { variant="outline" onClick={() => setShowResetConfirm(false)} > - Cancel + {t("common.cancel")} @@ -811,19 +811,19 @@ export const StorageTab: React.FC = () => { - SQL Query Editor + {t("storage.sqlEditor")} - Execute raw SQL queries on the database. Use with caution. + {t("storage.sqlEditorDesc")}
- +