X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Fi18n.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Fi18n.go;h=13fe810da02b3ab12ebbc86b3a302a197d99c903;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/i18n.go b/src/foundation/api/revel/i18n.go new file mode 100644 index 0000000..13fe810 --- /dev/null +++ b/src/foundation/api/revel/i18n.go @@ -0,0 +1,245 @@ +// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. +// Revel Framework source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package revel + +import ( + "fmt" + "html/template" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/revel/config" +) + +const ( + // CurrentLocaleViewArg the key for the current locale view arg value + CurrentLocaleViewArg = "currentLocale" + + messageFilesDirectory = "messages" + messageFilePattern = `^\w+\.[a-zA-Z]{2}$` + defaultUnknownFormat = "??? %s ???" + unknownFormatConfigKey = "i18n.unknown_format" + defaultLanguageOption = "i18n.default_language" + localeCookieConfigKey = "i18n.cookie" +) + +var ( + // All currently loaded message configs. + messages map[string]*config.Config + localeParameterName string + i18nLog = RevelLog.New("section", "i18n") +) + +// MessageFunc allows you to override the translation interface. +// +// Set this to your own function that translates to the current locale. +// This allows you to set up your own loading and logging of translated texts. +// +// See Message(...) in i18n.go for example of function. +var MessageFunc = Message + +// MessageLanguages returns 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 +} + +// Message performs 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) + unknownValueFormat := getUnknownValueFormat() + + messageConfig, knownLanguage := messages[language] + if !knownLanguage { + i18nLog.Debugf("Unsupported language for locale '%s' and message '%s', trying default language", locale, message) + + if defaultLanguage, found := Config.String(defaultLanguageOption); found { + i18nLog.Debugf("Using default language '%s'", defaultLanguage) + + messageConfig, knownLanguage = messages[defaultLanguage] + if !knownLanguage { + i18nLog.Debugf("Unsupported default language for locale '%s' and message '%s'", defaultLanguage, message) + return fmt.Sprintf(unknownValueFormat, message) + } + } else { + i18nLog.Warnf("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, err := messageConfig.String(region, message) + if err != nil { + i18nLog.Warnf("Unknown message '%s' for locale '%s'", message, locale) + return fmt.Sprintf(unknownValueFormat, message) + } + + if len(args) > 0 { + i18nLog.Debugf("Arguments detected, formatting '%s' with %v", value, args) + safeArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + switch a := arg.(type) { + case template.HTML: + safeArgs = append(safeArgs, a) + case string: + safeArgs = append(safeArgs, template.HTML(template.HTMLEscapeString(a))) + default: + safeArgs = append(safeArgs, a) + } + } + value = fmt.Sprintf(value, safeArgs...) + } + + return value +} + +func parseLocale(locale string) (language, region string) { + if strings.Contains(locale, "-") { + languageAndRegion := strings.Split(locale, "-") + return languageAndRegion[0], languageAndRegion[1] + } + + return locale, "" +} + +// Retrieve message format or default format when i18n message is missing. +func getUnknownValueFormat() string { + return Config.StringDefault(unknownFormatConfigKey, defaultUnknownFormat) +} + +// 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) + + // Read in messages from the modules. Load the module messges first, + // so that it can be override in parent application + for _, module := range Modules { + i18nLog.Debug("Importing messages from module:", "importpath", module.ImportPath) + if err := Walk(filepath.Join(module.Path, messageFilesDirectory), loadMessageFile); err != nil && + !os.IsNotExist(err) { + i18nLog.Error("Error reading messages files from module:", "error", err) + } + } + + if err := Walk(path, loadMessageFile); err != nil && !os.IsNotExist(err) { + i18nLog.Error("Error reading messages files:", "error", err) + } +} + +// Load a single message file +func loadMessageFile(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 { + messageConfig, err := parseMessagesFile(path) + if err != nil { + return err + } + locale := parseLocaleFromFileName(info.Name()) + + // If we have already parsed a message file for this locale, merge both + if _, exists := messages[locale]; exists { + messages[locale].Merge(messageConfig) + i18nLog.Debugf("Successfully merged messages for locale '%s'", locale) + } else { + messages[locale] = messageConfig + } + + i18nLog.Debug("Successfully loaded messages from file", "file", info.Name()) + } else { + i18nLog.Warn("Ignoring file because it did not have a valid extension", "file", info.Name()) + } + + return nil +} + +func parseMessagesFile(path string) (messageConfig *config.Config, err error) { + messageConfig, err = config.ReadDefault(path) + return +} + +func parseLocaleFromFileName(file string) string { + extension := filepath.Ext(file)[1:] + return strings.ToLower(extension) +} + +func init() { + OnAppStart(func() { + loadMessages(filepath.Join(BasePath, messageFilesDirectory)) + localeParameterName = Config.StringDefault("i18n.locale.parameter", "") + }, 0) +} + +func I18nFilter(c *Controller, fc []Filter) { + foundLocale := false + // Search for a parameter first + if localeParameterName != "" { + if locale, found := c.Params.Values[localeParameterName]; found && len(locale[0]) > 0 { + setCurrentLocaleControllerArguments(c, locale[0]) + foundLocale = true + i18nLog.Debug("Found locale parameter value: ", "locale", locale[0]) + } + } + if !foundLocale { + if foundCookie, cookieValue := hasLocaleCookie(c.Request); foundCookie { + i18nLog.Debug("Found locale cookie value: ", "cookie", cookieValue) + setCurrentLocaleControllerArguments(c, cookieValue) + } else if foundHeader, headerValue := hasAcceptLanguageHeader(c.Request); foundHeader { + i18nLog.Debug("Found Accept-Language header value: ", "header", headerValue) + setCurrentLocaleControllerArguments(c, headerValue) + } else { + i18nLog.Debug("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 *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 *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 *Request) (bool, string) { + if request != nil { + name := Config.StringDefault(localeCookieConfigKey, CookiePrefix+"_LANG") + cookie, err := request.Cookie(name) + if err == nil { + return true, cookie.GetValue() + } + i18nLog.Debug("Unable to read locale cookie ", "name", name, "error", err) + } + + return false, "" +}