mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
add subdomain support for i18n and a custom language Indentifier - rel to: #1369
Former-commit-id: b4d31978f6ddcdcebd18505eaa0db297db462d8e
This commit is contained in:
parent
cad90a2753
commit
2c229234f1
26
_examples/miscellaneous/i18n/hosts
Normal file
26
_examples/miscellaneous/i18n/hosts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (c) 1993-2009 Microsoft Corp.
|
||||||
|
#
|
||||||
|
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
|
||||||
|
#
|
||||||
|
# This file contains the mappings of IP addresses to host names. Each
|
||||||
|
# entry should be kept on an individual line. The IP address should
|
||||||
|
# be placed in the first column followed by the corresponding host name.
|
||||||
|
# The IP address and the host name should be separated by at least one
|
||||||
|
# space.
|
||||||
|
#
|
||||||
|
# Additionally, comments (such as these) may be inserted on individual
|
||||||
|
# lines or following the machine name denoted by a '#' symbol.
|
||||||
|
#
|
||||||
|
# For example:
|
||||||
|
#
|
||||||
|
# 102.54.94.97 rhino.acme.com # source server
|
||||||
|
# 38.25.63.10 x.acme.com # x client host
|
||||||
|
|
||||||
|
# localhost name resolution is handled within DNS itself.
|
||||||
|
# 127.0.0.1 localhost
|
||||||
|
# ::1 localhost
|
||||||
|
127.0.0.1 mydomain.com
|
||||||
|
127.0.0.1 en.mydomain.com
|
||||||
|
127.0.0.1 el.mydomain.com
|
||||||
|
127.0.0.1 el-gr.mydomain.com
|
||||||
|
127.0.0.1 zh.mydomain.com
|
|
@ -6,31 +6,34 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var i18nConfig = i18n.Config{
|
var i18nConfig = i18n.Config{
|
||||||
Default: "en-US",
|
Default: "en-US",
|
||||||
URLParameter: "lang", // optional.
|
|
||||||
PathParameter: "lang", // optional.
|
|
||||||
Languages: map[string]string{
|
Languages: map[string]string{
|
||||||
"en-US": "./locales/locale_en-US.ini", // maps to en-US, en-us and en.
|
"en-US": "./locales/locale_en-US.ini", // maps to en-US, en-us and en.
|
||||||
"el-GR": "./locales/locale_el-GR.ini", // maps to el-GR, el-gr and el.
|
"el-GR": "./locales/locale_el-GR.ini", // maps to el-GR, el-gr and el.
|
||||||
"zh-CN": "./locales/locale_zh-CN.ini", // maps to zh-CN, zh-cn and zh.
|
"zh-CN": "./locales/locale_zh-CN.ini", // maps to zh-CN, zh-cn and zh.
|
||||||
},
|
},
|
||||||
|
// Optionals.
|
||||||
Alternatives: map[string]string{ // optional.
|
Alternatives: map[string]string{ // optional.
|
||||||
"english": "en-US", // now english maps to en-US
|
"english": "en-US", // now english maps to en-US
|
||||||
"greek": "el-GR", // and greek to el-GR
|
"greek": "el-GR", // and greek to el-GR
|
||||||
"chinese": "zh-CN", // and chinese to zh-CN too.
|
"chinese": "zh-CN", // and chinese to zh-CN too.
|
||||||
},
|
},
|
||||||
|
URLParameter: "lang",
|
||||||
|
Subdomain: true,
|
||||||
|
// Cookie: "lang",
|
||||||
|
// SetCookie: false,
|
||||||
|
// Indentifier: func(ctx iris.Context) string { return "zh-CN" },
|
||||||
}
|
}
|
||||||
|
|
||||||
func newApp() *iris.Application {
|
func newApp() *iris.Application {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
|
|
||||||
|
i18nMiddleware := i18n.NewI18n(i18nConfig)
|
||||||
|
app.Use(i18nMiddleware.Handler())
|
||||||
|
|
||||||
// See https://github.com/kataras/iris/issues/1369
|
// See https://github.com/kataras/iris/issues/1369
|
||||||
// if you want to enable this (SEO) feature (OPTIONAL).
|
// if you want to enable this (SEO) feature (OPTIONAL).
|
||||||
i18nWrapper := i18n.NewWrapper(i18nConfig)
|
app.WrapRouter(i18nMiddleware.Wrapper())
|
||||||
app.WrapRouter(i18nWrapper)
|
|
||||||
|
|
||||||
i18nMiddleware := i18n.New(i18nConfig)
|
|
||||||
app.Use(i18nMiddleware)
|
|
||||||
|
|
||||||
app.Get("/", func(ctx iris.Context) {
|
app.Get("/", func(ctx iris.Context) {
|
||||||
// Ir tries to find the language by:
|
// Ir tries to find the language by:
|
||||||
|
@ -105,9 +108,10 @@ func newApp() *iris.Application {
|
||||||
func main() {
|
func main() {
|
||||||
app := newApp()
|
app := newApp()
|
||||||
|
|
||||||
// go to http://localhost:8080/el-GR/some-path
|
// go to http://localhost:8080/el-gr/some-path (by path prefix)
|
||||||
// or http://localhost:8080/zh-cn/templates
|
// or http://el.mydomain.com8080/some-path (by subdomain - test locally with the hosts file)
|
||||||
// or http://localhost:8080/some-path?lang=el-GR
|
// or http://localhost:8080/zh-CN/templates (by path prefix with uppercase)
|
||||||
|
// or http://localhost:8080/some-path?lang=el-GR (by url parameter)
|
||||||
// or http://localhost:8080 (default is en-US)
|
// or http://localhost:8080 (default is en-US)
|
||||||
// or http://localhost:8080/?lang=zh-CN
|
// or http://localhost:8080/?lang=zh-CN
|
||||||
//
|
//
|
||||||
|
|
|
@ -28,22 +28,22 @@ type Config struct {
|
||||||
//
|
//
|
||||||
// Checked: Serving state, runtime.
|
// Checked: Serving state, runtime.
|
||||||
URLParameter string
|
URLParameter string
|
||||||
// PathParameter is the name of the path parameter which the language can be indentified,
|
// Cookie is the key of the request cookie which the language can be indentified,
|
||||||
// e.g. "lang" for "{lang:string}".
|
// e.g. "lang".
|
||||||
//
|
//
|
||||||
// Checked: Serving state, runtime.
|
// Checked: Serving state, runtime.
|
||||||
//
|
Cookie string
|
||||||
// You can set custom handler to set the language too.
|
// If SetCookie is true and Cookie field is not empty
|
||||||
// Example:
|
// then it will set the cookie to the language found by y Context's Value's "lang" key or URLParameter or Cookie or Indentifier.
|
||||||
// setLangMiddleware := func(ctx iris.Context){
|
// Defaults to false.
|
||||||
// langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey()
|
SetCookie bool
|
||||||
// languageByPath := ctx.Params().Get("lang") // see {lang}
|
|
||||||
// ctx.Values().Set(langKey, languageByPath)
|
// If Subdomain is true then it will try to map a subdomain
|
||||||
// ctx.Next()
|
// with a valid language from the language list or alternatives.
|
||||||
// }
|
Subdomain bool
|
||||||
// app.Use(setLangMiddleware)
|
|
||||||
// app.Use(theI18nMiddlewareInstance)
|
// Indentifier is a function which the language can be indentified if the above URLParameter and Cookie failed to.
|
||||||
PathParameter string
|
Indentifier func(context.Context) string
|
||||||
|
|
||||||
// Languages is a map[string]string which the key is the language i81n and the value is the file location.
|
// Languages is a map[string]string which the key is the language i81n and the value is the file location.
|
||||||
//
|
//
|
||||||
|
@ -56,10 +56,6 @@ type Config struct {
|
||||||
// Languages: map[string]string{"en-US": "./locales/en-US.ini"} set
|
// 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{ "en":"en-US", "english": "en-US"}.
|
||||||
Alternatives map[string]string
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exists returns true if the language, or something similar
|
// Exists returns true if the language, or something similar
|
||||||
|
@ -139,79 +135,139 @@ func (c *Config) loadLanguages() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// test file: ../../_examples/miscellaneous/i18n/main_test.go
|
// I18n is the structure which keeps the i18n configuration and implement all Iris i18n features.
|
||||||
type i18nMiddleware struct {
|
type I18n struct {
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new i18n middleware.
|
// NewI18n returns a new i18n middleware which contains
|
||||||
func New(c Config) context.Handler {
|
// the middleware itself and a router wrapper.
|
||||||
c.loadLanguages()
|
func NewI18n(config Config) *I18n {
|
||||||
i := &i18nMiddleware{config: c}
|
config.loadLanguages()
|
||||||
return i.ServeHTTP
|
return &I18n{config}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP serves the request, the actual middleware's job is located here.
|
// Handler returns the middleware handler.
|
||||||
func (i *i18nMiddleware) ServeHTTP(ctx context.Context) {
|
func (i *I18n) Handler() context.Handler {
|
||||||
wasByCookie := false
|
return func(ctx context.Context) {
|
||||||
|
wasByCookie := false
|
||||||
|
|
||||||
langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey()
|
langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey()
|
||||||
language := ctx.Values().GetString(langKey)
|
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 == "" {
|
if language == "" {
|
||||||
// try to get by url parameter
|
if i.config.URLParameter != "" {
|
||||||
language = ctx.URLParam(i.config.URLParameter)
|
// try to get by url parameter
|
||||||
if language == "" {
|
language = ctx.URLParam(i.config.URLParameter)
|
||||||
// then try to take the lang field from the cookie
|
}
|
||||||
language = ctx.GetCookie(langKey)
|
|
||||||
|
|
||||||
if len(language) > 0 {
|
if language == "" {
|
||||||
wasByCookie = true
|
if i.config.Cookie != "" {
|
||||||
} else {
|
// then try to take the lang field from the cookie
|
||||||
|
language = ctx.GetCookie(i.config.Cookie)
|
||||||
|
wasByCookie = language != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if language == "" && i.config.Subdomain {
|
||||||
|
if subdomain := ctx.Subdomain(); subdomain != "" {
|
||||||
|
if lang, ok := i.config.Exists(subdomain); ok {
|
||||||
|
language = lang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
// try to get by the request headers.
|
// try to get by the request headers.
|
||||||
langHeader := ctx.GetHeader("Accept-Language")
|
langHeader := ctx.GetHeader("Accept-Language")
|
||||||
if len(langHeader) > 0 {
|
if len(langHeader) > 0 {
|
||||||
for _, langEntry := range strings.Split(langHeader, ",") {
|
for _, langEntry := range strings.Split(langHeader, ",") {
|
||||||
lc := strings.Split(langEntry, ";")[0]
|
lc := strings.Split(langEntry, ";")[0]
|
||||||
if lc, ok := i.config.Exists(lc); ok {
|
if lang, ok := i.config.Exists(lc); ok {
|
||||||
language = lc
|
language = lang
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if language == "" && i.config.Indentifier != nil {
|
||||||
|
language = i.config.Indentifier(ctx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if language == "" {
|
||||||
|
language = i.config.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it was not taken by the cookie, then set the cookie in order to have it.
|
||||||
|
if !wasByCookie && i.config.SetCookie && i.config.Cookie != "" {
|
||||||
|
ctx.SetCookieKV(i.config.Cookie, 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()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if language == "" {
|
// Wrapper returns a new router wrapper.
|
||||||
language = i.config.Default
|
// 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(i.Handler())`
|
||||||
|
// which registers the i18n middleware itself.
|
||||||
|
func (i *I18n) Wrapper() func(http.ResponseWriter, *http.Request, http.HandlerFunc) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request, routerHandler http.HandlerFunc) {
|
||||||
|
found := false
|
||||||
|
path := r.URL.Path[1:]
|
||||||
|
|
||||||
|
if idx := strings.IndexByte(path, '/'); idx > 0 {
|
||||||
|
path = path[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
if path != "" {
|
||||||
|
if lang, ok := i.config.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)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found && i.config.Subdomain {
|
||||||
|
host := context.GetHost(r)
|
||||||
|
if dotIdx := strings.IndexByte(host, '.'); dotIdx > 0 {
|
||||||
|
subdomain := host[0:dotIdx]
|
||||||
|
if subdomain != "" {
|
||||||
|
if lang, ok := i.config.Exists(subdomain); ok {
|
||||||
|
host = host[dotIdx+1:]
|
||||||
|
r.URL.Host = host
|
||||||
|
r.Host = host
|
||||||
|
r.Header.Set("Accept-Language", lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
routerHandler(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
func getTranslateFunction(lang string) func(string, ...interface{}) string {
|
||||||
|
@ -220,6 +276,11 @@ func getTranslateFunction(lang string) func(string, ...interface{}) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New returns a new i18n middleware.
|
||||||
|
func New(config Config) context.Handler {
|
||||||
|
return NewI18n(config).Handler()
|
||||||
|
}
|
||||||
|
|
||||||
// NewWrapper accepts a Config and returns a new router wrapper.
|
// NewWrapper accepts a Config and returns a new router wrapper.
|
||||||
// The result function can be passed on `Application.WrapRouter`.
|
// The result function can be passed on `Application.WrapRouter`.
|
||||||
// It compares the path prefix for translated language and
|
// It compares the path prefix for translated language and
|
||||||
|
@ -227,30 +288,8 @@ func getTranslateFunction(lang string) func(string, ...interface{}) string {
|
||||||
//
|
//
|
||||||
// In order this to work as expected, it should be combined with `Application.Use(New)`
|
// In order this to work as expected, it should be combined with `Application.Use(New)`
|
||||||
// which registers the i18n middleware itself.
|
// which registers the i18n middleware itself.
|
||||||
func NewWrapper(c Config) func(http.ResponseWriter, *http.Request, http.HandlerFunc) {
|
func NewWrapper(config Config) func(http.ResponseWriter, *http.Request, http.HandlerFunc) {
|
||||||
c.loadLanguages()
|
return NewI18n(config).Wrapper()
|
||||||
|
|
||||||
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 path != "" {
|
|
||||||
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.
|
// Translate returns the translated word from a context based on the current selected locale.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user