224 lines
6.8 KiB
Go
224 lines
6.8 KiB
Go
package i18n
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/revel/revel"
|
|
"github.com/robfig/config"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
CurrentLocaleViewArg = "currentLocale" // The key for the current locale render arg value
|
|
|
|
messageFilesDirectory = "messages"
|
|
messageFilePattern = `^\w+\.conf$`
|
|
unknownValueFormat = "??? %s ???"
|
|
defaultLanguageOption = "i18n.default_language"
|
|
localeCookieConfigKey = "i18n.cookie"
|
|
)
|
|
|
|
var (
|
|
// All currently loaded message configs.
|
|
// en-us, zh-cn, zh-hk ->
|
|
messages map[string]*config.Config
|
|
)
|
|
|
|
func GetAllLangMessages() map[string]*config.Config {
|
|
return messages
|
|
}
|
|
|
|
func HasLang(lang string) bool {
|
|
_, ok := messages[lang]
|
|
return ok
|
|
}
|
|
|
|
func GetDefaultLang() string {
|
|
lang, _ := revel.Config.String(defaultLanguageOption)
|
|
return lang
|
|
}
|
|
|
|
// Return all currently loaded message languages.
|
|
func MessageLanguages() []string {
|
|
languages := make([]string, len(messages))
|
|
i := 0
|
|
for language, _ := range messages {
|
|
languages[i] = language
|
|
i++
|
|
}
|
|
return languages
|
|
}
|
|
|
|
// Perform a message look-up for the given locale and message using the given arguments.
|
|
//
|
|
// When either an unknown locale or message is detected, a specially formatted string is returned.
|
|
func Message(locale, message string, args ...interface{}) string {
|
|
language, region := parseLocale(locale)
|
|
|
|
langAndRegion := language + "-" + region
|
|
// revel.TRACE.Println(langAndRegion + " 怎么回事")
|
|
|
|
messageConfig, knownLanguage := messages[langAndRegion]
|
|
if !knownLanguage {
|
|
// revel.TRACE.Printf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message)
|
|
|
|
if defaultLanguage, found := revel.Config.String(defaultLanguageOption); found {
|
|
// revel.TRACE.Printf("Using default language '%s'", defaultLanguage)
|
|
|
|
messageConfig, knownLanguage = messages[defaultLanguage]
|
|
if !knownLanguage {
|
|
// WARN.Printf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message)
|
|
return fmt.Sprintf(unknownValueFormat, message)
|
|
}
|
|
} else {
|
|
// WARN.Printf("Unable to find default language option (%s); messages for unsupported locales will never be translated", defaultLanguageOption)
|
|
return fmt.Sprintf(unknownValueFormat, message)
|
|
}
|
|
}
|
|
|
|
// This works because unlike the goconfig documentation suggests it will actually
|
|
// try to resolve message in DEFAULT if it did not find it in the given section.
|
|
value, error := messageConfig.String(region, message)
|
|
if error != nil {
|
|
// WARN.Printf("Unknown message '%s' for locale '%s'", message, locale)
|
|
return fmt.Sprintf(unknownValueFormat, message)
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
// revel.TRACE.Printf("Arguments detected, formatting '%s' with %v", value, args)
|
|
value = fmt.Sprintf(value, args...)
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
func parseLocale(locale string) (language, region string) {
|
|
if strings.Contains(locale, "-") {
|
|
languageAndRegion := strings.Split(locale, "-")
|
|
return languageAndRegion[0], languageAndRegion[1]
|
|
}
|
|
|
|
return locale, ""
|
|
}
|
|
|
|
// Recursively read and cache all available messages from all message files on the given path.
|
|
func loadMessages(path string) {
|
|
messages = make(map[string]*config.Config)
|
|
|
|
if error := filepath.Walk(path, loadEachMessageLang); error != nil && !os.IsNotExist(error) {
|
|
// ERROR.Println("Error reading messages files:", error)
|
|
}
|
|
}
|
|
|
|
// 加载每一个文件夹
|
|
func loadEachMessageLang(parentPath string, parentInfo os.FileInfo, osError error) (err error) {
|
|
if !parentInfo.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
if err := filepath.Walk(parentPath, func(path string, info os.FileInfo, osError error) error {
|
|
return loadMessageFile(parentInfo.Name(), path, info, osError)
|
|
|
|
}); err != nil && !os.IsNotExist(err) {
|
|
// ERROR.Println("Error reading messages files:", error)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Load a single message file
|
|
func loadMessageFile(locale string, path string, info os.FileInfo, osError error) error {
|
|
if osError != nil {
|
|
return osError
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
if matched, _ := regexp.MatchString(messageFilePattern, info.Name()); matched {
|
|
if config, error := parseMessagesFile(path); error != nil {
|
|
return error
|
|
} else {
|
|
// locale := parseLocaleFromFileName(info.Name())
|
|
// revel.TRACE.Print(locale + "----locale")
|
|
|
|
// If we have already parsed a message file for this locale, merge both
|
|
if _, exists := messages[locale]; exists {
|
|
messages[locale].Merge(config)
|
|
revel.TRACE.Printf("Successfully merged messages for locale '%s'", locale)
|
|
} else {
|
|
messages[locale] = config
|
|
}
|
|
|
|
revel.TRACE.Println("Successfully loaded messages from file", info.Name())
|
|
}
|
|
} else {
|
|
revel.TRACE.Printf("Ignoring file %s because it did not have a valid extension", info.Name())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseMessagesFile(path string) (messageConfig *config.Config, error error) {
|
|
messageConfig, error = config.ReadDefault(path)
|
|
return
|
|
}
|
|
|
|
func parseLocaleFromFileName(file string) string {
|
|
extension := filepath.Ext(file)[1:]
|
|
return strings.ToLower(extension)
|
|
}
|
|
|
|
func init() {
|
|
revel.OnAppStart(func() {
|
|
loadMessages(filepath.Join(revel.BasePath, messageFilesDirectory))
|
|
})
|
|
}
|
|
|
|
func I18nFilter(c *revel.Controller, fc []revel.Filter) {
|
|
if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie {
|
|
revel.TRACE.Printf("Found locale cookie value: %s", cookieValue)
|
|
setCurrentLocaleControllerArguments(c, cookieValue)
|
|
} else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader {
|
|
revel.TRACE.Printf("Found Accept-Language header value: %s", headerValue)
|
|
setCurrentLocaleControllerArguments(c, headerValue)
|
|
} else {
|
|
revel.TRACE.Println("Unable to find locale in cookie or header, using empty string")
|
|
setCurrentLocaleControllerArguments(c, "")
|
|
}
|
|
fc[0](c, fc[1:])
|
|
}
|
|
|
|
// Set the current locale controller argument (CurrentLocaleControllerArg) with the given locale.
|
|
func setCurrentLocaleControllerArguments(c *revel.Controller, locale string) {
|
|
c.Request.Locale = locale
|
|
c.ViewArgs[CurrentLocaleViewArg] = locale
|
|
}
|
|
|
|
// Determine whether the given request has valid Accept-Language value.
|
|
//
|
|
// Assumes that the accept languages stored in the request are sorted according to quality, with top
|
|
// quality first in the slice.
|
|
func hasAcceptLanguageHeader(request *revel.Request) (bool, string) {
|
|
if request.AcceptLanguages != nil && len(request.AcceptLanguages) > 0 {
|
|
return true, request.AcceptLanguages[0].Language
|
|
}
|
|
|
|
return false, ""
|
|
}
|
|
|
|
// Determine whether the given request has a valid language cookie value.
|
|
func hasLocaleCookie(request *revel.Request) (bool, string) {
|
|
if request != nil && request.Cookies() != nil {
|
|
name := revel.Config.StringDefault(localeCookieConfigKey, revel.CookiePrefix+"_LANG")
|
|
if cookie, error := request.Cookie(name); error == nil {
|
|
return true, cookie.Value
|
|
} else {
|
|
revel.TRACE.Printf("Unable to read locale cookie with name '%s': %s", name, error.Error())
|
|
}
|
|
}
|
|
|
|
return false, ""
|
|
}
|