Files
EdgeCommon/cmd/langs/main.go
2023-06-28 19:06:16 +08:00

325 lines
7.8 KiB
Go

// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package main
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"go/format"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
)
func main() {
var args = os.Args
if len(args) >= 2 {
switch args[1] {
case "generate":
runGenerate()
}
} else {
fmt.Println("Usage: langs [generate]")
}
}
func runGenerate() {
var rootDir = filepath.Clean(Tea.Root + "/../pkg/langs/protos")
dir, err := os.Open(rootDir)
if err != nil {
fmt.Println("[ERROR]read dir failed: " + err.Error())
return
}
defer func() {
_ = dir.Close()
}()
files, err := dir.Readdir(0)
if err != nil {
fmt.Println("[ERROR]read dir failed: " + err.Error())
return
}
var dirRegexp = regexp.MustCompile(`^[a-z]+-[a-z]+$`)
var jsonFileNameRegexp = regexp.MustCompile(`^([a-zA-Z0-9]+)(_([a-zA-Z0-9]+))+\.json$`)
var messageCodeRegexp = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
var jsonCommentRegexp = regexp.MustCompile(`//.+`)
var messageCodes = []string{}
var langMaps = map[string]*langs.Lang{} // lang => *langs.Lang
var defaultLang = langs.DefaultManager().DefaultLang()
for _, file := range files {
var dirName = file.Name()
if !file.IsDir() || !dirRegexp.MatchString(dirName) {
continue
}
var langCode = dirName
var isBaseLang = langCode == defaultLang
var processOk = func() bool {
jsonFiles, err := filepath.Glob(rootDir + "/" + dirName + "/*.json")
if err != nil {
fmt.Println("[ERROR]list json files failed: " + err.Error())
return false
}
for _, jsonFile := range jsonFiles {
var jsonFileName = filepath.Base(jsonFile)
if len(jsonFileName) == 0 || !jsonFileNameRegexp.MatchString(jsonFileName) {
continue
}
var module = strings.TrimSuffix(jsonFileName, ".json")
data, err := os.ReadFile(jsonFile)
if err != nil {
fmt.Println("[ERROR]read json file '" + jsonFile + "' failed: " + err.Error())
return false
}
// remove comments in json
data = jsonCommentRegexp.ReplaceAll(data, []byte{})
var m = map[string]string{} // code => value
err = json.Unmarshal(data, &m)
if err != nil {
fmt.Println("[ERROR]decode json file '" + jsonFile + "' failed: " + err.Error())
return false
}
var newM = map[string]string{}
for code, value := range m {
if !messageCodeRegexp.MatchString(code) {
fmt.Println("[ERROR]invalid message code '" + code + "'")
return false
}
var fullCode = module + "_" + code
if isBaseLang {
messageCodes = append(messageCodes, fullCode)
}
newM[fullCode] = value
}
finalLang, ok := langMaps[langCode]
if !ok {
finalLang = langs.NewLang(langCode)
langMaps[langCode] = finalLang
}
for code, value := range newM {
finalLang.Set(code, value)
}
}
return true
}()
if !processOk {
return
}
}
// compile
for langCode, lang := range langMaps {
err = lang.Compile()
if err != nil {
fmt.Println("[ERROR]compile '" + langCode + "' failed: " + err.Error())
return
}
}
// check message codes
fmt.Println("checking message codes ...")
var defaultMessageMap = map[string]string{}
for langCode, messageLang := range langMaps {
if langCode == defaultLang { // only check lang not equal to 'en-us'
defaultMessageMap = messageLang.GetAll()
continue
}
for messageCode := range messageLang.GetAll() {
if !lists.ContainsString(messageCodes, messageCode) {
fmt.Println("[ERROR]message code '" + messageCode + "' in lang '" + langCode + "' not exist in default lang file ('" + defaultLang + "')")
return
}
}
}
fmt.Println("found '" + types.String(len(messageCodes)) + "' message codes")
// generate codes/codes.go
sort.Strings(messageCodes)
var codesSource = `
// generated by run 'langs generate'
package codes
import(
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
)
const (
`
for index, messageCode := range messageCodes {
// add comment to message code
comment, _, _ := strings.Cut(defaultMessageMap[messageCode], "\n")
codesSource += upperWords(messageCode) + " langs.MessageCode = " + strconv.Quote(messageCode) + " // " + comment
// add NL
if index != len(messageCodes)-1 {
codesSource += "\n"
}
}
codesSource += `
)
`
formattedCodesSource, err := format.Source([]byte(codesSource))
if err != nil {
fmt.Println("[ERROR]format 'codes.go' failed: " + err.Error())
return
}
fmt.Println("generating 'codes/codes.go' ...")
err = os.WriteFile(Tea.Root+"/../pkg/langs/codes/codes.go", formattedCodesSource, 0666)
if err != nil {
fmt.Println("[ERROR]write to 'codes.go' failed: " + err.Error())
return
}
// generate messages_LANG.go
for langCode, messageLang := range langMaps {
var langFile = strings.ReplaceAll("messages/messages_"+langCode+".go", "-", "_")
fmt.Println("generating '" + langFile + "' ...")
var source = `
// generated by run 'langs generate'
package messages
import(
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
)
func init() {
langs.Load("` + langCode + `", map[string]string{
`
for code, value := range messageLang.GetAll() {
source += strconv.Quote(code) + ": " + strconv.Quote(value) + ",\n"
}
source += `
})
}
`
formattedSource, err := format.Source([]byte(source))
if err != nil {
fmt.Println("[ERROR]format '" + langFile + "' failed: " + err.Error())
return
}
err = os.WriteFile(Tea.Root+"/../pkg/langs/"+langFile, formattedSource, 0666)
if err != nil {
fmt.Println("[ERROR]write file '" + langFile + "' failed: " + err.Error())
return
}
}
// generate language javascript files for EdgeAdmin and EdgeUser (commercial versions)
for lang, messageLang := range langMaps {
if lang != defaultLang {
// TODO merge messageMap with default message map
}
for _, component := range []string{"EdgeAdmin", "EdgeUser"} {
fmt.Println("generating '" + lang + ".js' for " + component + " ...")
var targetFile = filepath.Clean(Tea.Root + "/../../" + component + "/web/public/js/langs/" + lang + ".js")
var targetDir = filepath.Dir(targetFile)
dirStat, _ := os.Stat(targetDir)
if dirStat != nil {
var prefix = ""
switch component {
case "EdgeAdmin":
prefix = "admin_"
case "EdgeUser":
prefix = "user_"
}
if len(prefix) == 0 {
continue
}
var filteredMessages = map[string]string{}
for code, value := range messageLang.GetAll() {
if strings.HasPrefix(code, prefix) {
filteredMessages[code] = value
}
}
messageMapJSON, err := json.Marshal(filteredMessages)
if err != nil {
fmt.Println("[ERROR]marshal message map failed: " + err.Error())
return
}
err = os.WriteFile(targetFile, []byte(`// generated by 'langs generate'
window.LANG_MESSAGES = `+string(messageMapJSON)+";\n"), 0666)
if err != nil {
fmt.Println("[ERROR]write file '" + targetFile + "' failed: " + err.Error())
return
}
}
}
}
fmt.Println("success")
}
func upperWords(s string) string {
var words = strings.Split(s, "_")
for index, word := range words {
words[index] = upperWord(word)
}
return strings.Join(words, "")
}
func upperWord(word string) string {
var l = len(word)
if l == 0 {
return ""
}
if l == 1 {
return strings.ToUpper(word)
}
// special words
switch word {
case "api", "http", "https", "tcp", "tls", "udp", "ip", "dns", "ns",
"waf", "acme", "ssh", "toa", "http2", "http3", "uam", "cc",
"db", "isp", "sni", "ui", "soa", "ocsp", "en", "zh":
return strings.ToUpper(word)
case "ipv6":
return "IPv6"
case "ddos":
return "DDoS"
case "webp":
return "WebP"
case "doh":
return "DoH"
}
return strings.ToUpper(word[:1]) + word[1:]
}