123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- // Copyright 2014 The Macaron Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License"): you may
- // not use this file except in compliance with the License. You may obtain
- // a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- // License for the specific language governing permissions and limitations
- // under the License.
- // Package i18n is a middleware that provides app Internationalization and Localization of Macaron.
- package i18n
- import (
- "fmt"
- "path"
- "strings"
- "github.com/Unknwon/com"
- "github.com/Unknwon/i18n"
- "golang.org/x/text/language"
- "gopkg.in/macaron.v1"
- )
- const _VERSION = "0.3.0"
- func Version() string {
- return _VERSION
- }
- // initLocales initializes language type list and Accept-Language header matcher.
- func initLocales(opt Options) language.Matcher {
- tags := make([]language.Tag, len(opt.Langs))
- for i, lang := range opt.Langs {
- tags[i] = language.Raw.Make(lang)
- fname := fmt.Sprintf(opt.Format, lang)
- // Append custom locale file.
- custom := []interface{}{}
- customPath := path.Join(opt.CustomDirectory, fname)
- if com.IsFile(customPath) {
- custom = append(custom, customPath)
- }
- var locale interface{}
- if data, ok := opt.Files[fname]; ok {
- locale = data
- } else {
- locale = path.Join(opt.Directory, fname)
- }
- err := i18n.SetMessageWithDesc(lang, opt.Names[i], locale, custom...)
- if err != nil && err != i18n.ErrLangAlreadyExist {
- panic(fmt.Errorf("fail to set message file(%s): %v", lang, err))
- }
- }
- return language.NewMatcher(tags)
- }
- // A Locale describles the information of localization.
- type Locale struct {
- i18n.Locale
- }
- // Language returns language current locale represents.
- func (l Locale) Language() string {
- return l.Lang
- }
- // Options represents a struct for specifying configuration options for the i18n middleware.
- type Options struct {
- // Suburl of path. Default is empty.
- SubURL string
- // Directory to load locale files. Default is "conf/locale"
- Directory string
- // File stores actual data of locale files. Used for in-memory purpose.
- Files map[string][]byte
- // Custom directory to overload locale files. Default is "custom/conf/locale"
- CustomDirectory string
- // Langauges that will be supported, order is meaningful.
- Langs []string
- // Human friendly names corresponding to Langs list.
- Names []string
- // Default language locale, leave empty to remain unset.
- DefaultLang string
- // Locale file naming style. Default is "locale_%s.ini".
- Format string
- // Name of language parameter name in URL. Default is "lang".
- Parameter string
- // Redirect when user uses get parameter to specify language.
- Redirect bool
- // Name that maps into template variable. Default is "i18n".
- TmplName string
- // Configuration section name. Default is "i18n".
- Section string
- }
- func prepareOptions(options []Options) Options {
- var opt Options
- if len(options) > 0 {
- opt = options[0]
- }
- if len(opt.Section) == 0 {
- opt.Section = "i18n"
- }
- sec := macaron.Config().Section(opt.Section)
- opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
- if len(opt.Langs) == 0 {
- opt.Langs = sec.Key("LANGS").Strings(",")
- }
- if len(opt.Names) == 0 {
- opt.Names = sec.Key("NAMES").Strings(",")
- }
- if len(opt.Langs) == 0 {
- panic("no language is specified")
- } else if len(opt.Langs) != len(opt.Names) {
- panic("length of langs is not same as length of names")
- }
- i18n.SetDefaultLang(opt.DefaultLang)
- if len(opt.Directory) == 0 {
- opt.Directory = sec.Key("DIRECTORY").MustString("conf/locale")
- }
- if len(opt.CustomDirectory) == 0 {
- opt.CustomDirectory = sec.Key("CUSTOM_DIRECTORY").MustString("custom/conf/locale")
- }
- if len(opt.Format) == 0 {
- opt.Format = sec.Key("FORMAT").MustString("locale_%s.ini")
- }
- if len(opt.Parameter) == 0 {
- opt.Parameter = sec.Key("PARAMETER").MustString("lang")
- }
- if !opt.Redirect {
- opt.Redirect = sec.Key("REDIRECT").MustBool()
- }
- if len(opt.TmplName) == 0 {
- opt.TmplName = sec.Key("TMPL_NAME").MustString("i18n")
- }
- return opt
- }
- type LangType struct {
- Lang, Name string
- }
- // I18n is a middleware provides localization layer for your application.
- // Paramenter langs must be in the form of "en-US", "zh-CN", etc.
- // Otherwise it may not recognize browser input.
- func I18n(options ...Options) macaron.Handler {
- opt := prepareOptions(options)
- m := initLocales(opt)
- return func(ctx *macaron.Context) {
- isNeedRedir := false
- hasCookie := false
- // 1. Check URL arguments.
- lang := ctx.Query(opt.Parameter)
- // 2. Get language information from cookies.
- if len(lang) == 0 {
- lang = ctx.GetCookie("lang")
- hasCookie = true
- } else {
- isNeedRedir = true
- }
- // Check again in case someone modify by purpose.
- if !i18n.IsExist(lang) {
- lang = ""
- isNeedRedir = false
- hasCookie = false
- }
- // 3. Get language information from 'Accept-Language'.
- // The first element in the list is chosen to be the default language automatically.
- if len(lang) == 0 {
- tags, _, _ := language.ParseAcceptLanguage(ctx.Req.Header.Get("Accept-Language"))
- tag, _, _ := m.Match(tags...)
- lang = tag.String()
- isNeedRedir = false
- }
- curLang := LangType{
- Lang: lang,
- }
- // Save language information in cookies.
- if !hasCookie {
- ctx.SetCookie("lang", curLang.Lang, 1<<31-1, "/"+strings.TrimPrefix(opt.SubURL, "/"))
- }
- restLangs := make([]LangType, 0, i18n.Count()-1)
- langs := i18n.ListLangs()
- names := i18n.ListLangDescs()
- for i, v := range langs {
- if lang != v {
- restLangs = append(restLangs, LangType{v, names[i]})
- } else {
- curLang.Name = names[i]
- }
- }
- // Set language properties.
- locale := Locale{i18n.Locale{lang}}
- ctx.Map(locale)
- ctx.Locale = locale
- ctx.Data[opt.TmplName] = locale
- ctx.Data["Tr"] = i18n.Tr
- ctx.Data["Lang"] = locale.Lang
- ctx.Data["LangName"] = curLang.Name
- ctx.Data["AllLangs"] = append([]LangType{curLang}, restLangs...)
- ctx.Data["RestLangs"] = restLangs
- if opt.Redirect && isNeedRedir {
- ctx.Redirect(opt.SubURL + ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")])
- }
- }
- }
|