i18n: expose the LoaderConfig from the main i18n instance and add an example

Signed-off-by: Gerasimos (Makis) Maropoulos <kataras2006@hotmail.com>
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-09-10 05:17:03 +03:00
parent b77227a0f9
commit 777ef0cd3e
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
8 changed files with 134 additions and 23 deletions

View File

@ -0,0 +1,2 @@
Dog: "σκυλί"
HiDogs: Γειά {{plural (tr .locale "Dog") .count }}

View File

@ -0,0 +1,2 @@
Dog: "dog"
HiDogs: Hi {{plural (tr .locale "Dog") .count }}

View File

@ -0,0 +1,42 @@
package main
import (
"github.com/kataras/iris/v12"
// go get -u github.com/gertd/go-pluralize
"github.com/gertd/go-pluralize"
)
func main() {
app := newApp()
app.Listen(":8080")
}
func newApp() *iris.Application {
app := iris.New()
pluralize := pluralize.NewClient()
app.I18n.Loader.FuncMap = map[string]interface{}{
"plural": func(word string, count int) string {
// Your own implementation or use a 3rd-party package
// like we do here.
//
// Note that this is only for english,
// but you can accept the language code
// and use a map with dictionaries to
// pluralize words based on the given language.
return pluralize.Pluralize(word, count, true)
},
}
app.I18n.Load("./locales/*/*.yml", "en-US", "el-GR")
app.Get("/", func(ctx iris.Context) {
text := ctx.Tr("HiDogs", iris.Map{
"locale": ctx.GetLocale(),
"count": 2,
}) // prints "Hi 2 dogs".
ctx.WriteString(text)
})
return app
}

View File

@ -0,0 +1,17 @@
package main
import (
"testing"
"github.com/kataras/iris/v12/httptest"
)
func TestI18nLoaderFuncMap(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
e.GET("/").Expect().Status(httptest.StatusOK).
Body().Equal("Hi 2 dogs")
e.GET("/").WithHeader("Accept-Language", "el").Expect().Status(httptest.StatusOK).
Body().Equal("Γειά 2 σκυλί")
}

View File

@ -16,7 +16,8 @@ func main() {
ac := accesslog.File("./access.log") ac := accesslog.File("./access.log")
ac.TimeFormat = "2006-01-02 15:04:05" ac.TimeFormat = "2006-01-02 15:04:05"
ac.Async = true // Optionally run logging after response has sent:
// ac.Async = true
broker := ac.Broker() // <- IMPORTANT broker := ac.Broker() // <- IMPORTANT
app := iris.New() app := iris.New()
@ -25,7 +26,11 @@ func main() {
app.Get("/", indexHandler) app.Get("/", indexHandler)
app.Get("/profile/{username}", profileHandler) app.Get("/profile/{username}", profileHandler)
app.Post("/read_body", readBodyHandler) app.Post("/read_body", readBodyHandler)
app.Get("/logs", logsHandler(broker))
// register the /logs route,
// registers a listener and prints the incoming logs.
// Optionally, skip logging this handler.
app.Get("/logs", accesslog.SkipHandler, logsHandler(broker))
// http://localhost:8080/logs to see the logs at real-time. // http://localhost:8080/logs to see the logs at real-time.
app.Listen(":8080") app.Listen(":8080")
@ -52,8 +57,7 @@ func readBodyHandler(ctx iris.Context) {
func logsHandler(b *accesslog.Broker) iris.Handler { func logsHandler(b *accesslog.Broker) iris.Handler {
return func(ctx iris.Context) { return func(ctx iris.Context) {
accesslog.Skip(ctx) // skip logging this handler, optionally. // accesslog.Skip(ctx) // or inline skip.
logs := b.NewListener() // <- IMPORTANT logs := b.NewListener() // <- IMPORTANT
ctx.Header("Transfer-Encoding", "chunked") ctx.Header("Transfer-Encoding", "chunked")

View File

@ -49,6 +49,7 @@ type I18n struct {
localizer Localizer localizer Localizer
matcher *Matcher matcher *Matcher
Loader *LoaderConfig
loader Loader loader Loader
mu sync.Mutex mu sync.Mutex
@ -106,11 +107,18 @@ func makeTags(languages ...string) (tags []language.Tag) {
// New returns a new `I18n` instance. Use its `Load` or `LoadAssets` to load languages. // New returns a new `I18n` instance. Use its `Load` or `LoadAssets` to load languages.
func New() *I18n { func New() *I18n {
return &I18n{ i := &I18n{
Loader: &LoaderConfig{
Left: "{{",
Right: "}}",
Strict: false,
},
URLParameter: "lang", URLParameter: "lang",
Subdomain: true, Subdomain: true,
PathRedirect: true, PathRedirect: true,
} }
return i
} }
// Load is a method shortcut to load files using a filepath.Glob pattern. // Load is a method shortcut to load files using a filepath.Glob pattern.
@ -118,7 +126,7 @@ func New() *I18n {
// //
// See `New` and `Glob` package-level functions for more. // See `New` and `Glob` package-level functions for more.
func (i *I18n) Load(globPattern string, languages ...string) error { func (i *I18n) Load(globPattern string, languages ...string) error {
return i.Reset(Glob(globPattern), languages...) return i.Reset(Glob(globPattern, i.Loader), languages...)
} }
// LoadAssets is a method shortcut to load files using go-bindata. // LoadAssets is a method shortcut to load files using go-bindata.
@ -126,7 +134,7 @@ func (i *I18n) Load(globPattern string, languages ...string) error {
// //
// See `New` and `Asset` package-level functions for more. // See `New` and `Asset` package-level functions for more.
func (i *I18n) LoadAssets(assetNames func() []string, asset func(string) ([]byte, error), languages ...string) error { func (i *I18n) LoadAssets(assetNames func() []string, asset func(string) ([]byte, error), languages ...string) error {
return i.Reset(Assets(assetNames, asset), languages...) return i.Reset(Assets(assetNames, asset, i.Loader), languages...)
} }
// Reset sets the locales loader and languages. // Reset sets the locales loader and languages.

View File

@ -21,20 +21,39 @@ import (
// some options about how the template loader should act. // some options about how the template loader should act.
// //
// See `Glob` and `Assets` package-level functions. // See `Glob` and `Assets` package-level functions.
type LoaderConfig struct { type (
// Template delimeters, defaults to {{ }}. LoaderConfig struct {
Left, Right string // Template delimeters, defaults to {{ }}.
// Template functions map, defaults to nil. Left, Right string
FuncMap template.FuncMap // Template functions map, defaults to nil.
// If true then it will return error on invalid templates instead of moving them to simple string-line keys. FuncMap template.FuncMap
// Also it will report whether the registered languages matched the loaded ones. // If true then it will return error on invalid templates instead of moving them to simple string-line keys.
// Defaults to false. // Also it will report whether the registered languages matched the loaded ones.
Strict bool // Defaults to false.
} Strict bool
}
// LoaderOption is a type which accepts a pointer to `LoaderConfig`
// and can be optionally passed to the second
// variadic input argument of the `Glob` and `Assets` functions.
LoaderOption interface {
Apply(*LoaderConfig)
}
)
// LoaderOption is a type which accepts a pointer to `LoaderConfig` // Apply implements the `LoaderOption` interface.
// and can be optionally passed to the second variadic input argument of the `Glob` and `Assets` functions. func (c *LoaderConfig) Apply(cfg *LoaderConfig) {
type LoaderOption func(*LoaderConfig) for k, v := range c.FuncMap {
if cfg.FuncMap == nil {
cfg.FuncMap = make(template.FuncMap)
}
cfg.FuncMap[k] = v
}
cfg.Left = c.Left
cfg.Right = c.Right
cfg.Strict = c.Strict
}
// Glob accepts a glob pattern (see: https://golang.org/pkg/path/filepath/#Glob) // Glob accepts a glob pattern (see: https://golang.org/pkg/path/filepath/#Glob)
// and loads the locale files based on any "options". // and loads the locale files based on any "options".
@ -73,10 +92,18 @@ func load(assetNames []string, asset func(string) ([]byte, error), options ...Lo
Left: "{{", Left: "{{",
Right: "}}", Right: "}}",
Strict: false, Strict: false,
FuncMap: template.FuncMap{
// get returns the value of a translate key, can be used inside other template keys
// to translate different words based on the current locale.
"tr": func(locale context.Locale, key string, args ...interface{}) string {
return locale.GetMessage(key, args...)
},
// ^ Alternative to {{call .tr "Dog" | plural }}
},
} }
for _, opt := range options { for _, opt := range options {
opt(&c) opt.Apply(&c)
} }
return func(m *Matcher) (Localizer, error) { return func(m *Matcher) (Localizer, error) {
@ -250,9 +277,11 @@ func (l *defaultLocale) getMessage(langInput, key string, args ...interface{}) s
// search on templates. // search on templates.
if tmpl, ok := l.templateKeys[key]; ok { if tmpl, ok := l.templateKeys[key]; ok {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err := tmpl.Execute(buf, args[0]); err == nil { err := tmpl.Execute(buf, args[0])
return buf.String() if err != nil {
return err.Error()
} }
return buf.String()
} }
} }

View File

@ -43,6 +43,13 @@ func Skip(ctx iris.Context) {
ctx.Values().Set(skipLogContextKey, struct{}{}) ctx.Values().Set(skipLogContextKey, struct{}{})
} }
// SkipHandler same as `Skip` but it can be used
// as a middleware, it executes ctx.Next().
func SkipHandler(ctx iris.Context) {
Skip(ctx)
ctx.Next()
}
func shouldSkip(ctx iris.Context) bool { func shouldSkip(ctx iris.Context) bool {
return ctx.Values().Get(skipLogContextKey) != nil return ctx.Values().Get(skipLogContextKey) != nil
} }