mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
This pull request standardizes the use of the Go 1.18+ `any` type alias
instead of `interface{}` throughout the codebase. The change improves
code readability and aligns with modern Go best practices. The update
touches many files, including core data structures, code generation
templates, logging utilities, and test data, ensuring consistency across
all usages.
**Type alias migration to `any`:**
* Replaced all instances of `interface{}` with `any` in core data
structures such as `garray` and in generated model structs (e.g.,
`TableUser`, `User1`, `User2`) to modernize type usage.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[3]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[4]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[5]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[6]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
* Updated function signatures, method parameters, and return types from
`interface{}` to `any` in various parts of the codebase, including code
generation, service logic, and logging utilities (e.g., `mlog`).
[[1]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[2]](diffhunk://#diff-2b1953fb78cf3593d8c2c7d911e95b65fd0b847c30ed0b4d167d16fe6d781235L54-R74)
[[3]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[4]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
[[5]](diffhunk://#diff-c5d51d56f487779a2b6207c7ad26c7a20bbadcc846ce094fe60ab4cabff58c51L107-R107)
[[6]](diffhunk://#diff-f96e6a9fdb416eb1804ceaba1fe0ac637bff22c43837f8bb849c2366ce72d4a1L116-R121)
[[7]](diffhunk://#diff-f94c83a1b08ae060d9346f4a6031fc4a7b9a0b894e02d9afaa09018b6598eac0L112-R112)
[[8]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L36-R36)
[[9]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L74-R74)
[[10]](diffhunk://#diff-748b11dbe8828dd4c040ec23cae0b8fe57ecf0a2d1b7694ea39102294e633c64L96-R96)
**Generated code and templates:**
* Adjusted generated files and code generation templates to output `any`
instead of `interface{}` for relevant struct fields and function
signatures, ensuring that new code generation aligns with the updated
convention.
[[1]](diffhunk://#diff-6c19859cb32c7516ea95ddc8f8235460818eb2f24d2204308e0d9e1b19e7d90fL15-R19)
[[2]](diffhunk://#diff-a15ba2f5e830b4833c47b902515a4f9e5a4f83a3707698f3229b307ec3776b41L15-R18)
[[3]](diffhunk://#diff-52e0837e84d49221d1b810d88fdf78221f36cffcd664fb42f8aba49a79b974dcL15-R19)
[[4]](diffhunk://#diff-11c3457d1a23a4ca6ecd00d6b856289774936b6a708384cf03aff164044e7546L15-R19)
[[5]](diffhunk://#diff-2cff9cf8e6a0cc34087326d8c8149c3bbaf74c76fdbdf5a73daed13cc04249e1L15-R19)
[[6]](diffhunk://#diff-175edfeea54490b8fe4e18ffcbea5835efaf8f0b8acf623359073987cae7eb76L48-R55)
[[7]](diffhunk://#diff-e001b7a4b63603b9b14f00de78a4d570bb76c5f57d856a24643f071032e12356L66-R73)
[[8]](diffhunk://#diff-5582954e8a9983988dc8854ad82067fb2ac6269b988e07357ad8db1dfec5f1a0L39-R41)
**Container and utility updates:**
* Refactored the `garray` container implementation and related
constructors/methods to use `[]any` instead of `[]interface{}`, along
with corresponding function signatures.
[[1]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L31-R31)
[[2]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L52-R52)
[[3]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L62-R62)
[[4]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L73-R86)
[[5]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L96-R97)
[[6]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L107-R114)
[[7]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L124-R124)
[[8]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L135-R143)
[[9]](diffhunk://#diff-3a1259e160a4dfa5fe49dfe739fbdb986c0d0a2220a709882ea48d3ae1b8f911L167-R167)
These changes collectively modernize the codebase and prepare it for
future Go developments by using the idiomatic `any` type.
319 lines
8.5 KiB
Go
319 lines
8.5 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gi18n
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gogf/gf/v2/encoding/gjson"
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/internal/intlog"
|
|
"github.com/gogf/gf/v2/os/gfile"
|
|
"github.com/gogf/gf/v2/os/gfsnotify"
|
|
"github.com/gogf/gf/v2/os/gres"
|
|
"github.com/gogf/gf/v2/text/gregex"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
// pathType is the type for i18n file path.
|
|
type pathType string
|
|
|
|
const (
|
|
pathTypeNone pathType = "none"
|
|
pathTypeNormal pathType = "normal"
|
|
pathTypeGres pathType = "gres"
|
|
)
|
|
|
|
// Manager for i18n contents, it is concurrent safe, supporting hot reload.
|
|
type Manager struct {
|
|
mu sync.RWMutex
|
|
data map[string]map[string]string // Translating map.
|
|
pattern string // Pattern for regex parsing.
|
|
pathType pathType // Path type for i18n files.
|
|
options Options // configuration options.
|
|
}
|
|
|
|
// Options is used for i18n object configuration.
|
|
type Options struct {
|
|
Path string // I18n files storage path.
|
|
Language string // Default local language.
|
|
Delimiters []string // Delimiters for variable parsing.
|
|
Resource *gres.Resource // Resource for i18n files.
|
|
}
|
|
|
|
var (
|
|
// defaultLanguage defines the default language if user does not specify in options.
|
|
defaultLanguage = "en"
|
|
|
|
// defaultDelimiters defines the default key variable delimiters.
|
|
defaultDelimiters = []string{"{#", "}"}
|
|
|
|
// i18n files searching folders.
|
|
searchFolders = []string{"manifest/i18n", "manifest/config/i18n", "i18n"}
|
|
)
|
|
|
|
// New creates and returns a new i18n manager.
|
|
// The optional parameter `option` specifies the custom options for i18n manager.
|
|
// It uses a default one if it's not passed.
|
|
func New(options ...Options) *Manager {
|
|
var opts Options
|
|
var pathType = pathTypeNone
|
|
if len(options) > 0 {
|
|
opts = options[0]
|
|
pathType = opts.checkPathType(opts.Path)
|
|
} else {
|
|
opts = Options{}
|
|
for _, folder := range searchFolders {
|
|
pathType = opts.checkPathType(folder)
|
|
if pathType != pathTypeNone {
|
|
break
|
|
}
|
|
}
|
|
if opts.Path != "" {
|
|
// To avoid of the source path of GoFrame: github.com/gogf/i18n/gi18n
|
|
if gfile.Exists(opts.Path + gfile.Separator + "gi18n") {
|
|
opts.Path = ""
|
|
pathType = pathTypeNone
|
|
}
|
|
}
|
|
}
|
|
if len(opts.Language) == 0 {
|
|
opts.Language = defaultLanguage
|
|
}
|
|
if len(opts.Delimiters) == 0 {
|
|
opts.Delimiters = defaultDelimiters
|
|
}
|
|
m := &Manager{
|
|
options: opts,
|
|
pattern: fmt.Sprintf(
|
|
`%s(.+?)%s`,
|
|
gregex.Quote(opts.Delimiters[0]),
|
|
gregex.Quote(opts.Delimiters[1]),
|
|
),
|
|
pathType: pathType,
|
|
}
|
|
intlog.Printf(context.TODO(), `New: %#v`, m)
|
|
return m
|
|
}
|
|
|
|
// checkPathType checks and returns the path type for given directory path.
|
|
func (o *Options) checkPathType(dirPath string) pathType {
|
|
if dirPath == "" {
|
|
return pathTypeNone
|
|
}
|
|
|
|
if o.Resource == nil {
|
|
o.Resource = gres.Instance()
|
|
}
|
|
|
|
if o.Resource.Contains(dirPath) {
|
|
o.Path = dirPath
|
|
return pathTypeGres
|
|
}
|
|
|
|
realPath, _ := gfile.Search(dirPath)
|
|
if realPath != "" {
|
|
o.Path = realPath
|
|
return pathTypeNormal
|
|
}
|
|
|
|
return pathTypeNone
|
|
}
|
|
|
|
// SetPath sets the directory path storing i18n files.
|
|
func (m *Manager) SetPath(path string) error {
|
|
pathType := m.options.checkPathType(path)
|
|
if pathType == pathTypeNone {
|
|
return gerror.NewCodef(gcode.CodeInvalidParameter, `%s does not exist`, path)
|
|
}
|
|
|
|
m.pathType = pathType
|
|
intlog.Printf(context.TODO(), `SetPath[%s]: %s`, m.pathType, m.options.Path)
|
|
// Reset the manager after path changed.
|
|
m.reset()
|
|
return nil
|
|
}
|
|
|
|
// SetLanguage sets the language for translator.
|
|
func (m *Manager) SetLanguage(language string) {
|
|
m.options.Language = language
|
|
intlog.Printf(context.TODO(), `SetLanguage: %s`, m.options.Language)
|
|
}
|
|
|
|
// SetDelimiters sets the delimiters for translator.
|
|
func (m *Manager) SetDelimiters(left, right string) {
|
|
m.pattern = fmt.Sprintf(`%s(.+?)%s`, gregex.Quote(left), gregex.Quote(right))
|
|
intlog.Printf(context.TODO(), `SetDelimiters: %v`, m.pattern)
|
|
}
|
|
|
|
// T is alias of Translate for convenience.
|
|
func (m *Manager) T(ctx context.Context, content string) string {
|
|
return m.Translate(ctx, content)
|
|
}
|
|
|
|
// Tf is alias of TranslateFormat for convenience.
|
|
func (m *Manager) Tf(ctx context.Context, format string, values ...any) string {
|
|
return m.TranslateFormat(ctx, format, values...)
|
|
}
|
|
|
|
// TranslateFormat translates, formats and returns the `format` with configured language
|
|
// and given `values`.
|
|
func (m *Manager) TranslateFormat(ctx context.Context, format string, values ...any) string {
|
|
return fmt.Sprintf(m.Translate(ctx, format), values...)
|
|
}
|
|
|
|
// Translate translates `content` with configured language.
|
|
func (m *Manager) Translate(ctx context.Context, content string) string {
|
|
m.init(ctx)
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
transLang := m.options.Language
|
|
if lang := LanguageFromCtx(ctx); lang != "" {
|
|
transLang = lang
|
|
}
|
|
data := m.data[transLang]
|
|
if data == nil {
|
|
return content
|
|
}
|
|
// Parse content as name.
|
|
if v, ok := data[content]; ok {
|
|
return v
|
|
}
|
|
// Parse content as variables container.
|
|
result, _ := gregex.ReplaceStringFuncMatch(
|
|
m.pattern, content,
|
|
func(match []string) string {
|
|
if v, ok := data[match[1]]; ok {
|
|
return v
|
|
}
|
|
// return match[1] will return the content between delimiters
|
|
// return match[0] will return the original content
|
|
return match[0]
|
|
})
|
|
intlog.Printf(ctx, `Translate for language: %s`, transLang)
|
|
return result
|
|
}
|
|
|
|
// GetContent retrieves and returns the configured content for given key and specified language.
|
|
// It returns an empty string if not found.
|
|
func (m *Manager) GetContent(ctx context.Context, key string) string {
|
|
m.init(ctx)
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
transLang := m.options.Language
|
|
if lang := LanguageFromCtx(ctx); lang != "" {
|
|
transLang = lang
|
|
}
|
|
if data, ok := m.data[transLang]; ok {
|
|
return data[key]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// reset reset data of the manager.
|
|
func (m *Manager) reset() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.data = nil
|
|
}
|
|
|
|
// init initializes the manager for lazy initialization design.
|
|
// The i18n manager is only initialized once.
|
|
func (m *Manager) init(ctx context.Context) {
|
|
m.mu.RLock()
|
|
// If the data is not nil, means it's already initialized.
|
|
if m.data != nil {
|
|
m.mu.RUnlock()
|
|
return
|
|
}
|
|
m.mu.RUnlock()
|
|
|
|
defer func() {
|
|
intlog.Printf(ctx, `Manager init finish: %#v`, m)
|
|
}()
|
|
|
|
intlog.Printf(ctx, `init path: %s`, m.options.Path)
|
|
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
switch m.pathType {
|
|
case pathTypeGres:
|
|
files := m.options.Resource.ScanDirFile(m.options.Path, "*.*", true)
|
|
if len(files) > 0 {
|
|
var (
|
|
path string
|
|
name string
|
|
lang string
|
|
array []string
|
|
)
|
|
m.data = make(map[string]map[string]string)
|
|
for _, file := range files {
|
|
name = file.Name()
|
|
path = name[len(m.options.Path)+1:]
|
|
array = strings.Split(path, "/")
|
|
if len(array) > 1 {
|
|
lang = array[0]
|
|
} else if len(array) == 1 {
|
|
lang = gfile.Name(array[0])
|
|
}
|
|
if m.data[lang] == nil {
|
|
m.data[lang] = make(map[string]string)
|
|
}
|
|
if j, err := gjson.LoadContent(file.Content()); err == nil {
|
|
for k, v := range j.Var().Map() {
|
|
m.data[lang][k] = gconv.String(v)
|
|
}
|
|
} else {
|
|
intlog.Errorf(ctx, "load i18n file '%s' failed: %+v", name, err)
|
|
}
|
|
}
|
|
}
|
|
case pathTypeNormal:
|
|
files, _ := gfile.ScanDirFile(m.options.Path, "*.*", true)
|
|
if len(files) == 0 {
|
|
return
|
|
}
|
|
var (
|
|
path string
|
|
lang string
|
|
array []string
|
|
)
|
|
m.data = make(map[string]map[string]string)
|
|
for _, file := range files {
|
|
path = file[len(m.options.Path)+1:]
|
|
array = strings.Split(path, gfile.Separator)
|
|
if len(array) > 1 {
|
|
lang = array[0]
|
|
} else if len(array) == 1 {
|
|
lang = gfile.Name(array[0])
|
|
}
|
|
if m.data[lang] == nil {
|
|
m.data[lang] = make(map[string]string)
|
|
}
|
|
if j, err := gjson.LoadContent(gfile.GetBytes(file)); err == nil {
|
|
for k, v := range j.Var().Map() {
|
|
m.data[lang][k] = gconv.String(v)
|
|
}
|
|
} else {
|
|
intlog.Errorf(ctx, "load i18n file '%s' failed: %+v", file, err)
|
|
}
|
|
}
|
|
intlog.Printf(ctx, "i18n files loaded in path: %s", m.options.Path)
|
|
// Monitor changes of i18n files for hot reload feature.
|
|
_, _ = gfsnotify.Add(m.options.Path, func(event *gfsnotify.Event) {
|
|
intlog.Printf(ctx, `i18n file changed: %s`, event.Path)
|
|
// Any changes of i18n files, clear the data.
|
|
m.reset()
|
|
gfsnotify.Exit()
|
|
})
|
|
}
|
|
}
|