From 0844c109d9728d2858d5a5b5f575ac566d65a4fd Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 19 Nov 2019 23:36:18 +0200 Subject: [PATCH] implement path prefx for i18n middleware, as requested at: #1369 Former-commit-id: b0d6b6e7f368e710b01faad9b70dfa4cebdd8c4d --- README.md | 4 + README_ES.md | 2 +- README_FA.md | 8 +- README_GR.md | 2 + README_KO.md | 6 +- README_ZH.md | 4 +- VERSION | 2 +- _examples/miscellaneous/i18n/main.go | 40 ++- .../miscellaneous/i18n/templates/index.html | 10 +- configuration.go | 38 ++- context/configuration.go | 6 +- context/context.go | 25 +- core/router/api_builder.go | 3 + doc.go | 2 +- middleware/i18n/config.go | 18 -- middleware/i18n/i18n.go | 264 +++++++++++++----- 16 files changed, 304 insertions(+), 130 deletions(-) delete mode 100644 middleware/i18n/config.go diff --git a/README.md b/README.md index 9bb81b62..548a5d07 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,7 @@ If you discover a security vulnerability within Iris, please send an e-mail to [ The project name "Iris" was inspired by the Greek mythology. Iris Web Framework is free and open-source software licensed under the [3-Clause BSD License](LICENSE). + +## Stargazers over time + +[![Stargazers over time](https://starchart.cc/kataras/iris.svg)](https://starchart.cc/kataras/iris) diff --git a/README_ES.md b/README_ES.md index 41970b97..4ebcbb1b 100644 --- a/README_ES.md +++ b/README_ES.md @@ -8,7 +8,7 @@ Iris es un framework web rápido, simple pero con muchas funcionalidades y muy e Descubra lo que [otros dicen sobre Iris](https://iris-go.com/testimonials/) y **siga** :star: este repositorio github. -> Iris **version 12 liberado**. Lea mas [aquí](HISTORY_ES.md#sábado-26-de-octubre-2019--v1200). +[![](https://media.giphy.com/media/eGku4UbilECflFjcNj/giphy.gif)](https://iris-go.com/testimonials/) ## Aprende Iris diff --git a/README_FA.md b/README_FA.md index c6794eb8..7a9fb7dc 100644 --- a/README_FA.md +++ b/README_FA.md @@ -11,13 +11,7 @@ برای این که بدانید دیگران در مورد آیریس چه می گویند لطفا در این لینک کلیک کنید [دیگران در مورد آیریس چه می گویند](https://iris-go.com/testimonials/) لطفا این پروژه را در گیتاب **استار** کنید. - -> نسخه 11.2 **آماده شد** - -
- -[![https://www.facebook.com/iris.framework/posts/3276606095684693](https://iris-go.com/images/iris-112-released.png)](https://www.facebook.com/iris.framework/posts/3276606095684693) -
+[![](https://media.giphy.com/media/eGku4UbilECflFjcNj/giphy.gif)](https://iris-go.com/testimonials/) ## آموزش آیریس diff --git a/README_GR.md b/README_GR.md index 19f1a88b..5045e8b8 100644 --- a/README_GR.md +++ b/README_GR.md @@ -6,6 +6,8 @@ Μάθετε τι [λένε οι άλλοι για το Iris](https://iris-go.com/testimonials/) και δώστε ένα **αστεράκι** στο GitHub. +[![](https://media.giphy.com/media/eGku4UbilECflFjcNj/giphy.gif)](https://iris-go.com/testimonials/) + ## Μαθαίνοντας το Iris
diff --git a/README_KO.md b/README_KO.md index e93d2c18..5f92d772 100644 --- a/README_KO.md +++ b/README_KO.md @@ -1,5 +1,3 @@ - - # Iris [![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=for-the-badge)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=for-the-badge)](https://goreportcard.com/report/github.com/kataras/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/tree/master/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=blue&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) [![release](https://img.shields.io/badge/release%20-v11.2-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/releases) @@ -8,9 +6,7 @@ Iris는 단순하고 빠르며 좋은 성능과 모든 기능을 갖춘 Go언어 [여러 사람들의 의견](https://iris-go.com/testimonials/)을 둘러보세요. 그리고 이 github repository을 **star**하세요. -> Version 11.2가 **릴리스되었습니다!** - -[![https://www.facebook.com/iris.framework/posts/3276606095684693](https://iris-go.com/images/iris-112-released.png)](https://www.facebook.com/iris.framework/posts/3276606095684693) +[![](https://media.giphy.com/media/eGku4UbilECflFjcNj/giphy.gif)](https://iris-go.com/testimonials/) ## Iris 배우기 diff --git a/README_ZH.md b/README_ZH.md index e29d91b8..caf78eee 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -1,5 +1,3 @@ - - # Iris [![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=for-the-badge)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=for-the-badge)](https://goreportcard.com/report/github.com/kataras/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/tree/master/_examples) [![chat](https://img.shields.io/gitter/room/iris_go/community.svg?color=blue&logo=gitter&style=for-the-badge)](https://gitter.im/iris_go/community) [![release](https://img.shields.io/badge/release%20-v11.2-0077b3.svg?style=for-the-badge)](https://github.com/kataras/iris/releases) @@ -8,7 +6,7 @@ Iris 是基于 Go 编写的一个快速,简单但功能齐全且非常高效 看看 [其他人如何评价 Iris](https://iris-go.com/testimonials/),同时欢迎各位点亮 **star**。 -> 新版本 11.2 发布! [散布消息](https://www.facebook.com/iris.framework/posts/3276606095684693). +[![](https://media.giphy.com/media/eGku4UbilECflFjcNj/giphy.gif)](https://iris-go.com/testimonials/) ## 学习 Iris diff --git a/VERSION b/VERSION index ca308d35..50c9b748 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -12.0.1:https://github.com/kataras/iris/releases/tag/v12.0.1 \ No newline at end of file +12.0.2:https://github.com/kataras/iris/releases/tag/v12.0.2 \ No newline at end of file diff --git a/_examples/miscellaneous/i18n/main.go b/_examples/miscellaneous/i18n/main.go index 15fdbb5f..6afdd300 100644 --- a/_examples/miscellaneous/i18n/main.go +++ b/_examples/miscellaneous/i18n/main.go @@ -7,20 +7,29 @@ import ( func newApp() *iris.Application { app := iris.New() + app.Logger().SetLevel("debug") - globalLocale := i18n.New(i18n.Config{ - Default: "en-US", - URLParameter: "lang", + i18nConfig := i18n.Config{ + Default: "en-US", + URLParameter: "lang", + PathParameter: "lang", Languages: map[string]string{ "en-US": "./locales/locale_en-US.ini", "el-GR": "./locales/locale_el-GR.ini", "zh-CN": "./locales/locale_zh-CN.ini", }, - }) - app.Use(globalLocale) + Alternatives: map[string]string{"greek": "el-GR"}, + } + + // See https://github.com/kataras/iris/issues/1369 + // if you want to enable this (SEO) feature. + app.WrapRouter(i18n.NewWrapper(i18nConfig)) + + i18nMiddleware := i18n.New(i18nConfig) + app.Use(i18nMiddleware) app.Get("/", func(ctx iris.Context) { - // it tries to find the language by: + // Ir tries to find the language by: // ctx.Values().GetString("language") // if that was empty then // it tries to find from the URLParameter set on the configuration @@ -35,14 +44,23 @@ func newApp() *iris.Application { // or: hi := i18n.Translate(ctx, "hi", "iris") + // GetTranslateLanguageContextKey() == "language" language := ctx.Values().GetString(ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey()) // return is form of 'en-US' // The first succeed language found saved at the cookie with name ("language"), - // you can change that by changing the value of the: iris.TranslateLanguageContextKey + // you can change that by changing the value of the: iris.TranslateLanguageContextKey ctx.Writef("From the language %s translated output: %s", language, hi) }) + app.Get("/some-path", func(ctx iris.Context) { + ctx.Writef("%s", ctx.Translate("hi", "iris")) + }) + + app.Get("/sitemap.xml", func(ctx iris.Context) { + ctx.WriteString("sitemap") + }) + multiLocale := i18n.New(i18n.Config{ Default: "en-US", URLParameter: "lang", @@ -68,7 +86,8 @@ func newApp() *iris.Application { app.Get("/templates", func(ctx iris.Context) { ctx.View("index.html", iris.Map{ - "tr": ctx.Translate, + "tr": ctx.Translate, // word, arguments... + "trLang": ctx.TranslateLang, // locale, word, arguments... }) // it will return "hello, iris" // when {{call .tr "hi" "iris"}} @@ -81,7 +100,9 @@ func newApp() *iris.Application { func main() { app := newApp() - // go to http://localhost:8080/?lang=el-GR + // go to http://localhost:8080/el-GR/some-path + // or http://localhost:8080/zh-cn/templates + // or http://localhost:8080/some-path?lang=el-GR // or http://localhost:8080 (default is en-US) // or http://localhost:8080/?lang=zh-CN // @@ -90,5 +111,6 @@ func main() { // or http://localhost:8080/multi?lang=en-US // // or use cookies to set the language. + // app.Run(iris.Addr(":8080")) } diff --git a/_examples/miscellaneous/i18n/templates/index.html b/_examples/miscellaneous/i18n/templates/index.html index 7fc7cae8..f0dc0cad 100644 --- a/_examples/miscellaneous/i18n/templates/index.html +++ b/_examples/miscellaneous/i18n/templates/index.html @@ -1 +1,9 @@ -{{call .tr "hi" "iris"}} \ No newline at end of file +

Test translate current locale template function [dynamic] ("word", arguments...)
call .tr "hi" "iris"

+ +{{call .tr "hi" "iris"}} + +
+ +

Test translate of any language template function [static] ("language", "word", arguments...)
call .trLang "zh-CN" "hi" "iris"

+ +{{call .trLang "zh-CN" "hi" "iris"}} diff --git a/configuration.go b/configuration.go index 401bf0d5..f9287859 100644 --- a/configuration.go +++ b/configuration.go @@ -705,14 +705,17 @@ type Configuration struct { // Context values' keys for various features. // - // TranslateLanguageContextKey & TranslateFunctionContextKey are used by i18n handlers/middleware - // currently we have only one: https://github.com/kataras/iris/tree/master/middleware/i18n. + // TranslateLanguageContextKey & TranslateLangFunctionContextKey & TranslateFunctionContextKey are used by i18n handlers/middleware to set the selected locale's translate function. // - // Defaults to "iris.translate" and "iris.language" + // Defaults to "iris.translate". TranslateFunctionContextKey string `json:"translateFunctionContextKey,omitempty" yaml:"TranslateFunctionContextKey" toml:"TranslateFunctionContextKey"` - // TranslateLanguageContextKey used for i18n. + // TranslateLangFunctionContextKey & TranslateFunctionContextKey & TranslateLanguageContextKey are used by i18n handlers/middleware to set the global translate function. // - // Defaults to "iris.language" + // Defaults to "iris.languageGlobal". + TranslateLangFunctionContextKey string `json:"translateLangFunctionContextKey,omitempty" yaml:"TranslateLangFunctionContextKey" toml:"TranslateLangFunctionContextKey"` + // TranslateLanguageContextKey used to report the i18n selected locale. + // + // Defaults to "iris.language". TranslateLanguageContextKey string `json:"translateLanguageContextKey,omitempty" yaml:"TranslateLanguageContextKey" toml:"TranslateLanguageContextKey"` // GetViewLayoutContextKey is the key of the context's user values' key @@ -842,11 +845,17 @@ func (c Configuration) GetPostMaxMemory() int64 { } // GetTranslateFunctionContextKey returns the configuration's TranslateFunctionContextKey value, -// used for i18n. +// used for i18n inside templates. func (c Configuration) GetTranslateFunctionContextKey() string { return c.TranslateFunctionContextKey } +// GetTranslateLangFunctionContextKey returns the configuration's TranslateLangFunctionContextKey value, +// used for i18n inside templates. +func (c Configuration) GetTranslateLangFunctionContextKey() string { + return c.TranslateLangFunctionContextKey +} + // GetTranslateLanguageContextKey returns the configuration's TranslateLanguageContextKey value, // used for i18n. func (c Configuration) GetTranslateLanguageContextKey() string { @@ -1017,13 +1026,14 @@ func DefaultConfiguration() Configuration { // The request body the size limit // can be set by the middleware `LimitRequestBodySize` // or `context#SetMaxRequestBodySize`. - PostMaxMemory: 32 << 20, // 32MB - TranslateFunctionContextKey: "iris.translate", - TranslateLanguageContextKey: "iris.language", - ViewLayoutContextKey: "iris.viewLayout", - ViewDataContextKey: "iris.viewData", - RemoteAddrHeaders: make(map[string]bool), - EnableOptimizations: false, - Other: make(map[string]interface{}), + PostMaxMemory: 32 << 20, // 32MB + TranslateFunctionContextKey: "iris.translate", + TranslateLangFunctionContextKey: "iris.translateLang", + TranslateLanguageContextKey: "iris.language", + ViewLayoutContextKey: "iris.viewLayout", + ViewDataContextKey: "iris.viewData", + RemoteAddrHeaders: make(map[string]bool), + EnableOptimizations: false, + Other: make(map[string]interface{}), } } diff --git a/context/configuration.go b/context/configuration.go index 9eaee9f2..516de450 100644 --- a/context/configuration.go +++ b/context/configuration.go @@ -68,9 +68,11 @@ type ConfigurationReadOnly interface { GetPostMaxMemory() int64 // GetTranslateLanguageContextKey returns the configuration's TranslateFunctionContextKey value, - // used for i18n. + // used for i18n inside templates. GetTranslateFunctionContextKey() string - + // GetTranslateLangFunctionContextKey returns the configuration's TranslateLangFunctionContextKey value, + // used for i18n inside templates. + GetTranslateLangFunctionContextKey() string // GetTranslateLanguageContextKey returns the configuration's TranslateLanguageContextKey value, // used for i18n. GetTranslateLanguageContextKey() string diff --git a/context/context.go b/context/context.go index 16a490de..e6d6a6c1 100644 --- a/context/context.go +++ b/context/context.go @@ -294,10 +294,16 @@ type Context interface { Values() *memstore.Store // Translate is the i18n (localization) middleware's function, // it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey()) - // to execute the translate function and return the localized text value. + // to execute the translate function and returns the current localized text value. // // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n Translate(format string, args ...interface{}) string + // TranslateLang is the i18n (localization) middleware's function, + // it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateLangFunctionContextKey()) + // to execute the translate function and returns the localized text value based on the "lang". + // + // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n + TranslateLang(lang, format string, args ...interface{}) string // +------------------------------------------------------------+ // | Path, Host, Subdomain, IP, Headers etc... | @@ -1499,17 +1505,30 @@ func (ctx *context) Values() *memstore.Store { // Translate is the i18n (localization) middleware's function, // it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey()) -// to execute the translate function and return the localized text value. +// to execute the translate function and return the current localized text value. // // Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n func (ctx *context) Translate(format string, args ...interface{}) string { - if cb, ok := ctx.values.Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey()).(func(format string, args ...interface{}) string); ok { + if cb, ok := ctx.values.Get(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey()).(func(string, ...interface{}) string); ok { return cb(format, args...) } return "" } +// TranslateLang is the i18n (localization) middleware's function, +// it calls the Values().Get(ctx.Application().ConfigurationReadOnly().GetTranslateLangFunctionContextKey()) +// to execute the translate function and returns the localized text value based on the "lang". +// +// Example: https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n +func (ctx *context) TranslateLang(lang, format string, args ...interface{}) string { + if cb, ok := ctx.values.Get(ctx.Application().ConfigurationReadOnly().GetTranslateLangFunctionContextKey()).(func(string, string, ...interface{}) string); ok { + return cb(lang, format, args...) + } + + return "" +} + // +------------------------------------------------------------+ // | Path, Host, Subdomain, IP, Headers etc... | // +------------------------------------------------------------+ diff --git a/core/router/api_builder.go b/core/router/api_builder.go index c90b36d1..b789bb3b 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -397,6 +397,9 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co var route *Route // the last one is returned. for _, route = range routes { + if route == nil { + break + } // global route.topLink = api.routes.getRelative(route) diff --git a/doc.go b/doc.go index 397e29a1..29b99189 100644 --- a/doc.go +++ b/doc.go @@ -38,7 +38,7 @@ Source code and other details for the project are available at GitHub: Current Version -12.0.1 +12.0.2 Installation diff --git a/middleware/i18n/config.go b/middleware/i18n/config.go deleted file mode 100644 index becc89fc..00000000 --- a/middleware/i18n/config.go +++ /dev/null @@ -1,18 +0,0 @@ -package i18n - -// Config the i18n options -type Config struct { - // Default set it if you want a default language - // - // Checked: Configuration state, not at runtime - Default string - // URLParameter is the name of the url parameter which the language can be indentified - // - // Checked: Serving state, runtime - URLParameter string - // Languages is a map[string]string which the key is the language i81n and the value is the file location - // - // Example of key is: 'en-US' - // Example of value is: './locales/en-US.ini' - Languages map[string]string -} diff --git a/middleware/i18n/i18n.go b/middleware/i18n/i18n.go index 5a504997..13f4bc97 100644 --- a/middleware/i18n/i18n.go +++ b/middleware/i18n/i18n.go @@ -3,6 +3,8 @@ package i18n import ( + "fmt" + "net/http" "reflect" "strings" @@ -10,80 +12,84 @@ import ( "github.com/kataras/iris/v12/context" ) -// test file: ../../_examples/miscellaneous/i18n/main_test.go -type i18nMiddleware struct { - config Config +// Config the i18n options. +type Config struct { + // Default set it if you want a default language. + // + // Checked: Configuration state, not at runtime. + Default string + // URLParameter is the name of the url parameter which the language can be indentified, + // e.g. "lang" for ?lang=. + // + // Checked: Serving state, runtime. + URLParameter string + // PathParameter is the name of the path parameter which the language can be indentified, + // e.g. "lang" for "{lang:string}". + // + // Checked: Serving state, runtime. + // + // You can set custom handler to set the language too. + // Example: + // setLangMiddleware := func(ctx iris.Context){ + // langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey() + // languageByPath := ctx.Params().Get("lang") // see {lang} + // ctx.Values().Set(langKey, languageByPath) + // ctx.Next() + // } + // app.Use(setLangMiddleware) + // app.Use(theI18nMiddlewareInstance) + PathParameter string + + // Languages is a map[string]string which the key is the language i81n and the value is the file location. + // + // Example of key is: 'en-US'. + // Example of value is: './locales/en-US.ini'. + Languages map[string]string + // Alternatives is a language map which if it's filled, + // it tries to associate its keys with a value of "Languages" field when a possible value of "Language" was not present. + // Example of + // Languages: map[string]string{"en-US": "./locales/en-US.ini"} set + // Alternatives: map[string]string{ "en":"en-US", "english": "en-US"}. + Alternatives map[string]string + + // If SetCookie is true then it will set the cookie to the language found by URLParameter, PathParameter or by Context's Value's "lang" key. + // Defaults to false. + SetCookie bool } -// ServeHTTP serves the request, the actual middleware's job is here -func (i *i18nMiddleware) ServeHTTP(ctx context.Context) { - wasByCookie := false - - langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey() - language := ctx.Values().GetString(langKey) - if language == "" { - // try to get by url parameter - language = ctx.URLParam(i.config.URLParameter) - if language == "" { - // then try to take the lang field from the cookie - language = ctx.GetCookie(langKey) - - if len(language) > 0 { - wasByCookie = true - } else { - // try to get by the request headers. - langHeader := ctx.GetHeader("Accept-Language") - if len(langHeader) > 0 { - for _, langEntry := range strings.Split(langHeader, ",") { - lc := strings.Split(langEntry, ";")[0] - if lc, ok := i18n.IsExistSimilar(lc); ok { - language = lc - break - } - } - } - } +// Exists returns true if the language, or something similar +// exists (e.g. en-US maps to en or Alternatives[key] == lang). +// it returns the found name and whether it was able to match something. +func (c *Config) Exists(lang string) (string, bool) { + for k, v := range c.Alternatives { + if k == lang { + lang = v + break } - // if it was not taken by the cookie, then set the cookie in order to have it - if !wasByCookie { - ctx.SetCookieKV(langKey, language) - } - - if language == "" { - language = i.config.Default - } - - ctx.Values().Set(langKey, language) - } - locale := i18n.Locale{Lang: language} - - // if unexpected language given, the middleware will transtlate to the default language, the language key should be - // also this language instead of the user-given - if indexLang := locale.Index(); indexLang == -1 { - locale.Lang = i.config.Default } - translateFuncKey := ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey() - ctx.Values().Set(translateFuncKey, locale.Tr) - ctx.Next() + return i18n.IsExistSimilar(lang) } -// Translate returns the translated word from a context -// the second parameter is the key of the world or line inside the .ini file -// the third parameter is the '%s' of the world or line inside the .ini file -func Translate(ctx context.Context, format string, args ...interface{}) string { - return ctx.Translate(format, args...) -} - -// New returns a new i18n middleware -func New(c Config) context.Handler { +func (c *Config) loadLanguages() { if len(c.Languages) == 0 { - panic("You cannot use this middleware without set the Languages option, please try again and read the _example.") + panic("field Languages is empty") } - i := &i18nMiddleware{config: c} + + for k, v := range c.Alternatives { + if _, ok := c.Languages[v]; !ok { + panic(fmt.Sprintf("language alternative '%s' does not map to a valid language '%s'", k, v)) + } + } + firstlanguage := "" // load the files for k, langFileOrFiles := range c.Languages { + if i18n.IsExist(k) { + // if it is already stored through middleware (`New`) then skip it. + continue + } + // remove all spaces. langFileOrFiles = strings.Replace(langFileOrFiles, " ", "", -1) // note: if only one, then the first element is the "v". @@ -93,24 +99,152 @@ func New(c Config) context.Handler { if !strings.HasSuffix(v, ".ini") { v += ".ini" } + err := i18n.SetMessage(k, v) if err != nil && err != i18n.ErrLangAlreadyExist { - panic("Failed to set locale file'" + k + "' Error:" + err.Error()) + panic(fmt.Sprintf("Failed to set locale file' %s' with error: %v", k, err)) } if firstlanguage == "" { firstlanguage = k } } } - // if not default language set then set to the first of the i.config.Languages + // if not default language set then set to the first of the "Languages". if c.Default == "" { c.Default = firstlanguage } - i18n.SetDefaultLang(i.config.Default) + i18n.SetDefaultLang(c.Default) +} + +// test file: ../../_examples/miscellaneous/i18n/main_test.go +type i18nMiddleware struct { + config Config +} + +// New returns a new i18n middleware. +func New(c Config) context.Handler { + c.loadLanguages() + i := &i18nMiddleware{config: c} return i.ServeHTTP } +// ServeHTTP serves the request, the actual middleware's job is located here. +func (i *i18nMiddleware) ServeHTTP(ctx context.Context) { + wasByCookie := false + + langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey() + language := ctx.Values().GetString(langKey) + if language == "" { + // try to get by path parameter + if i.config.PathParameter != "" { + language = ctx.Params().Get(i.config.PathParameter) + } + + if language == "" { + // try to get by url parameter + language = ctx.URLParam(i.config.URLParameter) + + if language == "" { + // then try to take the lang field from the cookie + language = ctx.GetCookie(langKey) + + if len(language) > 0 { + wasByCookie = true + } else { + // try to get by the request headers. + langHeader := ctx.GetHeader("Accept-Language") + if len(langHeader) > 0 { + for _, langEntry := range strings.Split(langHeader, ",") { + lc := strings.Split(langEntry, ";")[0] + if lc, ok := i.config.Exists(lc); ok { + language = lc + break + } + } + } + } + } + } + } + + // returns the original key of the language and true + // when the language, or something similar exists (e.g. en-US maps to en). + if lc, ok := i.config.Exists(language); ok { + language = lc + } else { + // if unexpected language given, the middleware will translate to the default language, + // the language key should be also this language instead of the user-given. + language = i.config.Default + } + + // if it was not taken by the cookie, then set the cookie in order to have it. + if !wasByCookie && i.config.SetCookie { + ctx.SetCookieKV(langKey, language) + } + + ctx.Values().Set(langKey, language) + + // Set iris.translate and iris.translateLang functions (they can be passed to templates as they are later on). + ctx.Values().Set(ctx.Application().ConfigurationReadOnly().GetTranslateFunctionContextKey(), getTranslateFunction(language)) + // Note: translate (global) language function input argument should match exactly, case-sensitive and "Alternatives" field is not part of the fetch progress. + ctx.Values().Set(ctx.Application().ConfigurationReadOnly().GetTranslateLangFunctionContextKey(), i18n.Tr) + + ctx.Next() +} + +func getTranslateFunction(lang string) func(string, ...interface{}) string { + return func(format string, args ...interface{}) string { + return i18n.Tr(lang, format, args...) + } +} + +// NewWrapper accepts a Config and returns a new router wrapper. +// The result function can be passed on `Application.WrapRouter`. +// It compares the path prefix for translated language and +// local redirects the requested path with the selected (from the path) language to the router. +// +// In order this to work as expected, it should be combined with `Application.Use(New)` +// which registers the i18n middleware itself. +func NewWrapper(c Config) func(http.ResponseWriter, *http.Request, http.HandlerFunc) { + c.loadLanguages() + + return func(w http.ResponseWriter, r *http.Request, routerHandler http.HandlerFunc) { + path := r.URL.Path[1:] + + if idx := strings.IndexRune(path, '/'); idx > 0 { + path = path[:idx] + } + + if lang, ok := c.Exists(path); ok { + path = r.URL.Path[len(path)+1:] + if path == "" { + path = "/" + } + r.RequestURI = path + r.URL.Path = path + r.Header.Set("Accept-Language", lang) + } + + routerHandler(w, r) + } +} + +// Translate returns the translated word from a context based on the current selected locale. +// The second parameter is the key of the world or line inside the .ini file and +// the third parameter is the '%s' of the world or line inside the .ini file +func Translate(ctx context.Context, format string, args ...interface{}) string { + return ctx.Translate(format, args...) +} + +// TranslateLang returns the translated word from a context based on the given "lang". +// The second parameter is the language key which the word "format" is translated to and +// the third parameter is the key of the world or line inside the .ini file and +// the forth parameter is the '%s' of the world or line inside the .ini file +func TranslateLang(ctx context.Context, lang, format string, args ...interface{}) string { + return ctx.TranslateLang(lang, format, args...) +} + // TranslatedMap returns translated map[string]interface{} from i18n structure. func TranslatedMap(ctx context.Context, sourceInterface interface{}) map[string]interface{} { iType := reflect.TypeOf(sourceInterface).Elem()