diff --git a/i18n/gi18n/gi18n.go b/i18n/gi18n/gi18n.go index b0a85400b..bb2cc07e4 100644 --- a/i18n/gi18n/gi18n.go +++ b/i18n/gi18n/gi18n.go @@ -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 with configured language // and given . -func TranslateFormat(format string, values ...interface{}) string { - return Instance().TranslateFormat(format, values...) -} - -// TranslateFormatLang translates, formats and returns the with configured language -// and given . The parameter specifies custom translation language ignoring -// configured language. If 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 with configured language and returns the translated content. // The parameter 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) } diff --git a/i18n/gi18n/gi18n_ctx.go b/i18n/gi18n/gi18n_ctx.go new file mode 100644 index 000000000..4a9bc06ce --- /dev/null +++ b/i18n/gi18n/gi18n_ctx.go @@ -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 "" +} diff --git a/i18n/gi18n/gi18n_instance.go b/i18n/gi18n/gi18n_instance.go index d9abdbde2..43af97cc1 100644 --- a/i18n/gi18n/gi18n_instance.go +++ b/i18n/gi18n/gi18n_instance.go @@ -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" ) diff --git a/i18n/gi18n/gi18n_manager.go b/i18n/gi18n/gi18n_manager.go index 6b737df94..9dc6a8639 100644 --- a/i18n/gi18n/gi18n_manager.go +++ b/i18n/gi18n/gi18n_manager.go @@ -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 with configured language // and given . -func (m *Manager) TranslateFormat(format string, values ...interface{}) string { - return fmt.Sprintf(m.Translate(format), values...) -} - -// TranslateFormatLang translates, formats and returns the with configured language -// and given . The parameter specifies custom translation language ignoring -// configured language. If 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 with configured language. // The parameter 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] diff --git a/i18n/gi18n/gi18n_unit_test.go b/i18n/gi18n/gi18n_unit_test.go index b0012c1c7..0579bce5c 100644 --- a/i18n/gi18n/gi18n_unit_test.go +++ b/i18n/gi18n/gi18n_unit_test.go @@ -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}"), "你好世界") }) } diff --git a/net/ghttp/ghttp_response_view.go b/net/ghttp/ghttp_response_view.go index e3361197c..194d04c0d 100644 --- a/net/ghttp/ghttp_response_view.go +++ b/net/ghttp/ghttp_response_view.go @@ -59,18 +59,18 @@ func (r *Response) WriteTplContent(content string, params ...gview.Params) error // ParseTpl parses given template file with given template variables // 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 with given template parameters // 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 and returns the new template variables. diff --git a/net/ghttp/ghttp_server_admin.go b/net/ghttp/ghttp_server_admin.go index ee47fc2b5..dc4b107ea 100644 --- a/net/ghttp/ghttp_server_admin.go +++ b/net/ghttp/ghttp_server_admin.go @@ -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(), ` GoFrame Web Server Admin diff --git a/net/ghttp/ghttp_server_pprof.go b/net/ghttp/ghttp_server_pprof.go index dc63a6a74..cef4f2d35 100644 --- a/net/ghttp/ghttp_server_pprof.go +++ b/net/ghttp/ghttp_server_pprof.go @@ -62,7 +62,7 @@ func (p *utilPProf) Index(r *Request) { "profiles": profiles, } if len(action) == 0 { - buffer, _ := gview.ParseContent(` + buffer, _ := gview.ParseContent(r.Context(), ` GoFrame PProf diff --git a/os/gview/gview.go b/os/gview/gview.go index 54813b9ab..dc0013add 100644 --- a/os/gview/gview.go +++ b/os/gview/gview.go @@ -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. diff --git a/os/gview/gview_buildin.go b/os/gview/gview_buildin.go index 064903897..c838750ee 100644 --- a/os/gview/gview_buildin.go +++ b/os/gview/gview_buildin.go @@ -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()) } diff --git a/os/gview/gview_i18n.go b/os/gview/gview_i18n.go index df8ca6584..554f2f567 100644 --- a/os/gview/gview_i18n.go +++ b/os/gview/gview_i18n.go @@ -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 + } + } +} diff --git a/os/gview/gview_parse.go b/os/gview/gview_parse.go index e02d9dd75..f69d7e08b 100644 --- a/os/gview/gview_parse.go +++ b/os/gview/gview_parse.go @@ -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 with given template variables // 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 ""? result = gstr.Replace(buffer.String(), "", "") - 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 with template variables // 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 ""? result := gstr.Replace(buffer.String(), "", "") - result = view.i18nTranslate(result, variables) + result = view.i18nTranslate(ctx, result, variables) return result, nil } diff --git a/os/gview/gview_unit_basic_test.go b/os/gview/gview_unit_basic_test.go index 53caf1b8c..4cad9247c 100644 --- a/os/gview/gview_unit_basic_test.go +++ b/os/gview/gview_unit_basic_test.go @@ -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 = `{{"
测试
"|text}}` - result, err = gview.ParseContent(str, nil) + result, err = gview.ParseContent(context.TODO(), str, nil) t.Assert(err != nil, false) t.Assert(result, `测试`) str = `{{"
测试
"|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 = `{{"
测试
"|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, `
测试
`) 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, `热爱GF热爱生活`) 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
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, `

HEADER

hello gf

@@ -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 := "
" - 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 := "
" - 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 := "
" - 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"}`) }) diff --git a/os/gview/gview_unit_config_test.go b/os/gview/gview_unit_config_test.go index 314b4be48..899245157 100644 --- a/os/gview/gview_unit_config_test.go +++ b/os/gview/gview_unit_config_test.go @@ -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") }) diff --git a/os/gview/gview_unit_encode_test.go b/os/gview/gview_unit_encode_test.go index 1aad3616d..892be705b 100644 --- a/os/gview/gview_unit_encode_test.go +++ b/os/gview/gview_unit_encode_test.go @@ -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": "my title", }) 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": "my title", }) t.Assert(err, nil) diff --git a/os/gview/gview_unit_i18n_test.go b/os/gview/gview_unit_i18n_test.go index b04ba2bbd..23550e66c 100644 --- a/os/gview/gview_unit_i18n_test.go +++ b/os/gview/gview_unit_i18n_test.go @@ -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", }) diff --git a/util/gvalid/gvalid_error.go b/util/gvalid/gvalid_error.go index 49ebeb510..7a2a30141 100644 --- a/util/gvalid/gvalid_error.go +++ b/util/gvalid/gvalid_error.go @@ -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 } diff --git a/util/gvalid/gvalid_validator.go b/util/gvalid/gvalid_validator.go index 5709092f1..5f516a2c6 100644 --- a/util/gvalid/gvalid_validator.go +++ b/util/gvalid/gvalid_validator.go @@ -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 } diff --git a/util/gvalid/gvalid_validator_message.go b/util/gvalid/gvalid_validator_message.go index 56bf17251..e1737dce9 100644 --- a/util/gvalid/gvalid_validator_message.go +++ b/util/gvalid/gvalid_validator_message.go @@ -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__"] } diff --git a/util/gvalid/gvalid_z_unit_i18n_test.go b/util/gvalid/gvalid_z_unit_i18n_test.go new file mode 100644 index 000000000..bb27a3f4e --- /dev/null +++ b/util/gvalid/gvalid_z_unit_i18n_test.go @@ -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") + }) +} diff --git a/util/gvalid/testdata/i18n/cn/validation.toml b/util/gvalid/testdata/i18n/cn/validation.toml new file mode 100644 index 000000000..0cc409eb5 --- /dev/null +++ b/util/gvalid/testdata/i18n/cn/validation.toml @@ -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" \ No newline at end of file diff --git a/util/gvalid/testdata/i18n/en/validation.toml b/util/gvalid/testdata/i18n/en/validation.toml new file mode 100644 index 000000000..7dc56cab0 --- /dev/null +++ b/util/gvalid/testdata/i18n/en/validation.toml @@ -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" \ No newline at end of file