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:
John Guo
2021-05-13 00:16:45 +08:00
parent d21b9d58e1
commit a326f4a989
22 changed files with 369 additions and 196 deletions

View File

@ -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
View 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 ""
}

View File

@ -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"
)

View File

@ -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]

View File

@ -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}"), "你好世界")
})
}

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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())
}

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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, `&lt;div&gt;测试&lt;/div&gt;`)
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, `&lt;div&gt;测试&lt;/div&gt;`)
str = `{{"&lt;div&gt;测试&lt;/div&gt;"|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"}`)
})

View File

@ -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")
})

View File

@ -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)

View File

@ -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",
})

View File

@ -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
}

View File

@ -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
}

View File

@ -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__"]
}

View 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")
})
}

View 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"

View 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"