diff --git a/.env.example b/.env.example index b0a47571b..29f49b461 100644 --- a/.env.example +++ b/.env.example @@ -15,10 +15,10 @@ API_I18N_ENABLED=true API_HOST=localhost API_PORT=8000 -# Supported languages: en_US, en_GB, zh-Hans, zh-Hant -# You can interact with the agent in your preferred language. +# Supported languages: en, zh_CN, zh_TW, ja +# You can interact with the agent in your preferred language. # This variable is mainly intended for frontend use; setting LANG is not required. -LANG=en-US +LANG=en TIMEZONE=America/New_York PYTHONIOENCODING=utf-8 diff --git a/frontend/bun.lock b/frontend/bun.lock index 8c20803e5..f80651fa2 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -43,12 +43,14 @@ "dayjs": "^1.11.19", "echarts": "^6.0.0", "framer-motion": "^12.23.26", + "i18next": "^25.7.2", "isbot": "5.1.31", "lucide-react": "^0.559.0", "mutative": "^1.3.0", "next-themes": "^0.4.6", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-i18next": "^16.5.0", "react-markdown": "^10.1.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", @@ -135,6 +137,8 @@ "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], @@ -793,6 +797,8 @@ "headers-polyfill": ["headers-polyfill@4.0.3", "", {}, "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ=="], + "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=="], @@ -803,6 +809,8 @@ "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + "i18next": ["i18next@25.7.2", "", { "dependencies": { "@babel/runtime": "^7.28.4" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-58b4kmLpLv1buWUEwegMDUqZVR5J+rT+WTRFaBGL7lxDuJQQ0NrJFrq+eT2N94aYVR1k1Sr13QITNOL88tZCuw=="], + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -1135,6 +1143,8 @@ "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], + "react-i18next": ["react-i18next@16.5.0", "", { "dependencies": { "@babel/runtime": "^7.27.6", "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-IMpPTyCTKxEj8klCrLKUTIUa8uYTd851+jcu2fJuUB9Agkk9Qq8asw4omyeHVnOXHrLgQJGTm5zTvn8HpaPiqw=="], + "react-markdown": ["react-markdown@10.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-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="], @@ -1343,6 +1353,8 @@ "vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="], + "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-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], diff --git a/frontend/package.json b/frontend/package.json index 84ac86977..02aec7a9b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -56,12 +56,14 @@ "dayjs": "^1.11.19", "echarts": "^6.0.0", "framer-motion": "^12.23.26", + "i18next": "^25.7.2", "isbot": "5.1.31", "lucide-react": "^0.559.0", "mutative": "^1.3.0", "next-themes": "^0.4.6", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-i18next": "^16.5.0", "react-markdown": "^10.1.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", diff --git a/frontend/src/api/agent.ts b/frontend/src/api/agent.ts index 54d9278f8..bf7efc191 100644 --- a/frontend/src/api/agent.ts +++ b/frontend/src/api/agent.ts @@ -2,11 +2,17 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { VALUECELL_AGENT } from "@/constants/agent"; import { API_QUERY_KEYS } from "@/constants/api"; import { type ApiResponse, apiClient } from "@/lib/api-client"; +import { useLanguage } from "@/store/settings-store"; import type { AgentInfo } from "@/types/agent"; export const useGetAgentInfo = (params: { agentName: string }) => { + const language = useLanguage(); + return useQuery({ - queryKey: API_QUERY_KEYS.AGENT.agentInfo(Object.values(params)), + queryKey: API_QUERY_KEYS.AGENT.agentInfo([ + ...Object.values(params), + language, + ]), queryFn: async () => { // Return hardcoded data for ValueCellAgent if (params.agentName === "ValueCellAgent") { @@ -14,7 +20,7 @@ export const useGetAgentInfo = (params: { agentName: string }) => { } // Fetch from API for other agents return apiClient.get>( - `/agents/by-name/${params.agentName}`, + `/agents/by-name/${params.agentName}?language=${language}`, ); }, select: (data) => data.data, @@ -24,11 +30,16 @@ export const useGetAgentInfo = (params: { agentName: string }) => { export const useGetAgentList = ( params: { enabled_only: string } = { enabled_only: "false" }, ) => { + const language = useLanguage(); + return useQuery({ - queryKey: API_QUERY_KEYS.AGENT.agentList(Object.values(params)), + queryKey: API_QUERY_KEYS.AGENT.agentList([ + ...Object.values(params), + language, + ]), queryFn: () => apiClient.get>( - `/agents/?enabled_only=${params.enabled_only}`, + `/agents/?enabled_only=${params.enabled_only}&language=${language}`, ), select: (data) => data.data.agents, }); diff --git a/frontend/src/api/stock.ts b/frontend/src/api/stock.ts index 1e615b942..42321f29f 100644 --- a/frontend/src/api/stock.ts +++ b/frontend/src/api/stock.ts @@ -1,10 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { - API_QUERY_KEYS, - USER_LANGUAGE, - VALUECELL_BACKEND_URL, -} from "@/constants/api"; +import { API_QUERY_KEYS, VALUECELL_BACKEND_URL } from "@/constants/api"; import { type ApiResponse, apiClient } from "@/lib/api-client"; +import { useLanguage } from "@/store/settings-store"; import { useSystemStore } from "@/store/system-store"; import type { Stock, @@ -22,17 +19,20 @@ export const useGetWatchlist = () => select: (data) => data.data, }); -export const useGetStocksList = (params: { query: string }) => - useQuery({ - queryKey: API_QUERY_KEYS.STOCK.stockSearch(Object.values(params)), +export const useGetStocksList = (params: { query: string }) => { + const language = useLanguage(); + + return useQuery({ + queryKey: API_QUERY_KEYS.STOCK.stockSearch([params.query, language]), queryFn: ({ signal }) => apiClient.get>( - `watchlist/asset/search?q=${params.query}&language=${USER_LANGUAGE}`, + `watchlist/asset/search?q=${params.query}&language=${language}`, { signal }, ), select: (data) => data.data.results, enabled: !!params.query, }); +}; export const useAddStockToWatchlist = () => { const queryClient = useQueryClient(); diff --git a/frontend/src/api/system.ts b/frontend/src/api/system.ts index e3d8c9bd1..bcdde1d7f 100644 --- a/frontend/src/api/system.ts +++ b/frontend/src/api/system.ts @@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { API_QUERY_KEYS, VALUECELL_BACKEND_URL } from "@/constants/api"; import { type ApiResponse, apiClient } from "@/lib/api-client"; +import { useLanguage } from "@/store/settings-store"; import { useSystemStore } from "@/store/system-store"; import type { StrategyDetail, @@ -68,22 +69,29 @@ export const useSignOut = () => { export const useGetStrategyList = ( params: { limit: number; days: number } = { limit: 10, days: 7 }, ) => { + const language = useLanguage(); + return useQuery({ - queryKey: API_QUERY_KEYS.SYSTEM.strategyList(Object.values(params)), + queryKey: API_QUERY_KEYS.SYSTEM.strategyList([ + ...Object.values(params), + language, + ]), queryFn: () => apiClient.get>( - `${VALUECELL_BACKEND_URL}/strategy/list?limit=${params.limit}&days=${params.days}`, + `${VALUECELL_BACKEND_URL}/strategy/list?limit=${params.limit}&days=${params.days}&language=${language}`, ), select: (data) => data.data, }); }; export const useGetStrategyDetail = (id: number | null) => { + const language = useLanguage(); + return useQuery({ - queryKey: API_QUERY_KEYS.SYSTEM.strategyDetail([id ?? ""]), + queryKey: API_QUERY_KEYS.SYSTEM.strategyDetail([id ?? "", language]), queryFn: () => apiClient.get>( - `${VALUECELL_BACKEND_URL}/strategy/detail/${id}`, + `${VALUECELL_BACKEND_URL}/strategy/detail/${id}?language=${language}`, ), select: (data) => data.data, enabled: !!id, @@ -122,15 +130,21 @@ export const usePublishStrategy = () => { * @param region - Optional region override for testing (e.g., "cn" or "default"). * In development, you can set this to test different regions. */ -export const useGetDefaultTickers = (region?: string) => - useQuery({ - queryKey: ["system", "default-tickers", region], +export const useGetDefaultTickers = (region?: string) => { + const language = useLanguage(); + + return useQuery({ + queryKey: ["system", "default-tickers", region, language], queryFn: () => { - const params = region ? `?region=${region}` : ""; + const regionParam = region ? `region=${region}` : ""; + const langParam = `language=${language}`; + const params = [regionParam, langParam].filter(Boolean).join("&"); + return apiClient.get>( - `system/default-tickers${params}`, + `system/default-tickers?${params}`, ); }, select: (data) => data.data, staleTime: 1000 * 60 * 60, // Cache for 1 hour, region doesn't change frequently }); +}; diff --git a/frontend/src/app/agent/components/agent-view/common-agent-area.tsx b/frontend/src/app/agent/components/agent-view/common-agent-area.tsx index 9881b48a8..cd262d04c 100644 --- a/frontend/src/app/agent/components/agent-view/common-agent-area.tsx +++ b/frontend/src/app/agent/components/agent-view/common-agent-area.tsx @@ -1,5 +1,6 @@ import { useQueryClient } from "@tanstack/react-query"; import { type FC, memo, useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { Navigate, useLocation, @@ -42,6 +43,7 @@ interface CommonAgentAreaProps { } const CommonAgentAreaContent: FC = ({ agentName }) => { + const { t } = useTranslation(); const { data: agent, isLoading: isLoadingAgent } = useGetAgentInfo({ agentName: agentName ?? "", }); @@ -208,7 +210,7 @@ const CommonAgentAreaContent: FC = ({ agentName }) => { <> = ({ agentName }) => { value={inputValue} onChange={handleInputChange} onSend={handleSendMessage} - placeholder="Type your message..." + placeholder={t("chat.input.placeholder")} disabled={isStreaming} variant="chat" /> diff --git a/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx b/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx index bc4371f4b..9cf09f962 100644 --- a/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx +++ b/frontend/src/app/agent/components/agent-view/strategy-agent-area.tsx @@ -1,5 +1,6 @@ import { Plus } from "lucide-react"; import { type FC, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useDeleteStrategy, useGetStrategyDetails, @@ -34,6 +35,7 @@ const EmptyIllustration = () => ( ); const StrategyAgentArea: FC = () => { + const { t } = useTranslation(); const { data: strategies = [], isLoading: isLoadingStrategies } = useGetStrategyList(); const [selectedStrategy, setSelectedStrategy] = useState( @@ -80,7 +82,7 @@ const StrategyAgentArea: FC = () => {
{/* Left section: Strategy list */}
-

Trading Strategies

+

{t("strategy.title")}

{strategies && strategies.length > 0 ? ( = () => { ) : (
-
-

No trading strategies

-

Create your first trading strategy

+

{t("strategy.noStrategies")}

+

{t("strategy.createFirst")}

@@ -109,7 +110,7 @@ const StrategyAgentArea: FC = () => { className="w-full gap-3 rounded-lg py-4 text-base" > - Add trading strategy + {t("strategy.add")}
@@ -135,7 +136,7 @@ const StrategyAgentArea: FC = () => {

- No running strategies + {t("strategy.noStrategies")}

)} diff --git a/frontend/src/app/agent/components/chat-conversation/chat-conversation-header.tsx b/frontend/src/app/agent/components/chat-conversation/chat-conversation-header.tsx index d5582e148..03398376f 100644 --- a/frontend/src/app/agent/components/chat-conversation/chat-conversation-header.tsx +++ b/frontend/src/app/agent/components/chat-conversation/chat-conversation-header.tsx @@ -1,5 +1,6 @@ import { MessageCircle, Settings } from "lucide-react"; import { type FC, memo } from "react"; +import { useTranslation } from "react-i18next"; import { Link } from "react-router"; import { Button } from "@/components/ui/button"; import { @@ -16,6 +17,7 @@ interface ChatConversationHeaderProps { } const ChatConversationHeader: FC = ({ agent }) => { + const { t } = useTranslation(); return (
@@ -44,7 +46,7 @@ const ChatConversationHeader: FC = ({ agent }) => { - New Conversation + {t("chat.newConversation")} @@ -58,7 +60,7 @@ const ChatConversationHeader: FC = ({ agent }) => { - Settings + {t("chat.settings")}
diff --git a/frontend/src/app/agent/components/chat-conversation/chat-input-area.tsx b/frontend/src/app/agent/components/chat-conversation/chat-input-area.tsx index 4b8f43a70..4b86e9cc6 100644 --- a/frontend/src/app/agent/components/chat-conversation/chat-input-area.tsx +++ b/frontend/src/app/agent/components/chat-conversation/chat-input-area.tsx @@ -1,5 +1,6 @@ import { ArrowUp } from "lucide-react"; import { type FC, memo } from "react"; +import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import ScrollTextarea from "@/components/valuecell/scroll/scroll-textarea"; import { cn } from "@/lib/utils"; @@ -20,11 +21,14 @@ const ChatInputArea: FC = ({ onChange, onSend, onKeyDown, - placeholder = "Type your message...", + placeholder, disabled = false, className, variant = "chat", }) => { + const { t } = useTranslation(); + const resolvedPlaceholder = placeholder ?? t("chat.input.placeholder"); + const handleKeyDown = async (e: React.KeyboardEvent) => { // Send message on Enter key (excluding Shift+Enter line breaks and IME composition state) if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { @@ -56,7 +60,7 @@ const ChatInputArea: FC = ({ value={value} onInput={(e) => onChange(e.currentTarget.value)} onKeyDown={handleKeyDown} - placeholder={placeholder} + placeholder={resolvedPlaceholder} maxHeight={120} minHeight={24} disabled={disabled} diff --git a/frontend/src/app/agent/components/strategy-items/modals/create-strategy-modal.tsx b/frontend/src/app/agent/components/strategy-items/modals/create-strategy-modal.tsx index def7ed8f3..40542004f 100644 --- a/frontend/src/app/agent/components/strategy-items/modals/create-strategy-modal.tsx +++ b/frontend/src/app/agent/components/strategy-items/modals/create-strategy-modal.tsx @@ -2,6 +2,7 @@ import { useStore } from "@tanstack/react-form"; import { AlertCircleIcon } from "lucide-react"; import type { FC, RefObject } from "react"; import { memo, useImperativeHandle, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useGetModelProviderDetail } from "@/api/setting"; import { useCreateStrategy, @@ -44,20 +45,21 @@ interface CreateStrategyModalProps { ref?: RefObject; } -const STEPS = [ - { step: 1, title: "AI Models" }, - { step: 2, title: "Exchanges" }, - { step: 3, title: "Trading strategy" }, -]; - const CreateStrategyModal: FC = ({ ref, children, }) => { + const { t } = useTranslation(); const [open, setOpen] = useState(false); const [currentStep, setCurrentStep] = useState(1); const [error, setError] = useState(null); + const STEPS = [ + { step: 1, title: t("strategy.create.steps.aiModels") }, + { step: 2, title: t("strategy.create.steps.exchanges") }, + { step: 3, title: t("strategy.create.steps.tradingStrategy") }, + ]; + const { data: prompts = [] } = useGetStrategyPrompts(); const { data: strategies = [] } = useGetStrategyList(); const { mutateAsync: createStrategy, isPending: isCreatingStrategy } = @@ -191,7 +193,9 @@ const CreateStrategyModal: FC = ({ >
-

Add trading strategy

+

+ {t("strategy.create.title")} +

@@ -220,7 +224,7 @@ const CreateStrategyModal: FC = ({ {error && ( - Error Creating Strategy + {t("strategy.create.error")} {error} )} @@ -232,7 +236,9 @@ const CreateStrategyModal: FC = ({ onClick={currentStep === 1 ? resetAll : handleBack} className="border-gray-100 py-4 font-semibold text-base" > - {currentStep === 1 ? "Cancel" : "Back"} + {currentStep === 1 + ? t("strategy.action.cancel") + : t("strategy.action.back")}
diff --git a/frontend/src/app/agent/components/strategy-items/modals/strategy-detail-modal.tsx b/frontend/src/app/agent/components/strategy-items/modals/strategy-detail-modal.tsx index 8756edc5a..8e6c42d57 100644 --- a/frontend/src/app/agent/components/strategy-items/modals/strategy-detail-modal.tsx +++ b/frontend/src/app/agent/components/strategy-items/modals/strategy-detail-modal.tsx @@ -6,6 +6,7 @@ import { useRef, useState, } from "react"; +import { useTranslation } from "react-i18next"; import { useStrategyPerformance } from "@/api/strategy"; import { ValueCellAgentPng } from "@/assets/png"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -32,6 +33,7 @@ interface StrategyDetailModalProps { } const StrategyDetailModal: FC = ({ ref }) => { + const { t } = useTranslation(); const stockColors = useStockColors(); const [open, setOpen] = useState(false); const [strategyId, setStrategyId] = useState(null); @@ -64,11 +66,13 @@ const StrategyDetailModal: FC = ({ ref }) => { aria-describedby={undefined} > - Strategy Details + {t("strategy.detail.title")}
{isLoadingStrategyDetail || !strategyDetail ? ( -
Loading details...
+
+ {t("strategy.detail.loading")} +
) : (
@@ -94,34 +98,43 @@ const StrategyDetailModal: FC = ({ ref }) => { > {numberFixed(strategyDetail.return_rate_pct, 2)}%
-
Return Rate
+
+ {t("strategy.detail.returnRate")} +
-

Strategy Type

- {strategyDetail.strategy_type} +

{t("strategy.detail.strategyType")}

+ + {t(`strategy.types.${strategyDetail.strategy_type}`)} + -

Model Provider

- {strategyDetail.llm_provider} +

{t("strategy.detail.modelProvider")}

+ + {t(`strategy.providers.${strategyDetail.llm_provider}`) || + strategyDetail.llm_provider} + -

Model ID

+

{t("strategy.detail.modelId")}

{strategyDetail.llm_model_id} -

Initial Capital

+

{t("strategy.detail.initialCapital")}

{strategyDetail.initial_capital} -

Max Leverage

+

{t("strategy.detail.maxLeverage")}

{strategyDetail.max_leverage}x -

Trading Symbols

+

{t("strategy.detail.tradingSymbols")}

{strategyDetail.symbols.join(", ")}
- Prompt + + {t("strategy.detail.prompt")} +

{strategyDetail.prompt}

@@ -163,7 +176,7 @@ const StrategyDetailModal: FC = ({ ref }) => { }); }} > - Duplicate + {t("strategy.action.duplicate")} diff --git a/frontend/src/app/agent/components/strategy-items/portfolio-positions-group.tsx b/frontend/src/app/agent/components/strategy-items/portfolio-positions-group.tsx index 41b7109d3..2ee02c0de 100644 --- a/frontend/src/app/agent/components/strategy-items/portfolio-positions-group.tsx +++ b/frontend/src/app/agent/components/strategy-items/portfolio-positions-group.tsx @@ -1,5 +1,6 @@ import { LineChart, Wallet } from "lucide-react"; import { type FC, memo, useRef } from "react"; +import { useTranslation } from "react-i18next"; import { useStrategyPerformance } from "@/api/strategy"; import { usePublishStrategy } from "@/api/system"; import { ValueCellAgentPng } from "@/assets/png"; @@ -101,6 +102,7 @@ const PortfolioPositionsGroup: FC = ({ positions, strategy, }) => { + const { t } = useTranslation(); const sharePortfolioModalRef = useRef(null); const stockColors = useStockColors(); @@ -149,19 +151,21 @@ const PortfolioPositionsGroup: FC = ({

- Portfolio Value History + {t("strategy.portfolio.title")}

{isTauriApp && (isLogin ? ( - Share to Social + {" "} + {t("strategy.action.shareToSocial")} = ({ ) : ( )}{" "} - Share to Ranking + {t("strategy.action.shareToRanking")} ) : ( ))} @@ -187,19 +192,25 @@ const PortfolioPositionsGroup: FC = ({
-

Total Equity

+

+ {t("strategy.portfolio.totalEquity")} +

{numberFixed(summary?.total_value, 4)}

-

Available Balance

+

+ {t("strategy.portfolio.availableBalance")} +

{numberFixed(summary?.cash, 4)}

-

Total P&L

+

+ {t("strategy.portfolio.totalPnl")} +

= ({

- No portfolio value data + {t("strategy.portfolio.noData")}

- Portfolio value chart will appear once trading begins + {t("strategy.portfolio.noDataDesc")}

@@ -234,25 +245,37 @@ const PortfolioPositionsGroup: FC = ({ {/* Positions Section */}
-

Positions

+

+ {t("strategy.positions.title")} +

{hasPositions ? ( -

Symbol

+

+ {t("strategy.positions.symbol")} +

-

Type

+

+ {t("strategy.positions.type")} +

-

Leverage

+

+ {t("strategy.positions.leverage")} +

-

Quantity

+

+ {t("strategy.positions.quantity")} +

-

P&L

+

+ {t("strategy.positions.pnl")} +

@@ -273,10 +296,10 @@ const PortfolioPositionsGroup: FC = ({

- No open positions + {t("strategy.positions.noOpen")}

- Positions will appear here when trades are opened + {t("strategy.positions.noOpenDesc")}

diff --git a/frontend/src/app/agent/components/strategy-items/strategy-compose-list.tsx b/frontend/src/app/agent/components/strategy-items/strategy-compose-list.tsx index 8d1ca7930..705bb4f5e 100644 --- a/frontend/src/app/agent/components/strategy-items/strategy-compose-list.tsx +++ b/frontend/src/app/agent/components/strategy-items/strategy-compose-list.tsx @@ -1,5 +1,6 @@ import { ChevronDown, History } from "lucide-react"; import { type FC, memo, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { ValueCellAgentPng } from "@/assets/png"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -30,13 +31,14 @@ interface StrategyComposeItemProps { } const StrategyComposeItem: FC = ({ compose }) => { + const { t } = useTranslation(); const [isReasoningOpen, setIsReasoningOpen] = useState(false); return (

- {`Cycle #${compose.cycle_index}`} + {t("strategy.history.cycle", { index: compose.cycle_index })}

{TimeUtils.formatUTC(compose.created_at, TIME_FORMATS.DATETIME)} @@ -44,7 +46,9 @@ const StrategyComposeItem: FC = ({ compose }) => {
{/* AI Reasoning Logic */} -

AI reasoning logic

+

+ {t("strategy.history.aiReasoning")} +

= ({ compose }) => { {/* Perform Operation */} {compose.actions.length > 0 && ( <> -

Perform operation

+

+ {t("strategy.history.operation")} +

{compose.actions.map((action) => ( ))} @@ -79,6 +85,7 @@ const StrategyComposeItem: FC = ({ compose }) => { }; const ActionItem: FC<{ action: StrategyAction }> = ({ action }) => { + const { t } = useTranslation(); const stockColors = useStockColors(); const formatHoldingTime = (ms?: number) => { @@ -166,7 +173,7 @@ const ActionItem: FC<{ action: StrategyAction }> = ({ action }) => { {/* Data Grid */}
- Time + {t("strategy.history.details.time")} {TimeUtils.formatUTC( action.exit_at ?? action.entry_at, @@ -174,24 +181,26 @@ const ActionItem: FC<{ action: StrategyAction }> = ({ action }) => { )} - Price + {t("strategy.history.details.price")} {priceRange} - Quantity + {t("strategy.history.details.quantity")} {action.quantity} - Holding time + {t("strategy.history.details.holdingTime")} {formatHoldingTime(action.holding_time_ms)} - Trading Fee + {t("strategy.history.details.tradingFee")} {-numberFixed(action.fee_cost, 4)}
{/* Reasoning Box */}
-

Reasoning

+

+ {t("strategy.history.details.reasoning")} +

{action.rationale}

@@ -210,15 +219,18 @@ const StrategyComposeList: FC = ({ composes, tradingMode, }) => { + const { t } = useTranslation(); return (

- Trading History + {t("strategy.history.title")}

- {tradingMode === "live" ? "Live Trading" : "Virtual Trading"} + {tradingMode === "live" + ? t("strategy.history.live") + : t("strategy.history.virtual")}

@@ -237,10 +249,10 @@ const StrategyComposeList: FC = ({

- No trade history + {t("strategy.history.empty.title")}

- Your completed trades will appear here + {t("strategy.history.empty.desc")}

diff --git a/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx b/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx index b6ebe96cd..16ea1b930 100644 --- a/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx +++ b/frontend/src/app/agent/components/strategy-items/trade-strategy-group.tsx @@ -1,5 +1,6 @@ import { Copy, Eye, MoreVertical, Plus, TrendingUp } from "lucide-react"; import { type FC, memo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useStrategyPerformance } from "@/api/strategy"; import { DeleteStrategy, StrategyStatus } from "@/assets/svg"; import { @@ -61,6 +62,7 @@ const TradeStrategyCard: FC = ({ onStop, onDelete, }) => { + const { t } = useTranslation(); const stockColors = useStockColors(); const changeType = getChangeType(strategy.total_pnl_pct); @@ -94,11 +96,13 @@ const TradeStrategyCard: FC = ({
{strategy.strategy_type && (

- {strategy.strategy_type} + {t(`strategy.types.${strategy.strategy_type}`)}

)}

- {strategy.trading_mode === "live" ? "Live" : "Virtual"} + {strategy.trading_mode === "live" + ? t("strategy.status.live") + : t("strategy.status.virtual")}

@@ -123,7 +127,9 @@ const TradeStrategyCard: FC = ({ {strategy.status === "stopped" && strategy.stop_reason ? ( -

Stopped

+

+ {t("strategy.status.stopped")} +

{strategy.stop_reason} @@ -142,23 +148,29 @@ const TradeStrategyCard: FC = ({ )}

- {strategy.status === "running" ? "Running" : "Stopped"} + {strategy.status === "running" + ? t("strategy.status.running") + : t("strategy.status.stopped")}

- Stop Trading Strategy? + + {t("strategy.action.stop")} + - Stopping the strategy "{strategy.strategy_name}" will stop - it immediately and trigger a forced liquidation. Do you want - to proceed? + {t("strategy.action.stopDesc", { + name: strategy.strategy_name, + })} - Cancel + + {t("strategy.action.cancel")} + - Confirm Stop + {t("strategy.action.confirmStop")} @@ -178,7 +190,7 @@ const TradeStrategyCard: FC = ({ } > - Details + {t("strategy.action.details")} { @@ -215,14 +227,16 @@ const TradeStrategyCard: FC = ({ }} > - Duplicate + {t("strategy.action.duplicate")} setIsDeleting(true)}> {" "} - Delete + + {t("strategy.action.delete")} + @@ -232,17 +246,19 @@ const TradeStrategyCard: FC = ({ - Delete Strategy? + + {t("strategy.action.deleteTitle")} + - Deleting the strategy "{strategy.strategy_name}" will stop it - immediately and trigger a forced liquidation. Do you want to - proceed? + {t("strategy.action.deleteDesc", { + name: strategy.strategy_name, + })} - Cancel + {t("strategy.action.cancel")} - Confirm Delete + {t("strategy.action.confirmDelete")} @@ -261,6 +277,7 @@ const TradeStrategyGroup: FC = ({ onStrategyStop, onStrategyDelete, }) => { + const { t } = useTranslation(); const hasStrategies = strategies.length > 0; return ( @@ -288,10 +305,10 @@ const TradeStrategyGroup: FC = ({

- No trading strategies + {t("strategy.noStrategies")}

- Create your first strategy to start trading + {t("strategy.createFirst")}

@@ -305,7 +322,7 @@ const TradeStrategyGroup: FC = ({ className="w-full gap-3 rounded-lg py-4 text-base" > - Add trading strategy + {t("strategy.create.title")} diff --git a/frontend/src/app/agent/config.tsx b/frontend/src/app/agent/config.tsx index 78b983c96..b7d17598f 100644 --- a/frontend/src/app/agent/config.tsx +++ b/frontend/src/app/agent/config.tsx @@ -1,4 +1,5 @@ import { ArrowRight } from "lucide-react"; +import { useTranslation } from "react-i18next"; import { Link, Navigate, useParams } from "react-router"; import { useEnableAgent, useGetAgentInfo } from "@/api/agent"; import { Button } from "@/components/ui/button"; @@ -8,6 +9,7 @@ import { MarkdownRenderer } from "@/components/valuecell/renderer"; import type { Route } from "./+types/config"; export default function AgentConfig() { + const { t } = useTranslation(); const { agentName } = useParams(); const { data: agent, isLoading: isLoadingAgent } = useGetAgentInfo({ agentName: agentName ?? "", @@ -52,14 +54,14 @@ export default function AgentConfig() {
{agentName !== "ValueCellAgent" && ( )} - Chat + {t("agent.config.chat")}
) : ( @@ -68,7 +70,7 @@ export default function AgentConfig() { to={`/agent/${agentName}`} onClick={handleEnableAgent} > - Collect and chat + {t("agent.config.collectAndChat")} )} diff --git a/frontend/src/app/home/_layout.tsx b/frontend/src/app/home/_layout.tsx index c61c32997..50ceed701 100644 --- a/frontend/src/app/home/_layout.tsx +++ b/frontend/src/app/home/_layout.tsx @@ -1,12 +1,14 @@ import { Plus } from "lucide-react"; +import { useTranslation } from "react-i18next"; import { Outlet } from "react-router"; import { Button } from "@/components/ui/button"; import { StockList, StockSearchModal } from "./components"; export default function HomeLayout() { + const { t } = useTranslation(); return (
-

👋 Welcome to ValueCell !

+

{t("home.welcome")}

@@ -22,7 +24,7 @@ export default function HomeLayout() { className="mx-5 mb-6 font-bold text-sm hover:bg-gray-200" > - Add Stocks + {t("home.stock.add")} diff --git a/frontend/src/app/home/components/stock-history-chart.tsx b/frontend/src/app/home/components/stock-history-chart.tsx index 53dfbfc0b..56d402c08 100644 --- a/frontend/src/app/home/components/stock-history-chart.tsx +++ b/frontend/src/app/home/components/stock-history-chart.tsx @@ -13,9 +13,9 @@ interface StockHistoryChartProps { } const INTERVALS: { label: string; value: StockInterval }[] = [ - { label: "24h", value: "1m" }, - { label: "7d", value: "1h" }, - { label: "30d", value: "1d" }, + { label: "24H", value: "1m" }, + { label: "7D", value: "1h" }, + { label: "30D", value: "1d" }, ]; export const StockHistoryChart = ({ diff --git a/frontend/src/app/home/components/stock-list.tsx b/frontend/src/app/home/components/stock-list.tsx index 88708cb87..23326001f 100644 --- a/frontend/src/app/home/components/stock-list.tsx +++ b/frontend/src/app/home/components/stock-list.tsx @@ -1,4 +1,5 @@ import { memo, useMemo } from "react"; +import { useTranslation } from "react-i18next"; import { useLocation } from "react-router"; import { useGetStockPrice, useGetWatchlist } from "@/api/stock"; import { @@ -9,6 +10,7 @@ import { import type { Stock } from "@/types/stock"; function StockList() { + const { t } = useTranslation(); const { pathname } = useLocation(); const { data: stockList } = useGetWatchlist(); @@ -46,7 +48,7 @@ function StockList() { return ( - My Watchlist + {t("home.watchlist")}
{stockData?.map((stock) => ( diff --git a/frontend/src/app/home/components/stock-search-modal.tsx b/frontend/src/app/home/components/stock-search-modal.tsx index e815a6783..a5cae80bd 100644 --- a/frontend/src/app/home/components/stock-search-modal.tsx +++ b/frontend/src/app/home/components/stock-search-modal.tsx @@ -1,5 +1,6 @@ import { Plus, Search, X } from "lucide-react"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { useAddStockToWatchlist, useGetStocksList, @@ -23,6 +24,7 @@ interface StockSearchModalProps { } const StockItem = ({ stock }: { stock: Stock }) => { + const { t } = useTranslation(); const { mutateAsync: addStockToWatchlist, isPending: isPendingAddStockToWatchlist, @@ -60,22 +62,23 @@ const StockItem = ({ stock }: { stock: Stock }) => { {isPendingAddStockToWatchlist && ( <> - Watching... + {t("home.search.action.watching")} )} {!isStockInWatchlist && ( <> - Watchlist + {t("home.search.action.watch")} )} - {isStockInWatchlist && <>Watched} + {isStockInWatchlist && <>{t("home.search.action.watched")}}
); }; export default function StockSearchModal({ children }: StockSearchModalProps) { + const { t } = useTranslation(); const [query, setQuery] = useState(""); const debouncedQuery = useDebounce(query, 300); const { data: stockList, isLoading } = useGetStocksList({ @@ -110,7 +113,7 @@ export default function StockSearchModal({ children }: StockSearchModalProps) { >
- Stock Search + {t("home.search.title")}
@@ -133,7 +136,7 @@ export default function StockSearchModal({ children }: StockSearchModalProps) {
{isLoading ? (

- Searching... + {t("home.search.searching")}

) : filteredStockList && filteredStockList.length > 0 ? (
@@ -147,7 +150,7 @@ export default function StockSearchModal({ children }: StockSearchModalProps) { stockList && filteredStockList.length === 0 && (

- No related stocks found + {t("home.search.noResults")}

) )} diff --git a/frontend/src/app/home/home.tsx b/frontend/src/app/home/home.tsx index 0615eb06d..2a99d2641 100644 --- a/frontend/src/app/home/home.tsx +++ b/frontend/src/app/home/home.tsx @@ -1,8 +1,11 @@ import { useState } from "react"; +import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; import { useAllPollTaskList } from "@/api/conversation"; +import { IconGroupPng, MessageGroupPng, TrendPng } from "@/assets/png"; +import { AutoTrade, NewsPush, ResearchReport } from "@/assets/svg"; import TradingViewTickerTape from "@/components/tradingview/tradingview-ticker-tape"; -import { agentSuggestions } from "@/mock/agent-data"; +import SvgIcon from "@/components/valuecell/icon/svg-icon"; import ChatInputArea from "../agent/components/chat-conversation/chat-input-area"; import { AgentSuggestionsList, AgentTaskCards } from "./components"; @@ -17,6 +20,7 @@ const INDEX_SYMBOLS = [ ]; function Home() { + const { t, i18n } = useTranslation(); const navigate = useNavigate(); const [inputValue, setInputValue] = useState(""); @@ -26,11 +30,44 @@ function Home() { navigate(`/agent/${agentId}`); }; + const suggestions = [ + { + id: "ResearchAgent", + title: t("home.suggestions.research.title"), + icon: , + description: t("home.suggestions.research.description"), + bgColor: + "bg-gradient-to-r from-[#FFFFFF]/70 from-[5.05%] to-[#E7EFFF]/70 to-[100%]", + decorativeGraphics: IconGroup, + }, + { + id: "StrategyAgent", + title: t("home.suggestions.strategy.title"), + icon: , + description: t("home.suggestions.strategy.description"), + bgColor: + "bg-gradient-to-r from-[#FFFFFF]/70 from-[5.05%] to-[#EAE8FF]/70 to-[100%]", + decorativeGraphics: Trend, + }, + { + id: "NewsAgent", + title: t("home.suggestions.news.title"), + icon: , + description: t("home.suggestions.news.description"), + bgColor: + "bg-gradient-to-r from-[#FFFFFF]/70 from-[5.05%] to-[#FFE7FD]/70 to-[100%]", + decorativeGraphics: MessageGroup, + }, + ]; + return (
{allPollTaskList && allPollTaskList.length > 0 ? (
- +
@@ -50,10 +87,13 @@ function Home() {
) : (
- +

- 👋 Hello Investor! + {t("home.hello")}

({ + suggestions={suggestions.map((suggestion) => ({ ...suggestion, onClick: () => handleAgentClick(suggestion.id), }))} diff --git a/frontend/src/app/home/stock.tsx b/frontend/src/app/home/stock.tsx index 07861b92c..22b2018c5 100644 --- a/frontend/src/app/home/stock.tsx +++ b/frontend/src/app/home/stock.tsx @@ -1,5 +1,6 @@ import BackButton from "@valuecell/button/back-button"; import { memo } from "react"; +import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router"; import { useGetStockDetail, useRemoveStockFromWatchlist } from "@/api/stock"; import TradingViewAdvancedChart from "@/components/tradingview/tradingview-advanced-chart"; @@ -9,6 +10,7 @@ import { useIsLoggedIn } from "@/store/system-store"; import type { Route } from "./+types/stock"; function Stock() { + const { t, i18n } = useTranslation(); const { stockId } = useParams(); const navigate = useNavigate(); // Use stockId as ticker to fetch real data from API @@ -41,7 +43,7 @@ function Stock() { if (isDetailLoading) { return (
-
Loading stock data...
+
{t("home.stock.loading")}
); } @@ -51,7 +53,7 @@ function Stock() { return (
- Error loading stock data: {detailError?.message} + {t("home.stock.error", { message: detailError?.message })}
); @@ -71,7 +73,9 @@ function Stock() { onClick={handleRemoveStock} disabled={isRemovingStock} > - {isRemovingStock ? "Removing..." : "Remove"} + {isRemovingStock + ? t("home.stock.removing") + : t("home.stock.remove")}
@@ -84,14 +88,14 @@ function Stock() { interval="D" minHeight={420} theme="light" - locale="en" + locale={i18n.language} timezone="UTC" /> {/* )} */}
-

About

+

{t("home.stock.about")}

{stockDetailData?.properties.business_summary && (

@@ -102,20 +106,26 @@ function Stock() { {stockDetailData?.properties && (

- Sector: + + {t("home.stock.sector")} + {stockDetailData.properties.sector}
- Industry: + + {t("home.stock.industry")} + {stockDetailData.properties.industry}
{stockDetailData.properties.website && (
- Website: + + {t("home.stock.website")} + {/* Page Title */}

- Agent Market + {t("market.title")}

{/* Agent Cards Grid */} diff --git a/frontend/src/app/rank/board.tsx b/frontend/src/app/rank/board.tsx index 2be62975d..396212588 100644 --- a/frontend/src/app/rank/board.tsx +++ b/frontend/src/app/rank/board.tsx @@ -1,5 +1,6 @@ import { Eye } from "lucide-react"; import { useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useGetStrategyList } from "@/api/system"; import { ValueCellAgentPng } from "@/assets/png"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -25,6 +26,7 @@ import StrategyRemoteModal, { } from "./components/strategy-remote-modal"; export default function RankBoard() { + const { t } = useTranslation(); const [days, setDays] = useState(7); const strategyRemoteModalRef = useRef(null); @@ -50,9 +52,7 @@ export default function RankBoard() {
- - Profit Leaderboard - + {t("rank.title")} setDays(Number(val))} @@ -68,20 +68,22 @@ export default function RankBoard() {
- Rank - User - P&L - Strategy - Exchange - Model - Details + + {t("rank.table.rank")} + + {t("rank.table.user")} + {t("rank.table.pnl")} + {t("rank.table.strategy")} + {t("rank.table.exchange")} + {t("rank.table.model")} + {t("rank.table.details")} {isLoading ? ( - Loading... + {t("rank.table.loading")} ) : ( @@ -117,7 +119,9 @@ export default function RankBoard() { {numberFixed(strategy.return_rate_pct, 2)}% - {strategy.strategy_type} + + {t(`strategy.types.${strategy.strategy_type}`)} + - View + {t("agent.action.view")} diff --git a/frontend/src/app/rank/components/strategy-remote-modal.tsx b/frontend/src/app/rank/components/strategy-remote-modal.tsx index ee866b0f2..643040725 100644 --- a/frontend/src/app/rank/components/strategy-remote-modal.tsx +++ b/frontend/src/app/rank/components/strategy-remote-modal.tsx @@ -5,6 +5,7 @@ import { useRef, useState, } from "react"; +import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; import { useGetStrategyDetail } from "@/api/system"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; @@ -30,6 +31,7 @@ interface StrategyRemoteModalProps { } const StrategyRemoteModal: FC = ({ ref }) => { + const { t } = useTranslation(); const stockColors = useStockColors(); const navigate = useNavigate(); @@ -53,11 +55,13 @@ const StrategyRemoteModal: FC = ({ ref }) => { aria-describedby={undefined} > - Strategy Details + {t("strategy.detail.title")}
{isLoadingStrategyDetail || !strategyDetail ? ( -
Loading details...
+
+ {t("strategy.detail.loading")} +
) : (
@@ -81,34 +85,43 @@ const StrategyRemoteModal: FC = ({ ref }) => { > {numberFixed(strategyDetail.return_rate_pct, 2)}%
-
Return Rate
+
+ {t("strategy.detail.returnRate")} +
-

Strategy Type

- {strategyDetail.strategy_type} +

{t("strategy.detail.strategyType")}

+ + {t(`strategy.types.${strategyDetail.strategy_type}`)} + -

Model Provider

- {strategyDetail.llm_provider} +

{t("strategy.detail.modelProvider")}

+ + {t(`strategy.providers.${strategyDetail.llm_provider}`) || + strategyDetail.llm_provider} + -

Model ID

+

{t("strategy.detail.modelId")}

{strategyDetail.llm_model_id} -

Initial Capital

+

{t("strategy.detail.initialCapital")}

{strategyDetail.initial_capital} -

Max Leverage

+

{t("strategy.detail.maxLeverage")}

{strategyDetail.max_leverage}x -

Trading Symbols

+

{t("strategy.detail.tradingSymbols")}

{strategyDetail.symbols.join(", ")}
- Prompt + + {t("strategy.detail.prompt")} +

{strategyDetail.prompt}

@@ -120,7 +133,7 @@ const StrategyRemoteModal: FC = ({ ref }) => { diff --git a/frontend/src/app/setting/_layout.tsx b/frontend/src/app/setting/_layout.tsx index c81e83aaf..51b350009 100644 --- a/frontend/src/app/setting/_layout.tsx +++ b/frontend/src/app/setting/_layout.tsx @@ -1,4 +1,5 @@ import { Brain, Cpu, Settings } from "lucide-react"; +import { useTranslation } from "react-i18next"; import { NavLink, Outlet, useLocation } from "react-router"; import { Item, @@ -9,48 +10,39 @@ import { } from "@/components/ui/item"; import { cn } from "@/lib/utils"; -const settingNavItems = [ - { - id: "models", - icon: Cpu, - label: "Models", - path: "/setting", - }, - { - id: "general", - icon: Settings, - label: "General", - path: "/setting/general", - }, - { - id: "memory", - icon: Brain, - label: "Memory", - path: "/setting/memory", - }, - // { - // id: "language", - // icon: Globe, - // label: "Language", - // path: "/setting/language", - // }, - // { - // id: "about", - // icon: Info, - // label: "About us", - // path: "/setting/about", - // }, -]; - export default function SettingLayout() { + const { t } = useTranslation(); const location = useLocation(); + const settingNavItems = [ + { + id: "models", + icon: Cpu, + label: t("settings.nav.models"), + path: "/setting", + }, + { + id: "general", + icon: Settings, + label: t("settings.nav.general"), + path: "/setting/general", + }, + { + id: "memory", + icon: Brain, + label: t("settings.nav.memory"), + path: "/setting/memory", + }, + ]; + return (
{/* Left navigation */}