mirror of
https://github.com/kataras/iris.git
synced 2025-12-27 14:57:05 +00:00
fixes, i18n, sitemap generator and new examples
Former-commit-id: 54801dc705ee0fa66232f65063f8a68c9cc31921
This commit is contained in:
@@ -4,7 +4,6 @@ Builtin Handlers
|
||||
| Middleware | Example |
|
||||
| -----------|-------------|
|
||||
| [basic authentication](basicauth) | [iris/_examples/authentication/basicauth](https://github.com/kataras/iris/tree/master/_examples/authentication/basicauth) |
|
||||
| [localization and internationalization](i18n) | [iris/_examples/miscellaneous/i81n](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n) |
|
||||
| [request logger](logger) | [iris/_examples/http_request/request-logger](https://github.com/kataras/iris/tree/master/_examples/http_request/request-logger) |
|
||||
| [HTTP method override](methodoverride) | [iris/middleware/methodoverride/methodoverride_test.go](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go) |
|
||||
| [profiling (pprof)](pprof) | [iris/_examples/miscellaneous/pprof](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/pprof) |
|
||||
|
||||
@@ -1,442 +0,0 @@
|
||||
// Package i18n provides internalization and localization via middleware.
|
||||
// See _examples/miscellaneous/i18n
|
||||
package i18n
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// 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
|
||||
// Cookie is the key of the request cookie which the language can be indentified,
|
||||
// e.g. "lang".
|
||||
//
|
||||
// Checked: Serving state, runtime.
|
||||
Cookie string
|
||||
// If SetCookie is true and Cookie field is not empty
|
||||
// then it will set the cookie to the language found by Context's Value's "lang" key or URLParameter or Cookie or Indentifier.
|
||||
// Defaults to false.
|
||||
SetCookie bool
|
||||
|
||||
// If Subdomain is true then it will try to map a subdomain
|
||||
// with a valid language from the language list or a valid map to a language.
|
||||
Subdomain bool
|
||||
|
||||
// Indentifier is a function which the language can be indentified if the above URLParameter and Cookie failed to.
|
||||
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.
|
||||
//
|
||||
// Example of key is: 'en-US'.
|
||||
// Example of value is: './locales/en-US.ini'.
|
||||
Languages map[string]string
|
||||
// LanguagesMap is a language map which if it's filled,
|
||||
// it tries to associate an incoming possible language code to a key of "Languages" field
|
||||
// when the value of "Language" was not present as it is at serve-time.
|
||||
//
|
||||
// Defaults to a non-nil LanguagesMap which accepts all lowercase and [en] as [en-US] and e.t.c.
|
||||
LanguagesMap LanguagesMap
|
||||
}
|
||||
|
||||
// LanguagesMap the type for mapping an incoming word to a locale.
|
||||
type LanguagesMap interface {
|
||||
Map(lang string) (locale string, found bool)
|
||||
}
|
||||
|
||||
// Map is a Go map[string]string type which is a LanguagesMap that
|
||||
// matches literal key with value as the found locale.
|
||||
type Map map[string]string
|
||||
|
||||
// Map loops through its registered alternative language codes
|
||||
// and reports if it is valid registered locale one.
|
||||
func (m Map) Map(lang string) (string, bool) {
|
||||
locale, ok := m[lang]
|
||||
return locale, ok
|
||||
}
|
||||
|
||||
// MapFunc is a function shortcut for the LanguagesMap.
|
||||
type MapFunc func(lang string) (locale string, found bool)
|
||||
|
||||
// Map should report if a given "lang" is valid registered locale.
|
||||
func (m MapFunc) Map(lang string) (string, bool) {
|
||||
return m(lang)
|
||||
}
|
||||
|
||||
func makeDefaultLanguagesMap(languages map[string]string) MapFunc {
|
||||
return func(lang string) (string, bool) {
|
||||
lang = strings.ToLower(lang)
|
||||
for locale := range languages {
|
||||
if lang == strings.ToLower(locale) {
|
||||
return locale, true
|
||||
}
|
||||
|
||||
// this matches "en-anything" too, which can be accepted too on some cases, but not here.
|
||||
// if sep := strings.IndexRune(lang, '-'); sep > 0 {
|
||||
// lang = lang[0:sep]
|
||||
// }
|
||||
|
||||
if len(lang) == 2 {
|
||||
if strings.Contains(locale, lang) {
|
||||
return locale, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// I18n is the structure which keeps the i18n configuration and implement all Iris i18n features.
|
||||
type I18n struct {
|
||||
config Config
|
||||
|
||||
locales map[string][]*ini.File
|
||||
}
|
||||
|
||||
// If `Config.Default` is missing and `Config.Languages` or `Config.LanguagesMap` contains this key then it will set as the default locale,
|
||||
// no need to be exported(see `Config.Default`).
|
||||
const defLangCode = "en-US"
|
||||
|
||||
// NewI18n returns a new i18n middleware which contains
|
||||
// the middleware itself and a router wrapper.
|
||||
func NewI18n(c Config) *I18n {
|
||||
if len(c.Languages) == 0 {
|
||||
panic("field Languages is empty")
|
||||
}
|
||||
|
||||
// check and validate (if possible) languages map.
|
||||
if c.LanguagesMap == nil {
|
||||
c.LanguagesMap = makeDefaultLanguagesMap(c.Languages)
|
||||
}
|
||||
|
||||
if mTyp, ok := c.LanguagesMap.(Map); ok {
|
||||
for k, v := range mTyp {
|
||||
if _, ok := c.Languages[v]; !ok {
|
||||
panic(fmt.Sprintf("language alternative '%s' does not map to a valid language '%s'", k, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i := new(I18n)
|
||||
|
||||
// load messages.
|
||||
i.locales = make(map[string][]*ini.File)
|
||||
for locale, src := range c.Languages {
|
||||
if err := i.AddSource(locale, src); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// validate and set default lang code.
|
||||
if c.Default == "" {
|
||||
c.Default = defLangCode
|
||||
}
|
||||
|
||||
if locale, _, ok := i.Exists(c.Default); !ok {
|
||||
panic(fmt.Sprintf("default language '%s' does not match any of the registered language", c.Default))
|
||||
} else {
|
||||
c.Default = locale
|
||||
}
|
||||
|
||||
i.config = c
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// AddSource adds a source file to the lang locale.
|
||||
// It is called on NewI18n, New and NewWrapper.
|
||||
//
|
||||
// If you wish to use this at serve-time please protect the process with a mutex.
|
||||
func (i *I18n) AddSource(locale, src string) error {
|
||||
// remove all spaces.
|
||||
src = strings.Replace(src, " ", "", -1)
|
||||
// note: if only one, then the first element is the "v".
|
||||
languageFiles := strings.Split(src, ",")
|
||||
|
||||
for _, fileName := range languageFiles {
|
||||
if !strings.HasSuffix(fileName, ".ini") {
|
||||
fileName += ".ini"
|
||||
}
|
||||
|
||||
f, err := ini.Load(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.locales[locale] = append(i.locales[locale], f)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMessage returns a message from a locale, locale is case-sensitivity and languages map does not playing its part here.
|
||||
func (i *I18n) GetMessage(locale, section, format string, args ...interface{}) (string, bool) {
|
||||
files, ok := i.locales[locale]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
return i.getMessage(files, section, format, args)
|
||||
}
|
||||
|
||||
func (i *I18n) getMessage(files []*ini.File, section, format string, args []interface{}) (string, bool) {
|
||||
for _, f := range files {
|
||||
// returns the first available.
|
||||
// section is the same for both files if key(format) exists.
|
||||
s, err := f.GetSection(section)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
k, err := s.GetKey(format)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
format = k.Value()
|
||||
if len(args) > 0 {
|
||||
return fmt.Sprintf(format, args...), true
|
||||
}
|
||||
|
||||
return format, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Translate translates and returns a message based on any language code
|
||||
// and its key(format) with any optional arguments attached to it.
|
||||
func (i *I18n) Translate(lang, format string, args ...interface{}) string {
|
||||
if _, files, ok := i.Exists(lang); ok {
|
||||
return i.translate(files, format, args)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *I18n) translate(files []*ini.File, format string, args []interface{}) string {
|
||||
section := ""
|
||||
|
||||
if idx := strings.IndexRune(format, '.'); idx > 0 {
|
||||
section = format[:idx]
|
||||
format = format[idx+1:]
|
||||
}
|
||||
|
||||
msg, ok := i.getMessage(files, section, format, args)
|
||||
if !ok {
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
// Exists reports whether a language code is a valid registered locale through its Languages list and Languages mapping.
|
||||
func (i *I18n) Exists(lang string) (string, []*ini.File, bool) {
|
||||
if lang == "" {
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
files, ok := i.locales[lang]
|
||||
if ok {
|
||||
return lang, files, true
|
||||
}
|
||||
|
||||
for locale, files := range i.locales {
|
||||
if locale == lang {
|
||||
return locale, files, true
|
||||
}
|
||||
}
|
||||
|
||||
if i.config.LanguagesMap != nil {
|
||||
if locale, ok := i.config.LanguagesMap.Map(lang); ok {
|
||||
if files, ok := i.locales[locale]; ok {
|
||||
return locale, files, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
func (i *I18n) newTranslateLanguageFunc(files []*ini.File) func(format string, args ...interface{}) string {
|
||||
return func(format string, args ...interface{}) string {
|
||||
return i.translate(files, format, args)
|
||||
}
|
||||
}
|
||||
|
||||
const acceptLanguageHeaderKey = "Accept-Language"
|
||||
|
||||
// Handler returns the middleware handler.
|
||||
func (i *I18n) Handler() context.Handler {
|
||||
return func(ctx context.Context) {
|
||||
wasByCookie := false
|
||||
|
||||
langKey := ctx.Application().ConfigurationReadOnly().GetTranslateLanguageContextKey()
|
||||
|
||||
language, files, ok := i.Exists(ctx.Values().GetString(langKey))
|
||||
|
||||
if !ok {
|
||||
if i.config.URLParameter != "" {
|
||||
language, files, ok = i.Exists(ctx.URLParam(i.config.URLParameter))
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// then try to take the lang field from the cookie
|
||||
if i.config.Cookie != "" {
|
||||
if language, files, ok = i.Exists(ctx.GetCookie(i.config.Cookie)); ok {
|
||||
wasByCookie = true
|
||||
}
|
||||
}
|
||||
|
||||
if !ok && i.config.Subdomain {
|
||||
language, files, ok = i.Exists(ctx.Subdomain())
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// try to get by the request headers.
|
||||
if langHeader := ctx.GetHeader(acceptLanguageHeaderKey); langHeader != "" {
|
||||
idx := strings.IndexRune(langHeader, ';')
|
||||
if idx > 0 {
|
||||
langHeader = langHeader[:idx]
|
||||
}
|
||||
|
||||
language, files, ok = i.Exists(langHeader)
|
||||
}
|
||||
}
|
||||
|
||||
if !ok && i.config.Indentifier != nil {
|
||||
language, files, ok = i.Exists(i.config.Indentifier(ctx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
language, files, ok = i.Exists(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 && 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(), i.newTranslateLanguageFunc(files))
|
||||
ctx.Values().Set(ctx.Application().ConfigurationReadOnly().GetTranslateLangFunctionContextKey(), i.Translate)
|
||||
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper 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(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
|
||||
reqPath := r.URL.Path[1:]
|
||||
path := reqPath
|
||||
|
||||
if idx := strings.IndexByte(path, '/'); idx > 0 {
|
||||
path = path[:idx]
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
if lang, _, ok := i.Exists(path); ok {
|
||||
path = r.URL.Path[len(path)+1:]
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
|
||||
r.RequestURI = path
|
||||
r.URL.Path = path
|
||||
r.Header.Set(acceptLanguageHeaderKey, 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.Exists(subdomain); ok {
|
||||
host = host[dotIdx+1:]
|
||||
r.URL.Host = host
|
||||
r.Host = host
|
||||
r.Header.Set(acceptLanguageHeaderKey, lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routerHandler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new i18n middleware.
|
||||
func New(c Config) context.Handler {
|
||||
return NewI18n(c).Handler()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return NewI18n(c).Wrapper()
|
||||
}
|
||||
|
||||
// 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()
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for i := 0; i < iType.NumField(); i++ {
|
||||
fieldName := reflect.TypeOf(sourceInterface).Elem().Field(i).Name
|
||||
fieldValue := reflect.ValueOf(sourceInterface).Elem().Field(i).String()
|
||||
|
||||
result[fieldName] = Translate(ctx, fieldValue)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user