mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-02-03 20:20:10 +00:00
i18n: replace gettext implementation (#1056)
This commit is contained in:
4
go.mod
4
go.mod
@@ -4,7 +4,6 @@ go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/appleboy/gin-jwt/v2 v2.10.2
|
||||
github.com/chai2010/gettext-go v1.0.3
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20240428194347-eebcea082ee0
|
||||
github.com/gin-contrib/pprof v1.5.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
@@ -18,6 +17,7 @@ require (
|
||||
github.com/knadh/koanf/providers/env v1.0.0
|
||||
github.com/knadh/koanf/providers/file v1.1.2
|
||||
github.com/knadh/koanf/v2 v2.1.2
|
||||
github.com/leonelquinteros/gotext v1.7.1
|
||||
github.com/libdns/cloudflare v0.1.3
|
||||
github.com/libdns/he v1.0.2
|
||||
github.com/libdns/libdns v0.2.3
|
||||
@@ -29,6 +29,7 @@ require (
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
@@ -74,7 +75,6 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/swaggo/swag v1.16.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -9,8 +9,6 @@ github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=
|
||||
github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
@@ -98,6 +96,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/leonelquinteros/gotext v1.7.1 h1:/JNPeE3lY5JeVYv2+KBpz39994W3W9fmZCGq3eO9Ri8=
|
||||
github.com/leonelquinteros/gotext v1.7.1/go.mod h1:I0WoFDn9u2D3VbPnnDPT8mzZu0iSXG8iih+AH2fHHqg=
|
||||
github.com/libdns/cloudflare v0.1.3 h1:XPFa2f3Mm/3FDNwl9Ki2bfAQJ0Cm5GQB0e8PQVy25Us=
|
||||
github.com/libdns/cloudflare v0.1.3/go.mod h1:XbvSCSMcxspwpSialM3bq0LsS3/Houy9WYxW8Ok8b6M=
|
||||
github.com/libdns/he v1.0.2 h1:AUlHRkRyVCsoaifIXZRoH9dn+nj0MXXwLMvPV4OlNdk=
|
||||
|
||||
@@ -3,29 +3,45 @@ package i18n
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/chai2010/gettext-go"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
//go:embed translations
|
||||
var Translations embed.FS
|
||||
|
||||
type Localizer struct {
|
||||
intlMap map[string]gettext.Gettexter
|
||||
intlMap map[string]gotext.Translator
|
||||
lang string
|
||||
domain string
|
||||
path string
|
||||
fs fs.FS
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewLocalizer(lang, domain, path string, data any) *Localizer {
|
||||
intl := gettext.New(domain, path, data)
|
||||
intl.SetLanguage(lang)
|
||||
func NewLocalizer(lang, domain, path string, fs fs.FS) *Localizer {
|
||||
loc := &Localizer{
|
||||
intlMap: make(map[string]gotext.Translator),
|
||||
lang: lang,
|
||||
domain: domain,
|
||||
path: path,
|
||||
fs: fs,
|
||||
}
|
||||
|
||||
intlMap := make(map[string]gettext.Gettexter)
|
||||
intlMap[lang] = intl
|
||||
file := loc.findExt(lang, "mo")
|
||||
if file == "" {
|
||||
return loc
|
||||
}
|
||||
|
||||
return &Localizer{intlMap: intlMap, lang: lang}
|
||||
mo := gotext.NewMoFS(loc.fs)
|
||||
mo.ParseFile(file)
|
||||
loc.intlMap[lang] = mo
|
||||
|
||||
return loc
|
||||
}
|
||||
|
||||
func (l *Localizer) SetLanguage(lang string) {
|
||||
@@ -45,14 +61,19 @@ func (l *Localizer) Exists(lang string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *Localizer) AppendIntl(lang, domain, path string, data any) {
|
||||
intl := gettext.New(domain, path, data)
|
||||
intl.SetLanguage(lang)
|
||||
func (l *Localizer) AppendIntl(lang string) {
|
||||
file := l.findExt(lang, "mo")
|
||||
if file == "" {
|
||||
return
|
||||
}
|
||||
|
||||
mo := gotext.NewMoFS(l.fs)
|
||||
mo.ParseFile(file)
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
l.intlMap[lang] = intl
|
||||
l.intlMap[lang] = mo
|
||||
}
|
||||
|
||||
// Modified from k8s.io/kubectl/pkg/util/i18n
|
||||
@@ -65,7 +86,7 @@ func (l *Localizer) T(orig string) string {
|
||||
return orig
|
||||
}
|
||||
|
||||
return intl.PGettext("", orig)
|
||||
return intl.Get(orig)
|
||||
}
|
||||
|
||||
// N translates a string, possibly substituting arguments into it along
|
||||
@@ -80,9 +101,9 @@ func (l *Localizer) N(orig string, args ...int) string {
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
return intl.PGettext("", orig)
|
||||
return intl.Get(orig)
|
||||
}
|
||||
return fmt.Sprintf(intl.PNGettext("", orig, orig+".plural", args[0]),
|
||||
return fmt.Sprintf(intl.GetN(orig, orig+".plural", args[0]),
|
||||
args[0])
|
||||
}
|
||||
|
||||
@@ -96,3 +117,37 @@ func (l *Localizer) ErrorT(defaultValue string, args ...any) error {
|
||||
func (l *Localizer) Tf(defaultValue string, args ...any) string {
|
||||
return fmt.Sprintf(l.T(defaultValue), args...)
|
||||
}
|
||||
|
||||
// https://github.com/leonelquinteros/gotext/blob/v1.7.1/locale.go
|
||||
func (l *Localizer) findExt(lang, ext string) string {
|
||||
filename := path.Join(l.path, lang, "LC_MESSAGES", l.domain+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(lang) > 2 {
|
||||
filename = path.Join(l.path, lang[:2], "LC_MESSAGES", l.domain+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
filename = path.Join(l.path, lang, l.domain+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(lang) > 2 {
|
||||
filename = path.Join(l.path, lang[:2], l.domain+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *Localizer) fileExists(filename string) bool {
|
||||
_, err := fs.Stat(l.fs, filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
32
pkg/i18n/i18n_test.go
Normal file
32
pkg/i18n/i18n_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestI18n(t *testing.T) {
|
||||
const testStr = "database error"
|
||||
|
||||
t.Run("SwitchLocale", func(t *testing.T) {
|
||||
loc := NewLocalizer("zh_CN", "nezha", "translations", Translations)
|
||||
translated := loc.T(testStr)
|
||||
if translated != "数据库错误" {
|
||||
t.Fatalf("expected %s, but got %s", "数据库错误", translated)
|
||||
}
|
||||
|
||||
loc.AppendIntl("zh_TW")
|
||||
loc.SetLanguage("zh_TW")
|
||||
translated = loc.T(testStr)
|
||||
if translated != "資料庫錯誤" {
|
||||
t.Fatalf("expected %s, but got %s", "資料庫錯誤", translated)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Fallback", func(t *testing.T) {
|
||||
loc := NewLocalizer("invalid", "nezha", "translations", Translations)
|
||||
fallbackStr := loc.T(testStr)
|
||||
if fallbackStr != testStr {
|
||||
t.Fatalf("expected %s, but got %s", testStr, fallbackStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package singleton
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
@@ -27,12 +24,7 @@ func loadTranslation() error {
|
||||
}
|
||||
|
||||
lang = strings.Replace(lang, "-", "_", 1)
|
||||
data, err := getTranslationArchive(lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Localizer = i18n.NewLocalizer(lang, domain, domain+".zip", data)
|
||||
Localizer = i18n.NewLocalizer(lang, domain, "translations", i18n.Translations)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,41 +35,7 @@ func OnUpdateLang(lang string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := getTranslationArchive(lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Localizer.AppendIntl(lang, domain, domain+".zip", data)
|
||||
Localizer.AppendIntl(lang)
|
||||
Localizer.SetLanguage(lang)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTranslationArchive(lang string) ([]byte, error) {
|
||||
files := [...]string{
|
||||
fmt.Sprintf("translations/%s/LC_MESSAGES/%s.po", lang, domain),
|
||||
fmt.Sprintf("translations/%s/LC_MESSAGES/%s.mo", lang, domain),
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
w := zip.NewWriter(buf)
|
||||
|
||||
for _, file := range files {
|
||||
f, err := w.Create(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := i18n.Translations.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := f.Write(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user