添加多语言最基础代码

This commit is contained in:
GoEdgeLab
2023-06-28 09:12:13 +08:00
parent 18a8eaa543
commit 05bf52d04c
27 changed files with 850 additions and 9 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*_plus.go
*_plus_test.go
*_plus_test.go
*_plus.json

3
build/build-messages.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
go run ../cmd/langs/main.go generate

View File

@@ -13537,7 +13537,7 @@
},
{
"name": "AdminModuleList",
"code": "message AdminModuleList {\n\tint64 adminId = 1;\n\tbool isSuper = 2;\n\trepeated AdminModule Modules = 3;\n\tstring fullname = 4;\n\tstring theme = 5;\n}",
"code": "message AdminModuleList {\n\tint64 adminId = 1;\n\tbool isSuper = 2;\n\trepeated AdminModule Modules = 3;\n\tstring fullname = 4;\n\tstring theme = 5; // 风格模板\n\tstring lang = 6; // 界面语言\n}",
"doc": ""
},
{

276
cmd/langs/main.go Normal file
View File

@@ -0,0 +1,276 @@
// 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 messageCodes = []string{}
var messageMaps = map[string]map[string]string{} // lang => { K:V }
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
func() {
jsonFiles, err := filepath.Glob(rootDir + "/" + dirName + "/*.json")
if err != nil {
fmt.Println("[ERROR]list json files failed: " + err.Error())
return
}
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
}
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
}
var newM = map[string]string{}
for code, value := range m {
if !messageCodeRegexp.MatchString(code) {
fmt.Println("[ERROR]invalid message code '" + code + "'")
return
}
var fullCode = module + "_" + code
if isBaseLang {
messageCodes = append(messageCodes, fullCode)
}
newM[fullCode] = value
}
finalMap, ok := messageMaps[langCode]
if !ok {
finalMap = map[string]string{}
messageMaps[langCode] = finalMap
}
for code, value := range newM {
finalMap[code] = value
}
}
}()
}
// check message codes
fmt.Println("checking message codes ...")
var defaultMessageMap = map[string]string{}
for langCode, messageMap := range messageMaps {
if langCode == defaultLang { // only check lang not equal to 'en-us'
defaultMessageMap = messageMap
continue
}
for messageCode := range messageMap {
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 _, messageCode := range messageCodes {
// add comment to message code
comment, _, _ := strings.Cut(defaultMessageMap[messageCode], "\n")
codesSource += upperWords(messageCode) + " langs.MessageCode = " + strconv.Quote(messageCode) + " // " + comment + "\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, messageMap := range messageMaps {
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 messageMap {
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
for lang, messageMap := range messageMaps {
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 {
messageMapJSON, err := json.Marshal(messageMap)
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", "udp", "ip", "dns", "ns", "waf", "acme", "ssh", "toa":
return strings.ToUpper(word)
case "ipv6":
return "IPv6"
case "ddos":
return "DDoS"
}
return strings.ToUpper(word[:1]) + word[1:]
}

4
go.mod
View File

@@ -4,11 +4,11 @@ go 1.18
require (
github.com/golang/protobuf v1.5.2
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f
golang.org/x/net v0.8.0
google.golang.org/grpc v1.45.0
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
)
require (

4
go.sum
View File

@@ -59,6 +59,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475 h1:EseyfFaQOjWanGiby9KMw7PjDBMg/95tLDgIw/ns0Cw=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f h1:xo6XmXLtveKcwcZAXV6VMxkWNzy/2dStfHEnyowsGAE=
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -201,5 +203,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

74
pkg/langs/codes/codes.go Normal file
View File

@@ -0,0 +1,74 @@
// generated by run 'langs generate'
package codes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
)
const (
AdminDashboardDNS langs.MessageCode = "admin_dashboard_dns" // DNS
AdminDashboardEvents langs.MessageCode = "admin_dashboard_events" // 事件
AdminDashboardOverview langs.MessageCode = "admin_dashboard_overview" // 概况
AdminDashboardUser langs.MessageCode = "admin_dashboard_user" // 用户
AdminDashboardWAF langs.MessageCode = "admin_dashboard_waf" // WAF
AdminMenuAdminRecipients langs.MessageCode = "admin_menu_admin_recipients" // 通知媒介
AdminMenuAdmins langs.MessageCode = "admin_menu_admins" // 系统用户
AdminMenuDashboard langs.MessageCode = "admin_menu_dashboard" // 数据看板
AdminMenuDNS langs.MessageCode = "admin_menu_dns" // 域名解析
AdminMenuDNSClusters langs.MessageCode = "admin_menu_dns_clusters" // 集群列表
AdminMenuDNSIssues langs.MessageCode = "admin_menu_dns_issues" // 问题修复
AdminMenuDNSProviders langs.MessageCode = "admin_menu_dns_providers" // DNS服务商
AdminMenuFinance langs.MessageCode = "admin_menu_finance" // 财务管理
AdminMenuFinanceAccounts langs.MessageCode = "admin_menu_finance_accounts" // 用户账户
AdminMenuFinanceBills langs.MessageCode = "admin_menu_finance_bills" // 费用账单
AdminMenuFinanceFee langs.MessageCode = "admin_menu_finance_fee" // 计费设置
AdminMenuFinanceIncome langs.MessageCode = "admin_menu_finance_income" // 统计报表
AdminMenuFinanceLogs langs.MessageCode = "admin_menu_finance_logs" // 收支明细
AdminMenuFinanceOrders langs.MessageCode = "admin_menu_finance_orders" // 订单管理
AdminMenuFinancePackages langs.MessageCode = "admin_menu_finance_packages" // 流量包
AdminMenuLogs langs.MessageCode = "admin_menu_logs" // 日志审计
AdminMenuNodeAntiDDoSProducts langs.MessageCode = "admin_menu_node_anti_ddos_products" // 高防IP
AdminMenuNodeClusters langs.MessageCode = "admin_menu_node_clusters" // 集群列表
AdminMenuNodeDistributedMonitors langs.MessageCode = "admin_menu_node_distributed_monitors" // 区域监控
AdminMenuNodeIPList langs.MessageCode = "admin_menu_node_ip_list" // 节点IP
AdminMenuNodeLogs langs.MessageCode = "admin_menu_node_logs" // 节点日志
AdminMenuNodeRegions langs.MessageCode = "admin_menu_node_regions" // 区域设置
AdminMenuNodeSSHGrants langs.MessageCode = "admin_menu_node_ssh_grants" // 节点SSH
AdminMenuNodes langs.MessageCode = "admin_menu_nodes" // 边缘节点
AdminMenuNS langs.MessageCode = "admin_menu_ns" // 智能DNS
AdminMenuNSAccessLogs langs.MessageCode = "admin_menu_ns_access_logs" // 访问日志
AdminMenuNSClusters langs.MessageCode = "admin_menu_ns_clusters" // 集群管理
AdminMenuNSDomainBatchOperations langs.MessageCode = "admin_menu_ns_domain_batch_operations" // 批量操作
AdminMenuNSDomainGroups langs.MessageCode = "admin_menu_ns_domain_groups" // 域名分组
AdminMenuNSDomains langs.MessageCode = "admin_menu_ns_domains" // 域名管理
AdminMenuNSNodeLogs langs.MessageCode = "admin_menu_ns_node_logs" // 运行日志
AdminMenuNSPlans langs.MessageCode = "admin_menu_ns_plans" // 套餐设置
AdminMenuNSResolveTest langs.MessageCode = "admin_menu_ns_resolve_test" // 解析测试
AdminMenuNSRoutes langs.MessageCode = "admin_menu_ns_routes" // 线路管理
AdminMenuNSSettings langs.MessageCode = "admin_menu_ns_settings" // 全局配置
AdminMenuNSUserPlans langs.MessageCode = "admin_menu_ns_user_plans" // 用户套餐
AdminMenuPlanUserPlans langs.MessageCode = "admin_menu_plan_user_plans" // 已购套餐
AdminMenuPlans langs.MessageCode = "admin_menu_plans" // 套餐管理
AdminMenuServerAccessLogPolicies langs.MessageCode = "admin_menu_server_access_log_policies" // 日志策略
AdminMenuServerAccessLogs langs.MessageCode = "admin_menu_server_access_logs" // 访问日志
AdminMenuServerCachePolicies langs.MessageCode = "admin_menu_server_cache_policies" // 缓存策略
AdminMenuServerCerts langs.MessageCode = "admin_menu_server_certs" // 证书管理
AdminMenuServerGlobalSettings langs.MessageCode = "admin_menu_server_global_settings" // 通用设置
AdminMenuServerGroups langs.MessageCode = "admin_menu_server_groups" // 网站分组
AdminMenuServerIPLists langs.MessageCode = "admin_menu_server_ip_lists" // IP名单
AdminMenuServerMetrics langs.MessageCode = "admin_menu_server_metrics" // 统计指标
AdminMenuServerPurgeFetchCaches langs.MessageCode = "admin_menu_server_purge_fetch_caches" // 刷新预热
AdminMenuServerScripts langs.MessageCode = "admin_menu_server_scripts" // 脚本库
AdminMenuServerTrafficStats langs.MessageCode = "admin_menu_server_traffic_stats" // 用量统计
AdminMenuServerWAFPolicies langs.MessageCode = "admin_menu_server_waf_policies" // WAF策略
AdminMenuServers langs.MessageCode = "admin_menu_servers" // 网站列表
AdminMenuSettingAdvancedSettings langs.MessageCode = "admin_menu_setting_advanced_settings" // 高级设置
AdminMenuSettingAuthority langs.MessageCode = "admin_menu_setting_authority" // 商业版本
AdminMenuSettingBasicSettings langs.MessageCode = "admin_menu_setting_basic_settings" // 基础设置
AdminMenuSettings langs.MessageCode = "admin_menu_settings" // 系统设置
AdminMenuTicketCategory langs.MessageCode = "admin_menu_ticket_category" // 分类
AdminMenuTickets langs.MessageCode = "admin_menu_tickets" // 工单系统
AdminMenuUsers langs.MessageCode = "admin_menu_users" // 用户设置
)

32
pkg/langs/lang.go Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package langs
type Lang struct {
code string
messageMap map[string]string // message code => message text
}
func NewLang(code string) *Lang {
return &Lang{
code: code,
messageMap: map[string]string{},
}
}
func (this *Lang) Set(messageCode string, messageText string) {
this.messageMap[messageCode] = messageText
}
// Get 读取单条消息
// get single message with message code
func (this *Lang) Get(messageCode string) string {
return this.messageMap[messageCode]
}
// GetAll 读取所有消息
// get all messages
func (this *Lang) GetAll() map[string]string {
return this.messageMap
}

105
pkg/langs/manager.go Normal file
View File

@@ -0,0 +1,105 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package langs
import (
"fmt"
"strings"
)
var defaultManager = NewManager()
type Manager struct {
langMap map[string]*Lang // lang code => *Lang, lang code must be in lowercase
defaultLangCode string
}
func NewManager() *Manager {
return &Manager{
langMap: map[string]*Lang{},
defaultLangCode: "zh-cn",
}
}
func DefaultManager() *Manager {
return defaultManager
}
func (this *Manager) AddLang(code string) *Lang {
var lang = NewLang(code)
this.langMap[code] = lang
return lang
}
func (this *Manager) HasLang(code string) bool {
_, ok := this.langMap[code]
return ok
}
func (this *Manager) GetLang(code string) (lang *Lang, ok bool) {
lang, ok = this.langMap[code]
return
}
func (this *Manager) MatchLang(code string) (matchedCode string) {
// lookup exact match
code = strings.ToLower(code)
_, ok := this.langMap[code]
if ok {
return code
}
// lookup language family, such as en-us, en
if strings.Contains(code, "-") {
code, _, _ = strings.Cut(code, "-")
}
for rawCode := range this.langMap {
if strings.HasPrefix(rawCode, code+"-") { // en-us vs en
return rawCode
}
}
return this.DefaultLang()
}
func (this *Manager) SetDefaultLang(code string) {
this.defaultLangCode = code
}
func (this *Manager) DefaultLang() string {
if len(this.defaultLangCode) > 0 {
return this.defaultLangCode
}
return "zh-cn"
}
// GetMessage
// message: name: %s, age: %d, salary: %.2f
func (this *Manager) GetMessage(langCode string, messageCode string, args ...any) string {
var lang = this.langMap[langCode]
if lang == nil && len(this.defaultLangCode) > 0 {
lang = this.langMap[this.defaultLangCode]
}
if lang == nil {
return ""
}
var message = lang.Get(messageCode)
if len(message) == 0 {
// try to get message from default lang
if lang.code != this.defaultLangCode {
var defaultLang = this.langMap[this.defaultLangCode]
if defaultLang != nil {
return defaultLang.Get(messageCode)
}
}
return ""
}
if len(args) == 0 {
return message
}
return fmt.Sprintf(message, args...)
}

43
pkg/langs/manager_test.go Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package langs_test
import (
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"testing"
)
func TestManager_GetMessage(t *testing.T) {
var manager = langs.NewManager()
var lang = manager.AddLang("en-US")
lang.Set("user_description", "user: %s, age: %d")
t.Log(manager.GetMessage("en-US", "user_description", "Lily", 23))
t.Log(manager.GetMessage("zh-CN", "user_description", "Lucy", 23)) // use 'en-US' as fallback language
}
func TestManager_GetMessage2(t *testing.T) {
var manager = langs.NewManager()
manager.SetDefaultLang("zh-CN")
var lang = manager.AddLang("en-US")
lang.Set("user_description", "user: %s, age: %d")
t.Log(manager.GetMessage("en-US", "user_description", "Lily", 23))
t.Log(manager.GetMessage("zh-CN", "user_description", "Lucy", 23)) // should be empty
}
func TestManager_MatchLang(t *testing.T) {
var manager = langs.NewManager()
manager.AddLang("en-us")
manager.AddLang("en")
manager.AddLang("zh-cn")
manager.AddLang("zh-hk")
//manager.AddLang("zh-tw")
for _, code := range []string{
"en",
"en-us",
"zh-cn",
"zh-tw",
} {
t.Log(code, "=>", manager.MatchLang(code))
}
}

5
pkg/langs/message.go Normal file
View File

@@ -0,0 +1,5 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package langs
type MessageCode = string

View File

@@ -0,0 +1,75 @@
// generated by run 'langs generate'
package messages
import (
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
)
func init() {
langs.Load("zh-cn", map[string]string{
"admin_menu_finance_fee": "计费设置",
"admin_menu_server_traffic_stats": "用量统计",
"admin_menu_ns_node_logs": "运行日志",
"admin_menu_servers": "网站列表",
"admin_menu_finance_logs": "收支明细",
"admin_menu_plan_user_plans": "已购套餐",
"admin_menu_server_ip_lists": "IP名单",
"admin_menu_users": "用户设置",
"admin_menu_ns_access_logs": "访问日志",
"admin_menu_server_purge_fetch_caches": "刷新预热",
"admin_menu_node_distributed_monitors": "区域监控",
"admin_menu_finance_packages": "流量包",
"admin_menu_settings": "系统设置",
"admin_menu_ns_plans": "套餐设置",
"admin_dashboard_user": "用户",
"admin_dashboard_waf": "WAF",
"admin_menu_setting_advanced_settings": "高级设置",
"admin_menu_server_global_settings": "通用设置",
"admin_menu_node_logs": "节点日志",
"admin_menu_ns_clusters": "集群管理",
"admin_menu_server_cache_policies": "缓存策略",
"admin_menu_ns_resolve_test": "解析测试",
"admin_menu_finance_income": "统计报表",
"admin_menu_finance_accounts": "用户账户",
"admin_menu_server_certs": "证书管理",
"admin_menu_dashboard": "数据看板",
"admin_dashboard_events": "事件",
"admin_menu_node_ip_list": "节点IP",
"admin_dashboard_overview": "概况",
"admin_menu_server_access_logs": "访问日志",
"admin_menu_dns_providers": "DNS服务商",
"admin_menu_admins": "系统用户",
"admin_menu_setting_authority": "商业版本",
"admin_menu_dns_issues": "问题修复",
"admin_menu_server_metrics": "统计指标",
"admin_menu_node_ssh_grants": "节点SSH",
"admin_menu_server_waf_policies": "WAF策略",
"admin_menu_ns_domains": "域名管理",
"admin_menu_nodes": "边缘节点",
"admin_dashboard_dns": "DNS",
"admin_menu_logs": "日志审计",
"admin_menu_ns_user_plans": "用户套餐",
"admin_menu_ns_domain_groups": "域名分组",
"admin_menu_tickets": "工单系统",
"admin_menu_node_regions": "区域设置",
"admin_menu_ns_domain_batch_operations": "批量操作",
"admin_menu_plans": "套餐管理",
"admin_menu_finance_bills": "费用账单",
"admin_menu_ticket_category": "分类",
"admin_menu_finance": "财务管理",
"admin_menu_node_anti_ddos_products": "高防IP",
"admin_menu_ns": "智能DNS",
"admin_menu_dns": "域名解析",
"admin_menu_server_groups": "网站分组",
"admin_menu_server_scripts": "脚本库",
"admin_menu_ns_settings": "全局配置",
"admin_menu_ns_routes": "线路管理",
"admin_menu_server_access_log_policies": "日志策略",
"admin_menu_finance_orders": "订单管理",
"admin_menu_dns_clusters": "集群列表",
"admin_menu_admin_recipients": "通知媒介",
"admin_menu_setting_basic_settings": "基础设置",
"admin_menu_node_clusters": "集群列表",
})
}

View File

@@ -0,0 +1,2 @@
{
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,7 @@
{
"overview": "概况",
"waf": "WAF",
"dns": "DNS",
"user": "用户",
"events": "事件"
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,70 @@
{
"dashboard": "数据看板",
"servers": "网站列表",
"server_access_logs": "访问日志",
"server_groups": "网站分组",
"server_certs": "证书管理",
"server_traffic_stats": "用量统计",
"server_cache_policies": "缓存策略",
"server_purge_fetch_caches": "刷新预热",
"server_waf_policies": "WAF策略",
"server_ip_lists": "IP名单",
"server_access_log_policies": "日志策略",
"server_metrics": "统计指标",
"server_scripts": "脚本库",
"server_global_settings": "通用设置",
"nodes": "边缘节点",
"node_clusters": "集群列表",
"node_logs": "节点日志",
"node_ip_list": "节点IP",
"node_regions": "区域设置",
"node_ssh_grants": "节点SSH",
"node_distributed_monitors": "区域监控",
"node_anti_ddos_products": "高防IP",
"dns": "域名解析",
"dns_clusters": "集群列表",
"dns_providers": "DNS服务商",
"dns_issues": "问题修复",
"ns": "智能DNS",
"ns_domains": "域名管理",
"ns_domain_groups": "域名分组",
"ns_domain_batch_operations": "批量操作",
"ns_clusters": "集群管理",
"ns_routes": "线路管理",
"ns_settings": "全局配置",
"ns_user_plans": "用户套餐",
"ns_plans": "套餐设置",
"ns_access_logs": "访问日志",
"ns_node_logs": "运行日志",
"ns_resolve_test": "解析测试",
"users": "用户设置",
"finance": "财务管理",
"finance_bills": "费用账单",
"finance_accounts": "用户账户",
"finance_logs": "收支明细",
"finance_orders": "订单管理",
"finance_income": "统计报表",
"finance_fee": "计费设置",
"finance_packages": "流量包",
"plans": "套餐管理",
"plan_user_plans": "已购套餐",
"tickets": "工单系统",
"ticket_category": "分类",
"admins": "系统用户",
"admin_recipients": "通知媒介",
"logs": "日志审计",
"settings": "系统设置",
"setting_basic_settings": "基础设置",
"setting_advanced_settings": "高级设置",
"setting_authority": "商业版本"
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -0,0 +1,3 @@
{
}

74
pkg/langs/utils.go Normal file
View File

@@ -0,0 +1,74 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package langs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/iwind/TeaGo/actions"
"net/http"
"strings"
)
// Message 读取消息
// Read message
func Message(langCode string, messageCode MessageCode, args ...any) string {
return defaultManager.GetMessage(langCode, messageCode, args...)
}
func ParseLangFromRequest(req *http.Request) (langCode string) {
// parse language from cookie
const cookieName = "edgelang"
cookie, _ := req.Cookie(cookieName)
if cookie != nil && len(cookie.Value) > 0 && defaultManager.HasLang(cookie.Value) {
return cookie.Value
}
// parse language from 'Accept-Language'
var acceptLanguage = req.Header.Get("Accept-Language")
if len(acceptLanguage) > 0 {
var pieces = strings.Split(acceptLanguage, ",")
for _, lang := range pieces {
var index = strings.Index(lang, ";")
if index >= 0 {
lang = lang[:index]
}
var match = defaultManager.MatchLang(lang)
if len(match) > 0 {
return match
}
}
}
return defaultManager.DefaultLang()
}
func ParseLangFromAction(action actions.ActionWrapper) (langCode string) {
return ParseLangFromRequest(action.Object().Request)
}
// Format 格式化变量
// Format string that contains message variables, such as ${lang.MESSAGE_CODE}
//
// 暂时不支持变量中加参数
func Format(langCode string, varString string) string {
return configutils.ParseVariables(varString, func(varName string) (value string) {
const prefix = "lang."
if !strings.HasPrefix(varName, prefix) {
return "${" + varName + "}" // keep origin variable
}
return Message(langCode, varName[len(prefix):])
})
}
// Load 加载消息定义
// Load message definitions from map
func Load(langCode string, messageMap map[string]string) {
lang, ok := defaultManager.GetLang(langCode)
if !ok {
lang = defaultManager.AddLang(langCode)
}
for messageCode, messageText := range messageMap {
lang.Set(messageCode, messageText)
}
}

36
pkg/langs/utils_test.go Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package langs_test
import (
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"testing"
)
func TestMessage(t *testing.T) {
var lang = langs.DefaultManager().AddLang("en-us")
lang.Set("user_description", "user: %s")
t.Log(langs.Message("en-us", "user_description", "Lily"))
}
func TestMessageDefault(t *testing.T) {
var lang = langs.DefaultManager().AddLang("zh-cn")
lang.Set("user_description", "user: %s")
t.Log(langs.Message("en-us", "user_description", "Lily"))
}
func TestFormat(t *testing.T) {
{
var lang = langs.DefaultManager().AddLang("en-US")
lang.Set("book_name", "Golang")
}
{
var lang = langs.DefaultManager().AddLang("zh-CN")
lang.Set("book_name", "Go语言")
}
t.Log(langs.Format("en-US", "this is ${lang.book_name} book"))
t.Log(langs.Format("zh-CN", "this is ${lang.book_name} book"))
}

View File

@@ -34,7 +34,8 @@ type AdminModuleList struct {
IsSuper bool `protobuf:"varint,2,opt,name=isSuper,proto3" json:"isSuper,omitempty"`
Modules []*AdminModule `protobuf:"bytes,3,rep,name=Modules,proto3" json:"Modules,omitempty"`
Fullname string `protobuf:"bytes,4,opt,name=fullname,proto3" json:"fullname,omitempty"`
Theme string `protobuf:"bytes,5,opt,name=theme,proto3" json:"theme,omitempty"`
Theme string `protobuf:"bytes,5,opt,name=theme,proto3" json:"theme,omitempty"` // 风格模板
Lang string `protobuf:"bytes,6,opt,name=lang,proto3" json:"lang,omitempty"` // 界面语言
}
func (x *AdminModuleList) Reset() {
@@ -104,6 +105,13 @@ func (x *AdminModuleList) GetTheme() string {
return ""
}
func (x *AdminModuleList) GetLang() string {
if x != nil {
return x.Lang
}
return ""
}
var File_models_model_admin_list_proto protoreflect.FileDescriptor
var file_models_model_admin_list_proto_rawDesc = []byte{
@@ -111,7 +119,7 @@ var file_models_model_admin_list_proto_rawDesc = []byte{
0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x02, 0x70, 0x62, 0x1a, 0x1f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65,
0x6c, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa2, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x6f,
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb6, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x4d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x6d, 0x69,
0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x64, 0x6d, 0x69, 0x6e,
0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x73, 0x53, 0x75, 0x70, 0x65, 0x72, 0x18, 0x02, 0x20,
@@ -121,8 +129,9 @@ var file_models_model_admin_list_proto_rawDesc = []byte{
0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x6e,
0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x6e,
0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01,
0x28, 0x09, 0x52, 0x05, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x42, 0x06, 0x5a, 0x04, 0x2e, 0x2f, 0x70,
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x28, 0x09, 0x52, 0x05, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x6e,
0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x42, 0x06, 0x5a,
0x04, 0x2e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@@ -10,5 +10,6 @@ message AdminModuleList {
bool isSuper = 2;
repeated AdminModule Modules = 3;
string fullname = 4;
string theme = 5;
string theme = 5; // 风格模板
string lang = 6; // 界面语言
}