Skip to content

Commit

Permalink
fix issue reading resource from manage in function New of package gres (
Browse files Browse the repository at this point in the history
  • Loading branch information
hailaz committed Oct 10, 2023
1 parent d10d968 commit 3cd0059
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 50 deletions.
8 changes: 6 additions & 2 deletions i18n/gi18n/gi18n_ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
// Package gi18n implements internationalization and localization.
package gi18n

import "context"
import (
"context"

"github.com/gogf/gf/v2/os/gctx"
)

const (
ctxLanguage = "I18nLanguage"
ctxLanguage gctx.StrKey = "I18nLanguage"
)

// WithLanguage append language setting to the context and returns a new context.
Expand Down
124 changes: 83 additions & 41 deletions i18n/gi18n/gi18n_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,30 @@ import (
"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.
options Options // configuration options.
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.
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 (
Expand All @@ -54,10 +65,25 @@ var (
// 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 = DefaultOptions()
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
Expand All @@ -72,45 +98,47 @@ func New(options ...Options) *Manager {
gregex.Quote(opts.Delimiters[0]),
gregex.Quote(opts.Delimiters[1]),
),
pathType: pathType,
}
intlog.Printf(context.TODO(), `New: %#v`, m)
return m
}

// DefaultOptions creates and returns a default options for i18n manager.
func DefaultOptions() Options {
var path string
for _, folder := range searchFolders {
path, _ = gfile.Search(folder)
if path != "" {
break
}
// checkPathType checks and returns the path type for given directory path.
func (o *Options) checkPathType(dirPath string) pathType {
if dirPath == "" {
return pathTypeNone
}
if path != "" {
// To avoid of the source path of GoFrame: github.com/gogf/i18n/gi18n
if gfile.Exists(path + gfile.Separator + "gi18n") {
path = ""
}

if o.Resource == nil {
o.Resource = gres.Instance()
}
return Options{
Path: path,
Language: "en",
Delimiters: defaultDelimiters,

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 {
if gres.Contains(path) {
m.options.Path = path
} else {
realPath, _ := gfile.Search(path)
if realPath == "" {
return gerror.NewCodef(gcode.CodeInvalidParameter, `%s does not exist`, path)
}
m.options.Path = realPath
pathType := m.options.checkPathType(path)
if pathType == pathTypeNone {
return gerror.NewCodef(gcode.CodeInvalidParameter, `%s does not exist`, path)
}
intlog.Printf(context.TODO(), `SetPath: %s`, m.options.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
}

Expand Down Expand Up @@ -190,6 +218,13 @@ func (m *Manager) GetContent(ctx context.Context, key string) string {
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) {
Expand All @@ -201,10 +236,17 @@ func (m *Manager) init(ctx context.Context) {
}
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()
if gres.Contains(m.options.Path) {
files := gres.ScanDirFile(m.options.Path, "*.*", true)
switch m.pathType {
case pathTypeGres:
files := m.options.Resource.ScanDirFile(m.options.Path, "*.*", true)
if len(files) > 0 {
var (
path string
Expand Down Expand Up @@ -234,7 +276,7 @@ func (m *Manager) init(ctx context.Context) {
}
}
}
} else if m.options.Path != "" {
case pathTypeNormal:
files, _ := gfile.ScanDirFile(m.options.Path, "*.*", true)
if len(files) == 0 {
return
Expand Down Expand Up @@ -264,12 +306,12 @@ func (m *Manager) init(ctx context.Context) {
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(path, func(event *gfsnotify.Event) {
_, _ = 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.mu.Lock()
m.data = nil
m.mu.Unlock()
m.reset()
gfsnotify.Exit()
})
}
Expand Down
88 changes: 82 additions & 6 deletions i18n/gi18n/gi18n_z_unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
package gi18n_test

import (
"time"

"github.com/gogf/gf/v2/encoding/gbase64"
"github.com/gogf/gf/v2/os/gctx"
_ "github.com/gogf/gf/v2/os/gres/testdata/data"

"context"
"testing"
Expand Down Expand Up @@ -120,10 +122,9 @@ func Test_DefaultManager(t *testing.T) {
}

func Test_Instance(t *testing.T) {
gres.Dump()
gtest.C(t, func(t *gtest.T) {
m := gi18n.Instance()
err := m.SetPath("i18n-dir")
err := m.SetPath(gtest.DataPath("i18n-dir"))
t.AssertNil(err)
m.SetLanguage("zh-CN")
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界")
Expand All @@ -141,15 +142,15 @@ func Test_Instance(t *testing.T) {
// Default language is: en
gtest.C(t, func(t *gtest.T) {
m := gi18n.Instance(gconv.String(gtime.TimestampNano()))
m.SetPath("i18n-dir")
m.SetPath(gtest.DataPath("i18n-dir"))
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "HelloWorld")
})
}

func Test_Resource(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.I18n("resource")
err := m.SetPath("i18n-dir")
err := m.SetPath(gtest.DataPath("i18n-dir"))
t.AssertNil(err)

m.SetLanguage("none")
Expand Down Expand Up @@ -180,8 +181,83 @@ func Test_SetCtxLanguage(t *testing.T) {
})

gtest.C(t, func(t *gtest.T) {
ctx := gi18n.WithLanguage(nil, "zh-CN")
ctx := gi18n.WithLanguage(context.Background(), "zh-CN")
t.Assert(gi18n.LanguageFromCtx(ctx), "zh-CN")
})

}

func Test_GetContent(t *testing.T) {
i18n := gi18n.New(gi18n.Options{
Path: gtest.DataPath("i18n-file"),
})
gtest.C(t, func(t *gtest.T) {
t.Assert(i18n.GetContent(context.Background(), "hello"), "Hello")

ctx := gi18n.WithLanguage(context.Background(), "zh-CN")
t.Assert(i18n.GetContent(ctx, "hello"), "你好")

ctx = gi18n.WithLanguage(context.Background(), "unknown")
t.Assert(i18n.GetContent(ctx, "hello"), "")
})
}

func Test_PathInResource(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
binContent, err := gres.Pack(gtest.DataPath("i18n"))
t.AssertNil(err)
err = gres.Add(gbase64.EncodeToString(binContent))
t.AssertNil(err)

i18n := gi18n.New()
i18n.SetLanguage("zh-CN")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界")

err = i18n.SetPath("i18n")
t.Assert(err, nil)
i18n.SetLanguage("ja")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
})
}

func Test_PathInNormal(t *testing.T) {
// Copy i18n files to current directory.
gfile.CopyDir(gtest.DataPath("i18n"), gfile.Join(gdebug.CallerDirectory(), "manifest/i18n"))
// Remove copied files after testing.
defer gfile.Remove(gfile.Join(gdebug.CallerDirectory(), "manifest"))

i18n := gi18n.New()

gtest.C(t, func(t *gtest.T) {
i18n.SetLanguage("zh-CN")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界")
// Set not exist path.
err := i18n.SetPath("i18n-not-exist")
t.AssertNE(err, nil)
err = i18n.SetPath("")
t.AssertNE(err, nil)
i18n.SetLanguage("ja")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
})

// Change language file content.
gtest.C(t, func(t *gtest.T) {
i18n.SetLanguage("en")
t.Assert(i18n.T(context.Background(), "{#hello}{#world}{#name}"), "HelloWorld{#name}")
err := gfile.PutContentsAppend(gfile.Join(gdebug.CallerDirectory(), "manifest/i18n/en.toml"), "\nname = \"GoFrame\"")
t.Assert(err, nil)
// Wait for the file modification time to change.
time.Sleep(10 * time.Millisecond)
t.Assert(i18n.T(context.Background(), "{#hello}{#world}{#name}"), "HelloWorldGoFrame")
})

// Add new language
gtest.C(t, func(t *gtest.T) {
err := gfile.PutContents(gfile.Join(gdebug.CallerDirectory(), "manifest/i18n/en-US.toml"), "lang = \"en-US\"")
t.Assert(err, nil)
// Wait for the file modification time to change.
time.Sleep(10 * time.Millisecond)
i18n.SetLanguage("en-US")
t.Assert(i18n.T(context.Background(), "{#lang}"), "en-US")
})
}
3 changes: 2 additions & 1 deletion i18n/gi18n/testdata/i18n-dir/zh-CN/hello.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"hello": "你好"
"你好": "hello",
"hello": "你好"
}
1 change: 1 addition & 0 deletions i18n/gi18n/testdata/i18n-dir/zh-CN/world.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"世界": "world",
"world": "世界"
}

0 comments on commit 3cd0059

Please sign in to comment.