diff --git a/_examples/i18n/i18n-template/locales/el-GR/welcome.yml b/_examples/i18n/i18n-template/locales/el-GR/welcome.yml new file mode 100644 index 00000000..fc0e563d --- /dev/null +++ b/_examples/i18n/i18n-template/locales/el-GR/welcome.yml @@ -0,0 +1,2 @@ +Dog: "σκυλί" +HiDogs: Γειά {{plural (tr .locale "Dog") .count }} \ No newline at end of file diff --git a/_examples/i18n/i18n-template/locales/en-US/welcome.yml b/_examples/i18n/i18n-template/locales/en-US/welcome.yml new file mode 100644 index 00000000..4f118ced --- /dev/null +++ b/_examples/i18n/i18n-template/locales/en-US/welcome.yml @@ -0,0 +1,2 @@ +Dog: "dog" +HiDogs: Hi {{plural (tr .locale "Dog") .count }} \ No newline at end of file diff --git a/_examples/i18n/i18n-template/main.go b/_examples/i18n/i18n-template/main.go new file mode 100644 index 00000000..be0f8eb2 --- /dev/null +++ b/_examples/i18n/i18n-template/main.go @@ -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 +} diff --git a/_examples/i18n/i18n-template/main_test.go b/_examples/i18n/i18n-template/main_test.go new file mode 100644 index 00000000..2eca2fcd --- /dev/null +++ b/_examples/i18n/i18n-template/main_test.go @@ -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 σκυλί") +} diff --git a/_examples/logging/request-logger/accesslog-broker/main.go b/_examples/logging/request-logger/accesslog-broker/main.go index 8cfe40c2..a2f0b267 100644 --- a/_examples/logging/request-logger/accesslog-broker/main.go +++ b/_examples/logging/request-logger/accesslog-broker/main.go @@ -16,7 +16,8 @@ func main() { ac := accesslog.File("./access.log") 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 app := iris.New() @@ -25,7 +26,11 @@ func main() { app.Get("/", indexHandler) app.Get("/profile/{username}", profileHandler) 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. app.Listen(":8080") @@ -52,8 +57,7 @@ func readBodyHandler(ctx iris.Context) { func logsHandler(b *accesslog.Broker) iris.Handler { return func(ctx iris.Context) { - accesslog.Skip(ctx) // skip logging this handler, optionally. - + // accesslog.Skip(ctx) // or inline skip. logs := b.NewListener() // <- IMPORTANT ctx.Header("Transfer-Encoding", "chunked") diff --git a/i18n/i18n.go b/i18n/i18n.go index 5b38ad17..41fa45c9 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -49,6 +49,7 @@ type I18n struct { localizer Localizer matcher *Matcher + Loader *LoaderConfig loader Loader 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. func New() *I18n { - return &I18n{ + i := &I18n{ + Loader: &LoaderConfig{ + Left: "{{", + Right: "}}", + Strict: false, + }, URLParameter: "lang", Subdomain: true, PathRedirect: true, } + + return i } // 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. 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. @@ -126,7 +134,7 @@ func (i *I18n) Load(globPattern string, languages ...string) error { // // See `New` and `Asset` package-level functions for more. 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. diff --git a/i18n/loader.go b/i18n/loader.go index 4c3de0a9..35fee46f 100644 --- a/i18n/loader.go +++ b/i18n/loader.go @@ -21,20 +21,39 @@ import ( // some options about how the template loader should act. // // See `Glob` and `Assets` package-level functions. -type LoaderConfig struct { - // Template delimeters, defaults to {{ }}. - Left, Right string - // Template functions map, defaults to nil. - FuncMap template.FuncMap - // If true then it will return error on invalid templates instead of moving them to simple string-line keys. - // Also it will report whether the registered languages matched the loaded ones. - // Defaults to false. - Strict bool -} +type ( + LoaderConfig struct { + // Template delimeters, defaults to {{ }}. + Left, Right string + // Template functions map, defaults to nil. + FuncMap template.FuncMap + // If true then it will return error on invalid templates instead of moving them to simple string-line keys. + // Also it will report whether the registered languages matched the loaded ones. + // 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` -// and can be optionally passed to the second variadic input argument of the `Glob` and `Assets` functions. -type LoaderOption func(*LoaderConfig) +// Apply implements the `LoaderOption` interface. +func (c *LoaderConfig) Apply(cfg *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) // 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: "{{", Right: "}}", 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 { - opt(&c) + opt.Apply(&c) } return func(m *Matcher) (Localizer, error) { @@ -250,9 +277,11 @@ func (l *defaultLocale) getMessage(langInput, key string, args ...interface{}) s // search on templates. if tmpl, ok := l.templateKeys[key]; ok { buf := new(bytes.Buffer) - if err := tmpl.Execute(buf, args[0]); err == nil { - return buf.String() + err := tmpl.Execute(buf, args[0]) + if err != nil { + return err.Error() } + return buf.String() } } diff --git a/middleware/accesslog/accesslog.go b/middleware/accesslog/accesslog.go index a8d58563..47a90370 100644 --- a/middleware/accesslog/accesslog.go +++ b/middleware/accesslog/accesslog.go @@ -43,6 +43,13 @@ func Skip(ctx iris.Context) { 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 { return ctx.Values().Get(skipLogContextKey) != nil }