mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
feat: localize default strategy names by UI language at registration
- Pass `lang` from register request body to createDefaultStrategies - Support zh/en/id locales for strategy names and descriptions - Wrap strategy creation in a transaction to prevent partial writes - Frontend sends current UI language in register request body - Strategy list UI: 2-line clamp, unselected border, larger spacing, smaller font for non-zh
This commit is contained in:
+65
-16
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// handleLogout Add current token to blacklist
|
||||
@@ -60,6 +61,7 @@ func (s *Server) handleRegister(c *gin.Context) {
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Lang string `json:"lang"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -67,6 +69,11 @@ func (s *Server) handleRegister(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
lang := req.Lang
|
||||
if lang != "zh" && lang != "id" {
|
||||
lang = "en"
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
_, err = s.store.User().GetByEmail(req.Email)
|
||||
if err == nil {
|
||||
@@ -103,7 +110,7 @@ func (s *Server) handleRegister(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Initialize default model and exchange configs for user
|
||||
err = s.initUserDefaultConfigs(user.ID)
|
||||
err = s.initUserDefaultConfigs(user.ID, lang)
|
||||
if err != nil {
|
||||
logger.Infof("Failed to initialize user default configs: %v", err)
|
||||
}
|
||||
@@ -216,8 +223,8 @@ func (s *Server) handleResetPassword(c *gin.Context) {
|
||||
}
|
||||
|
||||
// initUserDefaultConfigs Initialize default configs for new user
|
||||
func (s *Server) initUserDefaultConfigs(userID string) error {
|
||||
if err := s.createDefaultStrategies(userID); err != nil {
|
||||
func (s *Server) initUserDefaultConfigs(userID string, lang string) error {
|
||||
if err := s.createDefaultStrategies(userID, lang); err != nil {
|
||||
logger.Warnf("Failed to create default strategies for user %s: %v", userID, err)
|
||||
// Non-fatal: user can create strategies manually
|
||||
}
|
||||
@@ -225,7 +232,35 @@ func (s *Server) initUserDefaultConfigs(userID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) createDefaultStrategies(userID string) error {
|
||||
func (s *Server) createDefaultStrategies(userID string, lang string) error {
|
||||
type strategyI18n struct {
|
||||
name, description string
|
||||
}
|
||||
type strategyLocale struct {
|
||||
balanced, conservative, aggressive strategyI18n
|
||||
}
|
||||
locales := map[string]strategyLocale{
|
||||
"zh": {
|
||||
balanced: strategyI18n{"均衡策略", "系统默认策略。均衡风险收益,适合大多数市场环境。5倍杠杆,最多3个仓位。"},
|
||||
conservative: strategyI18n{"稳健策略", "系统默认策略。低杠杆保守操作,优先保护本金。3倍杠杆,专注主流资产。"},
|
||||
aggressive: strategyI18n{"积极策略", "系统默认策略。高杠杆主动交易,更广泛的币种选择,适合经验丰富的交易者。10倍杠杆,最多5个仓位。"},
|
||||
},
|
||||
"en": {
|
||||
balanced: strategyI18n{"Balanced Strategy", "System default strategy. Balanced risk-reward, suitable for most market conditions. 5x leverage, up to 3 positions."},
|
||||
conservative: strategyI18n{"Conservative Strategy", "System default strategy. Low-leverage conservative trading, capital preservation first. 3x leverage, focused on major assets."},
|
||||
aggressive: strategyI18n{"Aggressive Strategy", "System default strategy. High-leverage active trading, wider asset selection, for experienced traders. 10x leverage, up to 5 positions."},
|
||||
},
|
||||
"id": {
|
||||
balanced: strategyI18n{"Strategi Seimbang", "Strategi default sistem. Risiko-reward seimbang, cocok untuk sebagian besar kondisi pasar. Leverage 5x, hingga 3 posisi."},
|
||||
conservative: strategyI18n{"Strategi Konservatif", "Strategi default sistem. Trading konservatif leverage rendah, utamakan perlindungan modal. Leverage 3x, fokus aset utama."},
|
||||
aggressive: strategyI18n{"Strategi Agresif", "Strategi default sistem. Trading aktif leverage tinggi, pilihan aset lebih luas, untuk trader berpengalaman. Leverage 10x, hingga 5 posisi."},
|
||||
},
|
||||
}
|
||||
locale, ok := locales[lang]
|
||||
if !ok {
|
||||
locale = locales["en"]
|
||||
}
|
||||
|
||||
type strategyDef struct {
|
||||
name string
|
||||
description string
|
||||
@@ -235,16 +270,16 @@ func (s *Server) createDefaultStrategies(userID string) error {
|
||||
|
||||
definitions := []strategyDef{
|
||||
{
|
||||
name: "均衡策略",
|
||||
description: "系统默认策略。均衡风险收益,适合大多数市场环境。5倍杠杆,最多3个仓位。",
|
||||
name: locale.balanced.name,
|
||||
description: locale.balanced.description,
|
||||
isActive: true,
|
||||
applyConfig: func(c *store.StrategyConfig) {
|
||||
// Uses default config as-is
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "稳健策略",
|
||||
description: "系统默认策略。低杠杆保守操作,优先保护本金。3倍杠杆,专注主流资产。",
|
||||
name: locale.conservative.name,
|
||||
description: locale.conservative.description,
|
||||
isActive: false,
|
||||
applyConfig: func(c *store.StrategyConfig) {
|
||||
c.RiskControl.BTCETHMaxLeverage = 3
|
||||
@@ -258,8 +293,8 @@ func (s *Server) createDefaultStrategies(userID string) error {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "积极策略",
|
||||
description: "系统默认策略。高杠杆主动交易,更广泛的币种选择,适合经验丰富的交易者。10倍杠杆,最多5个仓位。",
|
||||
name: locale.aggressive.name,
|
||||
description: locale.aggressive.description,
|
||||
isActive: false,
|
||||
applyConfig: func(c *store.StrategyConfig) {
|
||||
c.RiskControl.BTCETHMaxLeverage = 10
|
||||
@@ -276,8 +311,16 @@ func (s *Server) createDefaultStrategies(userID string) error {
|
||||
},
|
||||
}
|
||||
|
||||
// GetDefaultStrategyConfig only supports zh/en; map id -> en
|
||||
configLang := lang
|
||||
if lang == "id" {
|
||||
configLang = "en"
|
||||
}
|
||||
|
||||
// Pre-build all strategy objects before opening the transaction
|
||||
var strategies []*store.Strategy
|
||||
for _, def := range definitions {
|
||||
config := store.GetDefaultStrategyConfig("zh")
|
||||
config := store.GetDefaultStrategyConfig(configLang)
|
||||
def.applyConfig(&config)
|
||||
|
||||
strategy := &store.Strategy{
|
||||
@@ -291,10 +334,16 @@ func (s *Server) createDefaultStrategies(userID string) error {
|
||||
if err := strategy.SetConfig(&config); err != nil {
|
||||
return fmt.Errorf("failed to set config for strategy %q: %w", def.name, err)
|
||||
}
|
||||
if err := s.store.Strategy().Create(strategy); err != nil {
|
||||
return fmt.Errorf("failed to create strategy %q: %w", def.name, err)
|
||||
}
|
||||
logger.Infof(" ✓ Created default strategy: %s (active=%v)", def.name, def.isActive)
|
||||
strategies = append(strategies, strategy)
|
||||
}
|
||||
return nil
|
||||
|
||||
return s.store.Transaction(func(tx *gorm.DB) error {
|
||||
for _, strategy := range strategies {
|
||||
if err := tx.Create(strategy).Error; err != nil {
|
||||
return fmt.Errorf("failed to create strategy %q: %w", strategy.Name, err)
|
||||
}
|
||||
logger.Infof(" ✓ Created default strategy: %s (active=%v)", strategy.Name, strategy.IsActive)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { flushSync } from 'react-dom'
|
||||
import { getSystemConfig } from '../lib/config'
|
||||
import { reset401Flag, httpClient } from '../lib/httpClient'
|
||||
import { getPostAuthPath, setUserMode, type UserMode } from '../lib/onboarding'
|
||||
import { useLanguage } from './LanguageContext'
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
@@ -41,6 +42,7 @@ interface AuthContextType {
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const { language } = useLanguage()
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@@ -208,7 +210,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
email: string
|
||||
password: string
|
||||
beta_code?: string
|
||||
} = { email, password }
|
||||
lang?: string
|
||||
} = { email, password, lang: language }
|
||||
if (betaCode) {
|
||||
requestBody.beta_code = betaCode
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ export function StrategyStudioPage() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-2">
|
||||
{strategies.map((strategy) => (
|
||||
<div
|
||||
key={strategy.id}
|
||||
@@ -719,11 +719,11 @@ export function StrategyStudioPage() {
|
||||
}}
|
||||
className={`group px-2 py-2 rounded-lg cursor-pointer transition-all ${selectedStrategy?.id === strategy.id
|
||||
? 'ring-1 ring-nofx-gold/50 bg-nofx-gold/10 shadow-[0_0_15px_rgba(240,185,11,0.1)]'
|
||||
: 'hover:bg-nofx-bg-lighter/60 hover:ring-1 hover:ring-nofx-gold/20 bg-transparent'
|
||||
: 'hover:bg-nofx-bg-lighter/60 ring-1 ring-white/10 hover:ring-nofx-gold/20 bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm truncate text-nofx-text">{strategy.name}</span>
|
||||
<div className="flex items-start justify-between">
|
||||
<span className={`line-clamp-2 text-nofx-text ${language === 'zh' ? 'text-sm' : 'text-xs'}`}>{strategy.name}</span>
|
||||
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleExportStrategy(strategy) }}
|
||||
|
||||
Reference in New Issue
Block a user