feat(i18n): integrate i18next for internationalization support and add initial translation setup

This commit is contained in:
kever
2026-02-17 00:11:42 +08:00
parent dc73f6f6af
commit 9395f9d3af
8 changed files with 1081 additions and 445 deletions

View File

@@ -10,6 +10,7 @@ import (
"equipment-analyzer/internal/capture"
"equipment-analyzer/internal/config"
"equipment-analyzer/internal/model"
"equipment-analyzer/internal/optimizer"
"equipment-analyzer/internal/utils"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
@@ -83,6 +84,71 @@ func (a *App) Shutdown(ctx context.Context) {
}
}
// OptimizeBuilds runs the optimizer on latest parsed data.
func (a *App) OptimizeBuilds(req model.OptimizeRequest) (*model.OptimizeResponse, error) {
log.Printf("[service] OptimizeBuilds entry heroId=%s", req.HeroID)
parsedResult, err := a.GetLatestParsedDataFromDatabase()
if err != nil {
log.Printf("[service] OptimizeBuilds get data failed: %v", err)
return nil, err
}
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
log.Printf("[service] OptimizeBuilds no data items=%d heroes=%d", len(parsedResult.Items), len(parsedResult.Heroes))
return &model.OptimizeResponse{TotalCombos: 0, Results: []model.OptimizeResult{}}, nil
}
items, err := optimizer.ParseItems(parsedResult.Items)
if err != nil {
log.Printf("[service] OptimizeBuilds parse items failed: %v", err)
return nil, fmt.Errorf("parse items failed: %w", err)
}
logMissingSetStats(parsedResult.Items)
heroes, err := optimizer.ParseHeroes(parsedResult.Heroes)
if err != nil {
log.Printf("[service] OptimizeBuilds parse heroes failed: %v", err)
return nil, fmt.Errorf("parse heroes failed: %w", err)
}
templates, err := a.parserService.GetHeroTemplates()
if err != nil {
log.Printf("[service] OptimizeBuilds hero templates missing: %v", err)
return nil, fmt.Errorf("未读取到英雄信息数据")
}
templateHeroes := make([]model.OptimizeHero, 0, len(templates))
for _, t := range templates {
templateHeroes = append(templateHeroes, model.OptimizeHero{
Code: t.Code,
Name: t.Name,
BaseAtk: t.BaseAtk,
BaseDef: t.BaseDef,
BaseHp: t.BaseHp,
BaseSpd: t.BaseSpd,
BaseCr: t.BaseCr,
BaseCd: t.BaseCd,
BaseEff: t.BaseEff,
BaseRes: t.BaseRes,
Atk: t.BaseAtk,
Def: t.BaseDef,
Hp: t.BaseHp,
Spd: t.BaseSpd,
Cr: t.BaseCr,
Cd: t.BaseCd,
Eff: t.BaseEff,
Res: t.BaseRes,
})
}
// Use template heroes for optimizer to match the template-based selection.
heroes = templateHeroes
log.Printf("[service] OptimizeBuilds parsed items=%d heroes=%d", len(items), len(heroes))
resp, err := optimizer.Optimize(items, heroes, req)
if err != nil {
log.Printf("[service] OptimizeBuilds optimize failed: %v", err)
return nil, err
}
log.Printf("[service] OptimizeBuilds done results=%d totalCombos=%d", len(resp.Results), resp.TotalCombos)
return resp, nil
}
// GetNetworkInterfaces returns available network interfaces.
func (a *App) GetNetworkInterfaces() ([]model.NetworkInterface, error) {
interfaces, err := capture.GetNetworkInterfaces()
@@ -93,6 +159,11 @@ func (a *App) GetNetworkInterfaces() ([]model.NetworkInterface, error) {
return interfaces, nil
}
// GetHeroTemplates returns template heroes from local herodata.json.
func (a *App) GetHeroTemplates() ([]model.HeroTemplate, error) {
return a.parserService.GetHeroTemplates()
}
// StartCapture starts capture on the given interface.
func (a *App) StartCapture(interfaceName string) error {
if a.captureService.IsCapturing() {
@@ -329,6 +400,7 @@ func (a *App) GetLatestParsedDataFromDatabase() (*model.ParsedResult, error) {
return nil, fmt.Errorf("failed to unmarshal items: %w", err)
}
}
logMissingSetStats(items)
var heroes []interface{}
if heroesJSON != "" {
@@ -424,6 +496,95 @@ func (a *App) GetAllAppSettings() (map[string]string, error) {
return a.databaseService.GetAllAppSettings()
}
func logMissingSetStats(items []interface{}) {
if len(items) == 0 {
return
}
var total int
var emptyF int
var emptySet int
var healthF int
var healthSet int
var mapHp int
setCounts := map[string]int{}
uncategorized := 0
emptySetByF := map[string]int{}
emptySetByGear := map[string]int{}
ingameSetMap := map[string]string{
"set_acc": "HitSet",
"set_att": "AttackSet",
"set_coop": "UnitySet",
"set_counter": "CounterSet",
"set_cri_dmg": "DestructionSet",
"set_cri": "CriticalSet",
"set_def": "DefenseSet",
"set_immune": "ImmunitySet",
"set_max_hp": "HealthSet",
"set_penetrate": "PenetrationSet",
"set_rage": "RageSet",
"set_res": "ResistSet",
"set_revenge": "RevengeSet",
"set_scar": "InjurySet",
"set_speed": "SpeedSet",
"set_vampire": "LifestealSet",
"set_shield": "ProtectionSet",
"set_torrent": "TorrentSet",
"set_revenant": "ReversalSet",
"set_riposte": "RiposteSet",
"set_chase": "PursuitSet",
"set_opener": "WarfareSet",
}
for _, raw := range items {
item, ok := raw.(map[string]interface{})
if !ok {
continue
}
total++
if f, ok := item["f"]; !ok || f == nil || f == "" {
emptyF++
} else {
if f == "set_max_hp" {
healthF++
}
}
setValue, _ := item["set"].(string)
if setValue == "" {
emptySet++
fValue, _ := item["f"].(string)
if fValue != "" {
emptySetByF[fValue] = emptySetByF[fValue] + 1
}
if gear, ok := item["gear"].(string); ok && gear != "" {
emptySetByGear[gear] = emptySetByGear[gear] + 1
}
if fValue != "" {
if mapped, ok := ingameSetMap[fValue]; ok {
setCounts[mapped] = setCounts[mapped] + 1
} else {
uncategorized++
}
} else {
uncategorized++
}
} else {
setCounts[setValue] = setCounts[setValue] + 1
}
if set, ok := item["set"].(string); ok && set == "HealthSet" {
healthSet++
}
if f, ok := item["f"]; ok && f == "set_max_hp" {
mapHp++
}
}
log.Printf("[service] set stats: total=%d empty_f=%d empty_set=%d set_max_hp=%d HealthSet=%d mapped_hp=%d", total, emptyF, emptySet, healthF, healthSet, mapHp)
log.Printf("[service] set counts: %+v", setCounts)
log.Printf("[service] uncategorized items: %d", uncategorized)
if emptySet > 0 {
log.Printf("[service] empty set by f: %+v", emptySetByF)
log.Printf("[service] empty set by gear: %+v", emptySetByGear)
}
}
// StartCaptureWithFilter allows frontend to provide a custom BPF filter.
func (a *App) StartCaptureWithFilter(interfaceName string, filter string) error {
a.logger.Info("StartCaptureWithFilter requested", "interface", interfaceName, "filter", filter)