mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
improve package gi18n/gview/gvalid for more flexable i18n feature controll,add custom error configuration and i18n translation for custom error message
This commit is contained in:
@ -7,6 +7,8 @@
|
||||
// Package gi18n implements internationalization and localization.
|
||||
package gi18n
|
||||
|
||||
import "context"
|
||||
|
||||
// SetPath sets the directory path storing i18n files.
|
||||
func SetPath(path string) error {
|
||||
return Instance().SetPath(path)
|
||||
@ -23,42 +25,29 @@ func SetDelimiters(left, right string) {
|
||||
}
|
||||
|
||||
// T is alias of Translate for convenience.
|
||||
func T(content string, language ...string) string {
|
||||
return Instance().T(content, language...)
|
||||
func T(ctx context.Context, content string) string {
|
||||
return Instance().T(ctx, content)
|
||||
}
|
||||
|
||||
// Tf is alias of TranslateFormat for convenience.
|
||||
func Tf(format string, values ...interface{}) string {
|
||||
return Instance().TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// Tfl is alias of TranslateFormatLang for convenience.
|
||||
func Tfl(language string, format string, values ...interface{}) string {
|
||||
return Instance().TranslateFormatLang(language, format, values...)
|
||||
func Tf(ctx context.Context, format string, values ...interface{}) string {
|
||||
return Instance().TranslateFormat(ctx, format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func TranslateFormat(format string, values ...interface{}) string {
|
||||
return Instance().TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func TranslateFormatLang(language string, format string, values ...interface{}) string {
|
||||
return Instance().TranslateFormatLang(language, format, values...)
|
||||
func TranslateFormat(ctx context.Context, format string, values ...interface{}) string {
|
||||
return Instance().TranslateFormat(ctx, format, values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language and returns the translated content.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func Translate(content string, language ...string) string {
|
||||
return Instance().Translate(content, language...)
|
||||
func Translate(ctx context.Context, content string) string {
|
||||
return Instance().Translate(ctx, content)
|
||||
}
|
||||
|
||||
// GetValue retrieves and returns the configured content for given key and specified language.
|
||||
// GetContent retrieves and returns the configured content for given key and specified language.
|
||||
// It returns an empty string if not found.
|
||||
func GetContent(key string, language ...string) string {
|
||||
return Instance().GetContent(key, language...)
|
||||
func GetContent(ctx context.Context, key string) string {
|
||||
return Instance().GetContent(ctx, key)
|
||||
}
|
||||
|
||||
29
i18n/gi18n/gi18n_ctx.go
Normal file
29
i18n/gi18n/gi18n_ctx.go
Normal file
@ -0,0 +1,29 @@
|
||||
// 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 implements internationalization and localization.
|
||||
package gi18n
|
||||
|
||||
import "context"
|
||||
|
||||
const (
|
||||
ctxLanguage = "I18nLanguage"
|
||||
)
|
||||
|
||||
// WithLanguage append language setting to the context and returns a new context.
|
||||
func WithLanguage(ctx context.Context, language string) context.Context {
|
||||
return context.WithValue(ctx, ctxLanguage, language)
|
||||
}
|
||||
|
||||
// LanguageFromCtx retrieves and returns language name from context.
|
||||
// It returns an empty string if it is not set previously.
|
||||
func LanguageFromCtx(ctx context.Context) string {
|
||||
v := ctx.Value(ctxLanguage)
|
||||
if v != nil {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -9,7 +9,7 @@ package gi18n
|
||||
import "github.com/gogf/gf/container/gmap"
|
||||
|
||||
const (
|
||||
// Default group name for instance usage.
|
||||
// DefaultName is the default group name for instance usage.
|
||||
DefaultName = "default"
|
||||
)
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gi18n
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
@ -25,7 +26,7 @@ import (
|
||||
"github.com/gogf/gf/os/gres"
|
||||
)
|
||||
|
||||
// Manager, it is concurrent safe, supporting hot reload.
|
||||
// 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.
|
||||
@ -36,13 +37,13 @@ type Manager struct {
|
||||
// Options is used for i18n object configuration.
|
||||
type Options struct {
|
||||
Path string // I18n files storage path.
|
||||
Language string // Local language.
|
||||
Language string // Default local language.
|
||||
Delimiters []string // Delimiters for variable parsing.
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultDelimiters defines the default key variable delimiters.
|
||||
defaultDelimiters = []string{"{#", "}"}
|
||||
defaultLanguage = "en" // defaultDelimiters defines the default language if user does not specified in options.
|
||||
defaultDelimiters = []string{"{#", "}"} // defaultDelimiters defines the default key variable delimiters.
|
||||
)
|
||||
|
||||
// New creates and returns a new i18n manager.
|
||||
@ -55,6 +56,9 @@ func New(options ...Options) *Manager {
|
||||
} else {
|
||||
opts = DefaultOptions()
|
||||
}
|
||||
if len(opts.Language) == 0 {
|
||||
opts.Language = defaultLanguage
|
||||
}
|
||||
if len(opts.Delimiters) == 0 {
|
||||
opts.Delimiters = defaultDelimiters
|
||||
}
|
||||
@ -118,45 +122,30 @@ func (m *Manager) SetDelimiters(left, right string) {
|
||||
}
|
||||
|
||||
// T is alias of Translate for convenience.
|
||||
func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
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(format string, values ...interface{}) string {
|
||||
return m.TranslateFormat(format, values...)
|
||||
}
|
||||
|
||||
// Tfl is alias of TranslateFormatLang for convenience.
|
||||
func (m *Manager) Tfl(language string, format string, values ...interface{}) string {
|
||||
return m.TranslateFormatLang(language, format, values...)
|
||||
func (m *Manager) Tf(ctx context.Context, format string, values ...interface{}) string {
|
||||
return m.TranslateFormat(ctx, format, values...)
|
||||
}
|
||||
|
||||
// TranslateFormat translates, formats and returns the <format> with configured language
|
||||
// and given <values>.
|
||||
func (m *Manager) TranslateFormat(format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format), values...)
|
||||
}
|
||||
|
||||
// TranslateFormatLang translates, formats and returns the <format> with configured language
|
||||
// and given <values>. The parameter <language> specifies custom translation language ignoring
|
||||
// configured language. If <language> is given empty string, it uses the default configured
|
||||
// language for the translation.
|
||||
func (m *Manager) TranslateFormatLang(language string, format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(format, language), values...)
|
||||
func (m *Manager) TranslateFormat(ctx context.Context, format string, values ...interface{}) string {
|
||||
return fmt.Sprintf(m.Translate(ctx, format), values...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func (m *Manager) Translate(content string, language ...string) string {
|
||||
func (m *Manager) Translate(ctx context.Context, content string) string {
|
||||
m.init()
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
transLang := m.options.Language
|
||||
if len(language) > 0 && language[0] != "" {
|
||||
transLang = language[0]
|
||||
} else {
|
||||
transLang = m.options.Language
|
||||
if lang := LanguageFromCtx(ctx); lang != "" {
|
||||
transLang = lang
|
||||
}
|
||||
data := m.data[transLang]
|
||||
if data == nil {
|
||||
@ -179,17 +168,15 @@ func (m *Manager) Translate(content string, language ...string) string {
|
||||
return result
|
||||
}
|
||||
|
||||
// GetValue retrieves and returns the configured content for given key and specified language.
|
||||
// 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(key string, language ...string) string {
|
||||
func (m *Manager) GetContent(ctx context.Context, key string) string {
|
||||
m.init()
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
transLang := m.options.Language
|
||||
if len(language) > 0 && language[0] != "" {
|
||||
transLang = language[0]
|
||||
} else {
|
||||
transLang = m.options.Language
|
||||
if lang := LanguageFromCtx(ctx); lang != "" {
|
||||
transLang = lang
|
||||
}
|
||||
if data, ok := m.data[transLang]; ok {
|
||||
return data[key]
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gi18n_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/os/gres"
|
||||
@ -32,16 +33,16 @@ func Test_Basic(t *testing.T) {
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
i18n.SetLanguage("zh-CN")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
i18n.SetDelimiters("{$", "}")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(i18n.T("{$hello}{$world}"), "你好世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(i18n.T(context.Background(), "{$hello}{$world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -49,13 +50,13 @@ func Test_Basic(t *testing.T) {
|
||||
Path: gdebug.TestDataPath("i18n-file"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
i18n.SetLanguage("zh-CN")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -63,13 +64,13 @@ func Test_Basic(t *testing.T) {
|
||||
Path: gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n-dir",
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "こんにちは世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
i18n.SetLanguage("zh-CN")
|
||||
t.Assert(i18n.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(i18n.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,18 +81,10 @@ func Test_TranslateFormat(t *testing.T) {
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
i18n.SetLanguage("none")
|
||||
t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "{#hello}{#world} 2020")
|
||||
|
||||
i18n.SetLanguage("ja")
|
||||
t.Assert(i18n.Tf("{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
})
|
||||
// Tfl
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
i18n := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.TestDataPath("i18n"),
|
||||
})
|
||||
t.Assert(i18n.Tfl("ja", "{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
t.Assert(i18n.Tfl("zh-CN", "{#hello}{#world} %d", 2020), "你好世界 2020")
|
||||
t.Assert(i18n.Tf(context.Background(), "{#hello}{#world} %d", 2020), "こんにちは世界 2020")
|
||||
})
|
||||
}
|
||||
|
||||
@ -101,13 +94,13 @@ func Test_DefaultManager(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
|
||||
gi18n.SetLanguage("none")
|
||||
t.Assert(gi18n.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
gi18n.SetLanguage("ja")
|
||||
t.Assert(gi18n.T("{#hello}{#world}"), "こんにちは世界")
|
||||
t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
gi18n.SetLanguage("zh-CN")
|
||||
t.Assert(gi18n.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(gi18n.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
@ -115,13 +108,13 @@ func Test_DefaultManager(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
|
||||
gi18n.SetLanguage("none")
|
||||
t.Assert(gi18n.Translate("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
gi18n.SetLanguage("ja")
|
||||
t.Assert(gi18n.Translate("{#hello}{#world}"), "こんにちは世界")
|
||||
t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
gi18n.SetLanguage("zh-CN")
|
||||
t.Assert(gi18n.Translate("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(gi18n.Translate(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
}
|
||||
|
||||
@ -132,21 +125,21 @@ func Test_Instance(t *testing.T) {
|
||||
err := m.SetPath("i18n-dir")
|
||||
t.Assert(err, nil)
|
||||
m.SetLanguage("zh-CN")
|
||||
t.Assert(m.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gi18n.Instance()
|
||||
t.Assert(m.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(g.I18n().T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(g.I18n().T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
// Default language is: en
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := gi18n.Instance(gconv.String(gtime.TimestampNano()))
|
||||
t.Assert(m.T("{#hello}{#world}"), "HelloWorld")
|
||||
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "HelloWorld")
|
||||
})
|
||||
}
|
||||
|
||||
@ -157,12 +150,12 @@ func Test_Resource(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
|
||||
m.SetLanguage("none")
|
||||
t.Assert(m.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
m.SetLanguage("ja")
|
||||
t.Assert(m.T("{#hello}{#world}"), "こんにちは世界")
|
||||
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
m.SetLanguage("zh-CN")
|
||||
t.Assert(m.T("{#hello}{#world}"), "你好世界")
|
||||
t.Assert(m.T(context.Background(), "{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
}
|
||||
|
||||
@ -59,18 +59,18 @@ func (r *Response) WriteTplContent(content string, params ...gview.Params) error
|
||||
// ParseTpl parses given template file <tpl> with given template variables <params>
|
||||
// and returns the parsed template content.
|
||||
func (r *Response) ParseTpl(tpl string, params ...gview.Params) (string, error) {
|
||||
return r.Request.GetView().Parse(tpl, r.buildInVars(params...))
|
||||
return r.Request.GetView().Parse(r.Request.Context(), tpl, r.buildInVars(params...))
|
||||
}
|
||||
|
||||
// ParseDefault parses the default template file with params.
|
||||
func (r *Response) ParseTplDefault(params ...gview.Params) (string, error) {
|
||||
return r.Request.GetView().ParseDefault(r.buildInVars(params...))
|
||||
return r.Request.GetView().ParseDefault(r.Request.Context(), r.buildInVars(params...))
|
||||
}
|
||||
|
||||
// ParseTplContent parses given template file <file> with given template parameters <params>
|
||||
// and returns the parsed template content.
|
||||
func (r *Response) ParseTplContent(content string, params ...gview.Params) (string, error) {
|
||||
return r.Request.GetView().ParseContent(content, r.buildInVars(params...))
|
||||
return r.Request.GetView().ParseContent(r.Request.Context(), content, r.buildInVars(params...))
|
||||
}
|
||||
|
||||
// buildInVars merges build-in variables into <params> and returns the new template variables.
|
||||
|
||||
@ -26,7 +26,7 @@ func (p *utilAdmin) Index(r *Request) {
|
||||
"path": gfile.SelfPath(),
|
||||
"uri": strings.TrimRight(r.URL.Path, "/"),
|
||||
}
|
||||
buffer, _ := gview.ParseContent(`
|
||||
buffer, _ := gview.ParseContent(r.Context(), `
|
||||
<html>
|
||||
<head>
|
||||
<title>GoFrame Web Server Admin</title>
|
||||
|
||||
@ -62,7 +62,7 @@ func (p *utilPProf) Index(r *Request) {
|
||||
"profiles": profiles,
|
||||
}
|
||||
if len(action) == 0 {
|
||||
buffer, _ := gview.ParseContent(`
|
||||
buffer, _ := gview.ParseContent(r.Context(), `
|
||||
<html>
|
||||
<head>
|
||||
<title>GoFrame PProf</title>
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
package gview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
|
||||
@ -50,9 +51,9 @@ func checkAndInitDefaultView() {
|
||||
|
||||
// ParseContent parses the template content directly using the default view object
|
||||
// and returns the parsed content.
|
||||
func ParseContent(content string, params ...Params) (string, error) {
|
||||
func ParseContent(ctx context.Context, content string, params ...Params) (string, error) {
|
||||
checkAndInitDefaultView()
|
||||
return defaultViewObj.ParseContent(content, params...)
|
||||
return defaultViewObj.ParseContent(ctx, content, params...)
|
||||
}
|
||||
|
||||
// New returns a new view object.
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gview
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
@ -115,7 +116,7 @@ func (view *View) buildInFuncInclude(file interface{}, data ...map[string]interf
|
||||
return ""
|
||||
}
|
||||
// It will search the file internally.
|
||||
content, err := view.Parse(path, m)
|
||||
content, err := view.Parse(context.TODO(), path, m)
|
||||
if err != nil {
|
||||
return htmltpl.HTML(err.Error())
|
||||
}
|
||||
|
||||
@ -6,18 +6,33 @@
|
||||
|
||||
package gview
|
||||
|
||||
import "github.com/gogf/gf/util/gconv"
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
i18nLanguageVariableName = "I18nLanguage"
|
||||
)
|
||||
|
||||
// i18nTranslate translate the content with i18n feature.
|
||||
func (view *View) i18nTranslate(content string, params Params) string {
|
||||
func (view *View) i18nTranslate(ctx context.Context, content string, variables Params) string {
|
||||
if view.config.I18nManager != nil {
|
||||
if v, ok := params["I18nLanguage"]; ok {
|
||||
language := gconv.String(v)
|
||||
if language != "" {
|
||||
return view.config.I18nManager.T(content, language)
|
||||
}
|
||||
// Compatible with old version.
|
||||
if language, ok := variables[i18nLanguageVariableName]; ok {
|
||||
ctx = gi18n.WithLanguage(ctx, gconv.String(language))
|
||||
}
|
||||
return view.config.I18nManager.T(content)
|
||||
return view.config.I18nManager.T(ctx, content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// setI18nLanguageFromCtx retrieves language name from context and sets it to template variables map.
|
||||
func (view *View) setI18nLanguageFromCtx(ctx context.Context, variables map[string]interface{}) {
|
||||
if language, ok := variables[i18nLanguageVariableName]; !ok {
|
||||
if language = gi18n.LanguageFromCtx(ctx); language != "" {
|
||||
variables[i18nLanguageVariableName] = language
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ package gview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
@ -54,7 +55,7 @@ var (
|
||||
|
||||
// Parse parses given template file <file> with given template variables <params>
|
||||
// and returns the parsed template content.
|
||||
func (view *View) Parse(file string, params ...Params) (result string, err error) {
|
||||
func (view *View) Parse(ctx context.Context, file string, params ...Params) (result string, err error) {
|
||||
var tpl interface{}
|
||||
// It caches the file, folder and its content to enhance performance.
|
||||
r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} {
|
||||
@ -125,6 +126,8 @@ func (view *View) Parse(file string, params ...Params) (result string, err error
|
||||
if len(view.data) > 0 {
|
||||
gutil.MapMerge(variables, view.data)
|
||||
}
|
||||
view.setI18nLanguageFromCtx(ctx, variables)
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if view.config.AutoEncode {
|
||||
newTpl, err := tpl.(*htmltpl.Template).Clone()
|
||||
@ -142,18 +145,18 @@ func (view *View) Parse(file string, params ...Params) (result string, err error
|
||||
|
||||
// TODO any graceful plan to replace "<no value>"?
|
||||
result = gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
result = view.i18nTranslate(ctx, result, variables)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseDefault parses the default template file with params.
|
||||
func (view *View) ParseDefault(params ...Params) (result string, err error) {
|
||||
return view.Parse(view.config.DefaultFile, params...)
|
||||
func (view *View) ParseDefault(ctx context.Context, params ...Params) (result string, err error) {
|
||||
return view.Parse(ctx, view.config.DefaultFile, params...)
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content> with template variables <params>
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) ParseContent(content string, params ...Params) (string, error) {
|
||||
func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) {
|
||||
// It's not necessary continuing parsing if template content is empty.
|
||||
if content == "" {
|
||||
return "", nil
|
||||
@ -191,6 +194,8 @@ func (view *View) ParseContent(content string, params ...Params) (string, error)
|
||||
if len(view.data) > 0 {
|
||||
gutil.MapMerge(variables, view.data)
|
||||
}
|
||||
view.setI18nLanguageFromCtx(ctx, variables)
|
||||
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if view.config.AutoEncode {
|
||||
newTpl, err := tpl.(*htmltpl.Template).Clone()
|
||||
@ -207,7 +212,7 @@ func (view *View) ParseContent(content string, params ...Params) (string, error)
|
||||
}
|
||||
// TODO any graceful plan to replace "<no value>"?
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
result = view.i18nTranslate(ctx, result, variables)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gview_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/encoding/ghtml"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
@ -39,18 +40,18 @@ func Test_Basic(t *testing.T) {
|
||||
view.Assigns(g.Map{"version": "1.7.0"})
|
||||
view.BindFunc("GetName", func() string { return "gf" })
|
||||
view.BindFuncMap(gview.FuncMap{"GetVersion": func() string { return "1.7.0" }})
|
||||
result, err := view.ParseContent(str, g.Map{"other": "that's all"})
|
||||
result, err := view.ParseContent(context.TODO(), str, g.Map{"other": "that's all"})
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, "hello gf,version:1.7.0;hello gf,version:1.7.0;that's all")
|
||||
|
||||
//测试api方法
|
||||
str = `hello {{.name}}`
|
||||
result, err = gview.ParseContent(str, g.Map{"name": "gf"})
|
||||
result, err = gview.ParseContent(context.TODO(), str, g.Map{"name": "gf"})
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, "hello gf")
|
||||
|
||||
//测试instance方法
|
||||
result, err = gview.Instance().ParseContent(str, g.Map{"name": "gf"})
|
||||
result, err = gview.Instance().ParseContent(context.TODO(), str, g.Map{"name": "gf"})
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, "hello gf")
|
||||
})
|
||||
@ -59,132 +60,132 @@ func Test_Basic(t *testing.T) {
|
||||
func Test_Func(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
str := `{{eq 1 1}};{{eq 1 2}};{{eq "A" "B"}}`
|
||||
result, err := gview.ParseContent(str, nil)
|
||||
result, err := gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `true;false;false`)
|
||||
|
||||
str = `{{ne 1 2}};{{ne 1 1}};{{ne "A" "B"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `true;false;true`)
|
||||
|
||||
str = `{{lt 1 2}};{{lt 1 1}};{{lt 1 0}};{{lt "A" "B"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `true;false;false;true`)
|
||||
|
||||
str = `{{le 1 2}};{{le 1 1}};{{le 1 0}};{{le "A" "B"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `true;true;false;true`)
|
||||
|
||||
str = `{{gt 1 2}};{{gt 1 1}};{{gt 1 0}};{{gt "A" "B"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `false;false;true;false`)
|
||||
|
||||
str = `{{ge 1 2}};{{ge 1 1}};{{ge 1 0}};{{ge "A" "B"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `false;true;true;false`)
|
||||
|
||||
str = `{{"<div>测试</div>"|text}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `测试`)
|
||||
|
||||
str = `{{"<div>测试</div>"|html}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `<div>测试</div>`)
|
||||
|
||||
str = `{{"<div>测试</div>"|htmlencode}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `<div>测试</div>`)
|
||||
|
||||
str = `{{"<div>测试</div>"|htmldecode}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `<div>测试</div>`)
|
||||
|
||||
str = `{{"https://goframe.org"|url}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `https%3A%2F%2Fgoframe.org`)
|
||||
|
||||
str = `{{"https://goframe.org"|urlencode}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `https%3A%2F%2Fgoframe.org`)
|
||||
|
||||
str = `{{"https%3A%2F%2Fgoframe.org"|urldecode}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `https://goframe.org`)
|
||||
str = `{{"https%3NA%2F%2Fgoframe.org"|urldecode}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(gstr.Contains(result, "invalid URL escape"), true)
|
||||
|
||||
str = `{{1540822968 | date "Y-m-d"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `2018-10-29`)
|
||||
str = `{{date "Y-m-d"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
|
||||
str = `{{"我是中国人" | substr 2 -1}};{{"我是中国人" | substr 2 2}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `中国人;中国`)
|
||||
|
||||
str = `{{"我是中国人" | strlimit 2 "..."}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `我是...`)
|
||||
|
||||
str = `{{"I'm中国人" | replace "I'm" "我是"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `我是中国人`)
|
||||
|
||||
str = `{{compare "A" "B"}};{{compare "1" "2"}};{{compare 2 1}};{{compare 1 1}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `-1;-1;1;0`)
|
||||
|
||||
str = `{{"热爱GF热爱生活" | hidestr 20 "*"}};{{"热爱GF热爱生活" | hidestr 50 "*"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `热爱GF*爱生活;热爱****生活`)
|
||||
|
||||
str = `{{"热爱GF热爱生活" | highlight "GF" "red"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `热爱<span style="color:red;">GF</span>热爱生活`)
|
||||
|
||||
str = `{{"gf" | toupper}};{{"GF" | tolower}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `GF;gf`)
|
||||
|
||||
str = `{{concat "I" "Love" "GoFrame"}}`
|
||||
result, err = gview.ParseContent(str, nil)
|
||||
result, err = gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result, `ILoveGoFrame`)
|
||||
})
|
||||
// eq: multiple values.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
str := `{{eq 1 2 1 3 4 5}}`
|
||||
result, err := gview.ParseContent(str, nil)
|
||||
result, err := gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `true`)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
str := `{{eq 6 2 1 3 4 5}}`
|
||||
result, err := gview.ParseContent(str, nil)
|
||||
result, err := gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `false`)
|
||||
})
|
||||
@ -193,7 +194,7 @@ func Test_Func(t *testing.T) {
|
||||
func Test_FuncNl2Br(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
str := `{{"Go\nFrame" | nl2br}}`
|
||||
result, err := gview.ParseContent(str, nil)
|
||||
result, err := gview.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result, `Go<br>Frame`)
|
||||
})
|
||||
@ -203,7 +204,7 @@ func Test_FuncNl2Br(t *testing.T) {
|
||||
s += "Go\nFrame\n中文"
|
||||
}
|
||||
str := `{{.content | nl2br}}`
|
||||
result, err := gview.ParseContent(str, g.Map{
|
||||
result, err := gview.ParseContent(context.TODO(), str, g.Map{
|
||||
"content": s,
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -231,10 +232,10 @@ func Test_FuncInclude(t *testing.T) {
|
||||
ioutil.WriteFile(templatePath+gfile.Separator+"footer.html", []byte(footer), 0644)
|
||||
ioutil.WriteFile(templatePath+gfile.Separator+"layout.html", []byte(layout), 0644)
|
||||
view := gview.New(templatePath)
|
||||
result, err := view.Parse("notfound.html")
|
||||
result, err := view.Parse(context.TODO(), "notfound.html")
|
||||
t.Assert(err != nil, true)
|
||||
t.Assert(result, ``)
|
||||
result, err = view.Parse("layout.html")
|
||||
result, err = view.Parse(context.TODO(), "layout.html")
|
||||
t.Assert(err != nil, false)
|
||||
t.Assert(result, `<h1>HEADER</h1>
|
||||
<h1>hello gf</h1>
|
||||
@ -243,7 +244,7 @@ func Test_FuncInclude(t *testing.T) {
|
||||
gfile.Mkdir(templatePath + gfile.Separator + "template")
|
||||
gfile.Create(notfoundPath)
|
||||
ioutil.WriteFile(notfoundPath, []byte("notfound"), 0644)
|
||||
result, err = view.Parse("notfound.html")
|
||||
result, err = view.Parse(context.TODO(), "notfound.html")
|
||||
t.Assert(err != nil, true)
|
||||
t.Assert(result, ``)
|
||||
})
|
||||
@ -283,7 +284,7 @@ func Test_ParseContent(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
str := `{{.name}}`
|
||||
view := gview.New()
|
||||
result, err := view.ParseContent(str, g.Map{"name": func() {}})
|
||||
result, err := view.ParseContent(context.TODO(), str, g.Map{"name": func() {}})
|
||||
t.Assert(err != nil, true)
|
||||
t.Assert(result, ``)
|
||||
})
|
||||
@ -306,7 +307,7 @@ func Test_HotReload(t *testing.T) {
|
||||
view := gview.New(dirPath)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
result, err := view.Parse("test.html", g.Map{
|
||||
result, err := view.Parse(context.TODO(), "test.html", g.Map{
|
||||
"var": "1",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -317,7 +318,7 @@ func Test_HotReload(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
result, err = view.Parse("test.html", g.Map{
|
||||
result, err = view.Parse(context.TODO(), "test.html", g.Map{
|
||||
"var": "2",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -329,7 +330,7 @@ func Test_XSS(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
s := "<br>"
|
||||
r, err := v.ParseContent("{{.v}}", g.Map{
|
||||
r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{
|
||||
"v": s,
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -339,7 +340,7 @@ func Test_XSS(t *testing.T) {
|
||||
v := gview.New()
|
||||
v.SetAutoEncode(true)
|
||||
s := "<br>"
|
||||
r, err := v.ParseContent("{{.v}}", g.Map{
|
||||
r, err := v.ParseContent(context.TODO(), "{{.v}}", g.Map{
|
||||
"v": s,
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -350,7 +351,7 @@ func Test_XSS(t *testing.T) {
|
||||
v := gview.New()
|
||||
v.SetAutoEncode(true)
|
||||
s := "<br>"
|
||||
r, err := v.ParseContent("{{if eq 1 1}}{{.v}}{{end}}", g.Map{
|
||||
r, err := v.ParseContent(context.TODO(), "{{if eq 1 1}}{{.v}}{{end}}", g.Map{
|
||||
"v": s,
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -371,7 +372,7 @@ func Test_BuildInFuncMap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.Assign("v", new(TypeForBuildInFuncMap))
|
||||
r, err := v.ParseContent("{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}")
|
||||
r, err := v.ParseContent(context.TODO(), "{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.Contains(r, "Name:john"), true)
|
||||
t.Assert(gstr.Contains(r, "Score:99.9"), true)
|
||||
@ -394,7 +395,7 @@ func Test_BuildInFuncMaps(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.Assign("v", new(TypeForBuildInFuncMaps))
|
||||
r, err := v.ParseContent("{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}")
|
||||
r, err := v.ParseContent(context.TODO(), "{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, ` 0:john 99.9 1:smith 100 `)
|
||||
})
|
||||
@ -407,7 +408,7 @@ func Test_BuildInFuncDump(t *testing.T) {
|
||||
"name": "john",
|
||||
"score": 100,
|
||||
})
|
||||
r, err := v.ParseContent("{{dump .}}")
|
||||
r, err := v.ParseContent(context.TODO(), "{{dump .}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.Contains(r, `"name": "john"`), true)
|
||||
t.Assert(gstr.Contains(r, `"score": 100`), true)
|
||||
@ -420,7 +421,7 @@ func Test_BuildInFuncJson(t *testing.T) {
|
||||
v.Assign("v", g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
r, err := v.ParseContent("{{json .v}}")
|
||||
r, err := v.ParseContent(context.TODO(), "{{json .v}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, `{"name":"john"}`)
|
||||
})
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gview_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gview"
|
||||
@ -30,11 +31,11 @@ func Test_Config(t *testing.T) {
|
||||
|
||||
str := `hello ${.name},version:${.version}`
|
||||
view.Assigns(g.Map{"version": "1.7.0"})
|
||||
result, err := view.ParseContent(str, nil)
|
||||
result, err := view.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result, "hello gf,version:1.7.0")
|
||||
|
||||
result, err = view.ParseDefault()
|
||||
result, err = view.ParseDefault(context.TODO())
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result, "name:gf")
|
||||
})
|
||||
@ -55,11 +56,11 @@ func Test_ConfigWithMap(t *testing.T) {
|
||||
|
||||
str := `hello ${.name},version:${.version}`
|
||||
view.Assigns(g.Map{"version": "1.7.0"})
|
||||
result, err := view.ParseContent(str, nil)
|
||||
result, err := view.ParseContent(context.TODO(), str, nil)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result, "hello gf,version:1.7.0")
|
||||
|
||||
result, err = view.ParseDefault()
|
||||
result, err = view.ParseDefault(context.TODO())
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result, "name:gf")
|
||||
})
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gview_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
@ -20,7 +21,7 @@ func Test_Encode_Parse(t *testing.T) {
|
||||
v := gview.New()
|
||||
v.SetPath(gdebug.TestDataPath("tpl"))
|
||||
v.SetAutoEncode(true)
|
||||
result, err := v.Parse("encode.tpl", g.Map{
|
||||
result, err := v.Parse(context.TODO(), "encode.tpl", g.Map{
|
||||
"title": "<b>my title</b>",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -33,7 +34,7 @@ func Test_Encode_ParseContent(t *testing.T) {
|
||||
v := gview.New()
|
||||
tplContent := gfile.GetContents(gdebug.TestDataPath("tpl", "encode.tpl"))
|
||||
v.SetAutoEncode(true)
|
||||
result, err := v.ParseContent(tplContent, g.Map{
|
||||
result, err := v.ParseContent(context.TODO(), tplContent, g.Map{
|
||||
"title": "<b>my title</b>",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gview_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
@ -26,21 +27,21 @@ func Test_I18n(t *testing.T) {
|
||||
g.I18n().SetPath(gdebug.TestDataPath("i18n"))
|
||||
|
||||
g.I18n().SetLanguage("zh-CN")
|
||||
result1, err := g.View().ParseContent(content, g.Map{
|
||||
result1, err := g.View().ParseContent(context.TODO(), content, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result1, expect1)
|
||||
|
||||
g.I18n().SetLanguage("ja")
|
||||
result2, err := g.View().ParseContent(content, g.Map{
|
||||
result2, err := g.View().ParseContent(context.TODO(), content, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result2, expect2)
|
||||
|
||||
g.I18n().SetLanguage("none")
|
||||
result3, err := g.View().ParseContent(content, g.Map{
|
||||
result3, err := g.View().ParseContent(context.TODO(), content, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
@ -54,21 +55,21 @@ func Test_I18n(t *testing.T) {
|
||||
|
||||
g.I18n().SetPath(gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n")
|
||||
|
||||
result1, err := g.View().ParseContent(content, g.Map{
|
||||
result1, err := g.View().ParseContent(context.TODO(), content, g.Map{
|
||||
"name": "john",
|
||||
"I18nLanguage": "zh-CN",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result1, expect1)
|
||||
|
||||
result2, err := g.View().ParseContent(content, g.Map{
|
||||
result2, err := g.View().ParseContent(context.TODO(), content, g.Map{
|
||||
"name": "john",
|
||||
"I18nLanguage": "ja",
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
t.Assert(result2, expect2)
|
||||
|
||||
result3, err := g.View().ParseContent(content, g.Map{
|
||||
result3, err := g.View().ParseContent(context.TODO(), content, g.Map{
|
||||
"name": "john",
|
||||
"I18nLanguage": "none",
|
||||
})
|
||||
|
||||
@ -9,6 +9,7 @@ package gvalid
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -29,7 +30,9 @@ func newError(rules []string, errors map[string]map[string]string) *Error {
|
||||
for field, m := range errors {
|
||||
for k, v := range m {
|
||||
v = strings.Replace(v, ":attribute", field, -1)
|
||||
m[k], _ = gregex.ReplaceString(`\s{2,}`, ` `, v)
|
||||
v, _ = gregex.ReplaceString(`\s{2,}`, ` `, v)
|
||||
v = gstr.Trim(v)
|
||||
m[k] = v
|
||||
}
|
||||
errors[field] = m
|
||||
}
|
||||
|
||||
@ -6,36 +6,34 @@
|
||||
|
||||
package gvalid
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
)
|
||||
|
||||
// Validator is the validation manager.
|
||||
type Validator struct {
|
||||
i18nLang string // I18n language.
|
||||
ctx context.Context // Context containing custom context variables.
|
||||
ctx context.Context // Context containing custom context variables.
|
||||
i18nManager *gi18n.Manager // I18n manager for error message translation.
|
||||
|
||||
}
|
||||
|
||||
// New creates and returns a new Validator.
|
||||
func New() *Validator {
|
||||
return &Validator{}
|
||||
return &Validator{
|
||||
ctx: context.TODO(),
|
||||
i18nManager: gi18n.Instance(),
|
||||
}
|
||||
}
|
||||
|
||||
// Clone creates and returns a new Validator which is a shallow copy of current one.
|
||||
func (v *Validator) Clone() *Validator {
|
||||
newValidator := New()
|
||||
*newValidator = *v
|
||||
return newValidator
|
||||
}
|
||||
|
||||
// I18n is a chaining operation function which sets the I18n language for next validation.
|
||||
func (v *Validator) I18n(language string) *Validator {
|
||||
newValidator := v.Clone()
|
||||
newValidator.i18nLang = language
|
||||
return newValidator
|
||||
// I18n sets the i18n manager for the validator.
|
||||
func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator {
|
||||
v.i18nManager = i18nManager
|
||||
return v
|
||||
}
|
||||
|
||||
// Ctx is a chaining operation function which sets the context for next validation.
|
||||
func (v *Validator) Ctx(ctx context.Context) *Validator {
|
||||
newValidator := v.Clone()
|
||||
newValidator.ctx = ctx
|
||||
return newValidator
|
||||
v.ctx = ctx
|
||||
return v
|
||||
}
|
||||
|
||||
@ -6,9 +6,8 @@
|
||||
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
const (
|
||||
ruleMessagePrefixForI18n = "gf.gvalid.rule."
|
||||
)
|
||||
|
||||
// defaultMessages is the default error messages.
|
||||
@ -66,15 +65,21 @@ var defaultMessages = map[string]string{
|
||||
func (v *Validator) getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) string {
|
||||
content := customMsgMap[ruleKey]
|
||||
if content != "" {
|
||||
// I18n translation.
|
||||
i18nContent := v.i18nManager.GetContent(v.ctx, content)
|
||||
if i18nContent != "" {
|
||||
return i18nContent
|
||||
}
|
||||
return content
|
||||
}
|
||||
content = gi18n.GetContent(fmt.Sprintf(`gf.gvalid.rule.%s`, ruleKey), v.i18nLang)
|
||||
// Retrieve default message according to certain rule.
|
||||
content = v.i18nManager.GetContent(v.ctx, ruleMessagePrefixForI18n+ruleKey)
|
||||
if content == "" {
|
||||
content = defaultMessages[ruleKey]
|
||||
}
|
||||
// If there's no configured rule message, it uses default one.
|
||||
if content == "" {
|
||||
content = gi18n.GetContent(`gf.gvalid.rule.__default__`, v.i18nLang)
|
||||
content = v.i18nManager.GetContent(v.ctx, `gf.gvalid.rule.__default__`)
|
||||
if content == "" {
|
||||
content = defaultMessages["__default__"]
|
||||
}
|
||||
|
||||
50
util/gvalid/gvalid_z_unit_i18n_test.go
Normal file
50
util/gvalid/gvalid_z_unit_i18n_test.go
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 gvalid_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestValidator_I18n(t *testing.T) {
|
||||
var (
|
||||
err *gvalid.Error
|
||||
i18nManager = gi18n.New(gi18n.Options{Path: gdebug.TestDataPath("i18n")})
|
||||
ctxCn = gi18n.WithLanguage(context.TODO(), "cn")
|
||||
validator = gvalid.New().I18n(i18nManager)
|
||||
)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err = validator.Check("", "required", nil)
|
||||
t.Assert(err.String(), "The field is required")
|
||||
|
||||
err = validator.Ctx(ctxCn).Check("", "required", nil)
|
||||
t.Assert(err.String(), "字段不能为空")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err = validator.Ctx(ctxCn).Check("", "required", "CustomMessage")
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Params struct {
|
||||
Page int `v:"required|min:1 # page is required"`
|
||||
Size int `v:"required|between:1,100 # size is required"`
|
||||
ProjectId int `v:"between:1,10000 # project id must between :min, :max"`
|
||||
}
|
||||
obj := &Params{
|
||||
Page: 1,
|
||||
Size: 10,
|
||||
}
|
||||
err := validator.Ctx(ctxCn).CheckStruct(obj, nil)
|
||||
t.Assert(err.String(), "项目ID必须大于等于1并且要小于等于10000")
|
||||
})
|
||||
}
|
||||
48
util/gvalid/testdata/i18n/cn/validation.toml
vendored
Normal file
48
util/gvalid/testdata/i18n/cn/validation.toml
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
"gf.gvalid.rule.required" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.required-if" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.required-unless" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.required-with" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.required-with-all" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.required-without" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.required-without-all" = ":attribute 字段不能为空"
|
||||
"gf.gvalid.rule.date" = ":attribute 日期格式不正确"
|
||||
"gf.gvalid.rule.date-format" = ":attribute 日期格式不满足:format"
|
||||
"gf.gvalid.rule.email" = ":attribute 邮箱地址格式不正确"
|
||||
"gf.gvalid.rule.phone" = ":attribute 手机号码格式不正确"
|
||||
"gf.gvalid.rule.phone-loose" = ":attribute 手机号码格式不正确"
|
||||
"gf.gvalid.rule.telephone" = ":attribute 电话号码格式不正确"
|
||||
"gf.gvalid.rule.passport" = ":attribute 账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间"
|
||||
"gf.gvalid.rule.password" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符"
|
||||
"gf.gvalid.rule.password2" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字"
|
||||
"gf.gvalid.rule.password3" = ":attribute 密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符"
|
||||
"gf.gvalid.rule.postcode" = ":attribute 邮政编码不正确"
|
||||
"gf.gvalid.rule.resident-id" = ":attribute 身份证号码格式不正确"
|
||||
"gf.gvalid.rule.bank-card" = ":attribute 银行卡号格式不正确"
|
||||
"gf.gvalid.rule.qq" = ":attribute QQ号码格式不正确"
|
||||
"gf.gvalid.rule.ip" = ":attribute IP地址格式不正确"
|
||||
"gf.gvalid.rule.ipv4" = ":attribute IPv4地址格式不正确"
|
||||
"gf.gvalid.rule.ipv6" = ":attribute IPv6地址格式不正确"
|
||||
"gf.gvalid.rule.mac" = ":attribute MAC地址格式不正确"
|
||||
"gf.gvalid.rule.url" = ":attribute URL地址格式不正确"
|
||||
"gf.gvalid.rule.domain" = ":attribute 域名格式不正确"
|
||||
"gf.gvalid.rule.length" = ":attribute 字段长度为:min到:max个字符"
|
||||
"gf.gvalid.rule.min-length" = ":attribute 字段最小长度为:min"
|
||||
"gf.gvalid.rule.max-length" = ":attribute 字段最大长度为:max"
|
||||
"gf.gvalid.rule.between" = ":attribute 字段大小为:min到:max"
|
||||
"gf.gvalid.rule.min" = ":attribute 字段最小值为:min"
|
||||
"gf.gvalid.rule.max" = ":attribute 字段最大值为:max"
|
||||
"gf.gvalid.rule.json" = ":attribute 字段应当为JSON格式"
|
||||
"gf.gvalid.rule.xml" = ":attribute 字段应当为XML格式"
|
||||
"gf.gvalid.rule.array" = ":attribute 字段应当为数组"
|
||||
"gf.gvalid.rule.integer" = ":attribute 字段应当为整数"
|
||||
"gf.gvalid.rule.float" = ":attribute 字段应当为浮点数"
|
||||
"gf.gvalid.rule.boolean" = ":attribute 字段应当为布尔值"
|
||||
"gf.gvalid.rule.same" = ":attribute 字段值必须和:field相同"
|
||||
"gf.gvalid.rule.different" = ":attribute 字段值不能与:field相同"
|
||||
"gf.gvalid.rule.in" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.not-in" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.regex" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.__default__" = ":attribute 字段值不合法"
|
||||
|
||||
"CustomMessage" = "自定义错误"
|
||||
"project id must between :min, :max" = "项目ID必须大于等于:min并且要小于等于:max"
|
||||
45
util/gvalid/testdata/i18n/en/validation.toml
vendored
Normal file
45
util/gvalid/testdata/i18n/en/validation.toml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
"gf.gvalid.rule.required" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-if" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-unless" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-with-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.required-without-all" = "The :attribute field is required"
|
||||
"gf.gvalid.rule.date" = "The :attribute value is not a valid date"
|
||||
"gf.gvalid.rule.date-format" = "The :attribute value does not match the format :format"
|
||||
"gf.gvalid.rule.email" = "The :attribute value must be a valid email address"
|
||||
"gf.gvalid.rule.phone" = "The :attribute value must be a valid phone number"
|
||||
"gf.gvalid.rule.phone-loose" = "The :attribute value must be a valid phone number"
|
||||
"gf.gvalid.rule.telephone" = "The :attribute value must be a valid telephone number"
|
||||
"gf.gvalid.rule.passport" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password2" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.password3" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.postcode" = "The :attribute value is not a valid passport format"
|
||||
"gf.gvalid.rule.resident-id" = "The :attribute value is not a valid resident id number"
|
||||
"gf.gvalid.rule.bank-card" = "The :attribute value must be a valid bank card number"
|
||||
"gf.gvalid.rule.qq" = "The :attribute value must be a valid QQ number"
|
||||
"gf.gvalid.rule.ip" = "The :attribute value must be a valid IP address"
|
||||
"gf.gvalid.rule.ipv4" = "The :attribute value must be a valid IPv4 address"
|
||||
"gf.gvalid.rule.ipv6" = "The :attribute value must be a valid IPv6 address"
|
||||
"gf.gvalid.rule.mac" = "The :attribute value must be a valid MAC address"
|
||||
"gf.gvalid.rule.url" = "The :attribute value must be a valid URL address"
|
||||
"gf.gvalid.rule.domain" = "The :attribute value must be a valid domain format"
|
||||
"gf.gvalid.rule.length" = "The :attribute value length must be between :min and :max"
|
||||
"gf.gvalid.rule.min-length" = "The :attribute value length must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max-length" = "The :attribute value length must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.between" = "The :attribute value must be between :min and :max"
|
||||
"gf.gvalid.rule.min" = "The :attribute value must be equal or greater than :min"
|
||||
"gf.gvalid.rule.max" = "The :attribute value must be equal or lesser than :max"
|
||||
"gf.gvalid.rule.json" = "The :attribute value must be a valid JSON string"
|
||||
"gf.gvalid.rule.xml" = "The :attribute value must be a valid XML string"
|
||||
"gf.gvalid.rule.array" = "The :attribute value must be an array"
|
||||
"gf.gvalid.rule.integer" = "The :attribute value must be an integer"
|
||||
"gf.gvalid.rule.float" = "The :attribute value must be a float"
|
||||
"gf.gvalid.rule.boolean" = "The :attribute value field must be true or false"
|
||||
"gf.gvalid.rule.same" = "The :attribute value must be the same as field :field"
|
||||
"gf.gvalid.rule.different" = "The :attribute value must be different from field :field"
|
||||
"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.regex" = "The :attribute value is invalid"
|
||||
"gf.gvalid.rule.__default__" = "The :attribute value is invalid"
|
||||
Reference in New Issue
Block a user