package model import ( global_config "backend/global" "backend/util" "fmt" "os" "os/exec" "path/filepath" "regexp" "strings" "time" "github.com/xuri/excelize/v2" ) var lenCfg_zh_cn = map[string]int{ `^UI_MergeData_(\d+)$`: 20, `UI_MainLevelPanel_title`: 16, } var lenCfg_en_us = map[string]int{ `^UI_MergeData_(\d+)$`: 20, `UI_MainLevelPanel_title`: 16, } var lenCfg_pt_br = map[string]int{ `^UI_MergeData_(\d+)$`: 20, `UI_MainLevelPanel_title`: 16, } var lenCfg_es_latam = map[string]int{ `^UI_MergeData_(\d+)$`: 20, `UI_MainLevelPanel_title`: 16, } type Language struct { Id int `json:"Id" db:"Id"` Key string `json:"key" db:"key"` EN_US string `json:"en_US" db:"en_US"` ZH_CN string `json:"zh_CN" db:"zh_CN"` PT_BR string `json:"pt_BR" db:"pt_BR"` ES_LATAM string `json:"es_LATAM" db:"es_LATAM"` Url string `json:"url"` LenLimit int `json:"len_limit"` } type LanguageOp struct { Id int `json:"Id" db:"Id"` LanguageId int `json:"LanguageId" db:"LanguageId"` Field string `json:"Field" db:"Field"` OldValue string `json:"OldValue" db:"OldValue"` NewValue string `json:"NewValue" db:"NewValue"` Type string `json:"Type" db:"Type"` Update int `json:"Update" db:"Update"` } type LanguageMod struct { PageSize int `json:"PageSize"` CurrentPage int `json:"CurrentPage"` StartTime int `json:"StartTime"` EndTime int `json:"EndTime"` SearchField string `json:"SearchField"` SearchValue string `json:"SearchValue"` LenLimit string `json:"len_limit"` } func (l *LanguageMod) LanguageList(account string) (map[string]interface{}, error) { Db := util.MPool.GetGameDB() column := "" defer Db.Close() var languages []*Language SearchSQL := "" if l.SearchField != "" { if l.SearchValue != "" { escaped := strings.ReplaceAll(l.SearchValue, "'", "''") SearchSQL = " `" + l.SearchField + "` like '%" + escaped + "%' " } else { SearchSQL = " `" + l.SearchField + "` = '' " } } AccountConfig := global_config.GetTranlaterConfig(account) LanguageLimitConfig := global_config.GetLanguageLimitConfig() if AccountConfig != nil { column = AccountConfig.Colunm keysArr := strings.Split(AccountConfig.Keys, ",") if len(keysArr) > 0 { keyConditions := make([]string, len(keysArr)) for i, key := range keysArr { escapedKey := strings.ReplaceAll(strings.TrimSpace(key), "'", "''") keyConditions[i] = fmt.Sprintf("`key` like '%%%s%%'", escapedKey) } if SearchSQL != "" { SearchSQL += " AND " } if AccountConfig.KeysType == "no_include" { SearchSQL += " NOT (" + strings.Join(keyConditions, " or ") + ") " } else { SearchSQL += " (" + strings.Join(keyConditions, " or ") + ") " } } } if SearchSQL != "" { SearchSQL = " where " + SearchSQL } sql := fmt.Sprintf("SELECT `Id`, `key`, `en_US`, `zh_CN`, `pt_BR`, `es_LATAM` FROM language %s LIMIT ?, ?", SearchSQL) err := Db.Select(&languages, sql, (l.CurrentPage-1)*l.PageSize, l.PageSize) if err != nil { return nil, err } var languageOp []*LanguageOp err = Db.Select(&languageOp, "SELECT `Id`, `LanguageId`, `Field`, `OldValue`, `NewValue`, `Type`, `Update` FROM language_op where `Update` >= ? and `Update` <= ? ", l.StartTime, l.EndTime) if err != nil { return nil, err } var total int err = Db.QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM language %s", SearchSQL)).Scan(&total) if err != nil { return nil, err } for i := range languages { url := util.GetLanguageImageURL(languages[i].Key) if url != "" { //url = "../../../../public/" + url url = "./" + url } languages[i].Url = url } newlanguages := make([]*Language, 0) if l.LenLimit != "" { for _, i := range languages { for _, info := range LanguageLimitConfig { re := regexp.MustCompile(info.Key) m := re.FindStringSubmatch(i.Key) if len(m) >= 1 { var zhlen int switch l.LenLimit { case "zh_CN": zhlen = util.CountDisplayLen(i.ZH_CN) case "en_US": zhlen = util.CountDisplayLen(i.EN_US) case "pt_BR": zhlen = util.CountDisplayLen(i.PT_BR) case "es_LATAM": zhlen = util.CountDisplayLen(i.ES_LATAM) } if l.LenLimit == "zh_CN" && zhlen > info.Zh_CN { newlanguages = append(newlanguages, i) } else if l.LenLimit != "zh_CN" && zhlen > info.Latin { newlanguages = append(newlanguages, i) } } } } } if l.LenLimit != "" { languages = newlanguages } return map[string]interface{}{ "data": languages, "op": languageOp, "total": total, "column": column, "language_len_limit": LanguageLimitConfig, }, nil } func (l *LanguageMod) LanguageListAll() (map[string]interface{}, error) { Db := util.MPool.GetGameDB() defer Db.Close() var languages []*Language err := Db.Select(&languages, "SELECT `Id`, `key`, `en_US`, `zh_CN`,`pt_BR`, `es_LATAM` FROM language") if err != nil { return nil, err } var total int err = Db.QueryRow("SELECT COUNT(*) FROM language").Scan(&total) if err != nil { return nil, err } // 导出为 xlsx 文件 f := excelize.NewFile() mainSheet := "client" backendSheet := "backend" // 创建主表 if _, err := f.NewSheet(mainSheet); err != nil { return nil, err } // 创建 backend 子表 if _, err := f.NewSheet(backendSheet); err != nil { return nil, err } SaveTime, _ := util.GetLanguageLastUpdate() ExportTime, _ := util.GetLanguageExportLastUpdate() if SaveTime <= ExportTime { return map[string]interface{}{ "code": 1, "msg": "No changes since last export", }, nil } // 写表头(在主表和 backend 表都写一份) headers := []string{"Id", "key", "en_US", "zh_CN", "pt_BR", "es_LATAM"} cols := []string{"A", "B", "C", "D", "E", "F"} for i, h := range headers { if err := f.SetCellValue(mainSheet, cols[i]+"1", h); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, cols[i]+"1", h); err != nil { return nil, err } } headers2 := []string{"编号", "键", "英文", "简体中文", "葡萄牙语(巴西)", "西班牙语(拉丁美洲)"} for i, h := range headers2 { if err := f.SetCellValue(mainSheet, cols[i]+"2", h); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, cols[i]+"2", h); err != nil { return nil, err } } // 写数据行:分别维护主表和 backend 表的行号 mainRowIndex := 3 backendRowIndex := 3 for _, lang := range languages { if strings.HasPrefix(lang.Key, "backend") { row := fmt.Sprintf("%d", backendRowIndex) if err := f.SetCellValue(backendSheet, "A"+row, lang.Id); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, "B"+row, lang.Key); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, "C"+row, lang.EN_US); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, "D"+row, lang.ZH_CN); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, "E"+row, lang.PT_BR); err != nil { return nil, err } if err := f.SetCellValue(backendSheet, "F"+row, lang.ES_LATAM); err != nil { return nil, err } backendRowIndex++ } else { row := fmt.Sprintf("%d", mainRowIndex) if err := f.SetCellValue(mainSheet, "A"+row, lang.Id); err != nil { return nil, err } if err := f.SetCellValue(mainSheet, "B"+row, lang.Key); err != nil { return nil, err } if err := f.SetCellValue(mainSheet, "C"+row, lang.EN_US); err != nil { return nil, err } if err := f.SetCellValue(mainSheet, "D"+row, lang.ZH_CN); err != nil { return nil, err } if err := f.SetCellValue(mainSheet, "E"+row, lang.PT_BR); err != nil { return nil, err } if err := f.SetCellValue(mainSheet, "F"+row, lang.ES_LATAM); err != nil { return nil, err } mainRowIndex++ } } // 在 /data/docs 仓库中添加并提交该文件 repoDir := "/data/docs" cmd := exec.Command("git", "checkout", "main") cmd.Dir = repoDir if _, err := cmd.CombinedOutput(); err != nil { // try to fetch and force-create/reset local main from origin/main fetch := exec.Command("git", "fetch", "origin", "main") fetch.Dir = repoDir if fout, ferr := fetch.CombinedOutput(); ferr != nil { return nil, fmt.Errorf("git fetch failed: %v: %s", ferr, string(fout)) } cmd = exec.Command("git", "checkout", "-B", "main", "origin/main") cmd.Dir = repoDir if out2, err2 := cmd.CombinedOutput(); err2 != nil { return nil, fmt.Errorf("git checkout main failed: %v: %s", err2, string(out2)) } } cmd = exec.Command("git", "pull") cmd.Dir = repoDir if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("git pull failed: %v: %s", err, string(out)) } // 保存到临时文件 // 保存到指定目录 pathDir := "/data/docs/config" if err := os.MkdirAll(pathDir, 0755); err != nil { return nil, err } path := filepath.Join(pathDir, "AllLanguage.xlsx") if err := f.SaveAs(path); err != nil { return nil, err } relPath, err := filepath.Rel(repoDir, path) if err != nil { return nil, err } cmd = exec.Command("git", "add", relPath) cmd.Dir = repoDir if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("git add failed: %v: %s", err, string(out)) } commitMsg := fmt.Sprintf("Export AllLanguage.xlsx at %s", time.Now().Format(time.RFC3339)) cmd = exec.Command("git", "commit", "-m", commitMsg) cmd.Dir = repoDir if out, err := cmd.CombinedOutput(); err != nil { // 忽略 "nothing to commit" 情形 if !strings.Contains(string(out), "nothing to commit") { return nil, fmt.Errorf("git commit failed: %v: %s", err, string(out)) } } cmd = exec.Command("git", "push", "origin", "main") cmd.Dir = repoDir if out, err := cmd.CombinedOutput(); err != nil { return nil, fmt.Errorf("git push failed: %v: %s", err, string(out)) } util.SaveLanguageExportLastUpdate(util.Now()) // 将文件路径返回到调用方 return map[string]interface{}{ // "data": languages, "total": total, }, nil } func (l *LanguageMod) LanguageSave(langList []LanguageOp, admin string) error { Db := util.MPool.GetGameDB() defer Db.Close() for _, lang := range langList { _, err := Db.Exec("UPDATE language SET `"+lang.Field+"` = ? WHERE Id = ?", lang.NewValue, lang.LanguageId) if err != nil { return err } _, err = Db.Exec("insert into language_op (`LanguageId`, `Field`, `OldValue`, `NewValue`, `Type`, `Update`, `Role`) values (?, ?, ?, ?, ?,?,?)", lang.LanguageId, lang.Field, lang.OldValue, lang.NewValue, lang.Type, util.Now(), admin) if err != nil { return err } } // 删除 key 包含 "obsolete" 的记录 if _, err := Db.Exec("DELETE FROM language WHERE `key` LIKE ?", "%obsolete%"); err != nil { return err } util.SaveLanguageLastUpdate(util.Now()) return nil } func (l *LanguageMod) LanguageAdd(langList []Language, admin string) error { Db := util.MPool.GetGameDB() defer Db.Close() for _, lang := range langList { res, err := Db.Exec("INSERT INTO language (`key`, `en_US`, `zh_CN`, `pt_BR`, `es_LATAM`) VALUES (?, ?, ?, ?, ?)", lang.Key, lang.EN_US, lang.ZH_CN, lang.PT_BR, lang.ES_LATAM) if err != nil { return err } lastId, err := res.LastInsertId() if err != nil { return err } lang.Id = int(lastId) _, err = Db.Exec("insert into language_op (`LanguageId`, `Field`, `OldValue`, `NewValue`, `Type`, `Update`, `Role`) values (?, ?, ?, ?, ?,?,?)", lang.Id, "All", "", lang.EN_US+"|"+lang.ZH_CN+"|"+lang.PT_BR+"|"+lang.ES_LATAM, "Add", util.Now(), admin) if err != nil { return err } } util.SaveLanguageLastUpdate(util.Now()) return nil } func (l *LanguageMod) LanguageDelete(key string, admin string) error { Db := util.MPool.GetGameDB() defer Db.Close() var lang Language err := Db.Get(&lang, "SELECT `Id`, `key`, `en_US`, `zh_CN`, `pt_BR`, `es_LATAM` FROM language WHERE `key` = ?", key) if err != nil { return err } _, err = Db.Exec("DELETE FROM language WHERE `key` = ?", key) if err != nil { return err } _, err = Db.Exec("insert into language_op (`LanguageId`, `Field`, `OldValue`, `NewValue`, `Type`, `Update`, `Role`) values (?, ?, ?, ?, ?,?,?)", lang.Id, "All", lang.EN_US+"|"+lang.ZH_CN+"|"+lang.PT_BR+"|"+lang.ES_LATAM, "", "Delete", util.Now(), admin) if err != nil { return err } util.SaveLanguageLastUpdate(util.Now()) return nil }