diff --git a/Gulpfile.js b/Gulpfile.js index e9289c2..f1bd347 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -268,15 +268,17 @@ gulp.task('i18n', function() { // 必须要的 // keys.push(); - genI18nJsFile('blog.zh', [], keys); - genI18nJsFile('blog.en', [], keys); - genI18nJsFile('blog.fr', [], keys); - genI18nJsFile('blog.pt', [], keys); + genI18nJsFile('blog.zh-cn', [], keys); + genI18nJsFile('blog.zh-hk', [], keys); + genI18nJsFile('blog.en-us', [], keys); + genI18nJsFile('blog.fr-fr', [], keys); + genI18nJsFile('blog.pt-pt', [], keys); - genI18nJsFile('msg.fr', ['member.fr', 'markdown.fr', 'album.fr'], keys); - genI18nJsFile('msg.zh', ['member.zh', 'markdown.zh', 'album.zh'], keys); - genI18nJsFile('msg.en', ['member.en', 'markdown.en', 'album.en'], keys); - genI18nJsFile('msg.pt', ['member.pt', 'markdown.pt', 'album.pt'], keys); + genI18nJsFile('msg.fr-fr', ['member.fr-fr', 'markdown.fr-fr', 'album.fr-fr'], keys); + genI18nJsFile('msg.zh-cn', ['member.zh-cn', 'markdown.zh-cn', 'album.zh-cn'], keys); + genI18nJsFile('msg.zh-hk', ['member.zh-hk', 'markdown.zh-hk', 'album.zh-hk'], keys); + genI18nJsFile('msg.en-us', ['member.en-us', 'markdown.en-us', 'album.en-us'], keys); + genI18nJsFile('msg.pt-pt', ['member.pt-pt', 'markdown.pt-pt', 'album.pt-pt'], keys); }); // 合并album需要的js diff --git a/app/controllers/BaseController.go b/app/controllers/BaseController.go index f9c07b7..c3c8f47 100644 --- a/app/controllers/BaseController.go +++ b/app/controllers/BaseController.go @@ -3,6 +3,7 @@ package controllers import ( "encoding/json" "github.com/leanote/leanote/app/info" + "github.com/leanote/leanote/app/lea/i18n" "github.com/revel/revel" "gopkg.in/mgo.v2/bson" // . "github.com/leanote/leanote/app/lea" @@ -181,13 +182,17 @@ func (c BaseController) E404() revel.Result { // 设置本地 func (c BaseController) SetLocale() string { locale := string(c.Request.Locale) // zh-CN + // lang := locale + // if strings.Contains(locale, "-") { + // pos := strings.Index(locale, "-") + // lang = locale[0:pos] + // } + // if lang != "zh" && lang != "en" { + // lang = "en" + // } lang := locale - if strings.Contains(locale, "-") { - pos := strings.Index(locale, "-") - lang = locale[0:pos] - } - if lang != "zh" && lang != "en" && lang != "fr" && lang != "pt" { - lang = "en" + if !i18n.HasLang(locale) { + lang = i18n.GetDefaultLang() } c.RenderArgs["locale"] = lang c.RenderArgs["siteUrl"] = configService.GetSiteUrl() diff --git a/app/i18n/i18n.go b/app/i18n/i18n.go deleted file mode 100644 index b008de2..0000000 --- a/app/i18n/i18n.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "os" - "strings" -) - -// convert revel msg to js msg - -var msgBasePath = "/Users/life/Documents/Go/package2/src/github.com/leanote/leanote/messages/" -var targetBasePath = "/Users/life/Documents/Go/package2/src/github.com/leanote/leanote/public/js/i18n/" - -func parse(filename string) { - file, err := os.Open(msgBasePath + filename) - reader := bufio.NewReader(file) - msg := map[string]string{} - if err != nil { - fmt.Println(err) - return - } - for true { - line, _, err := reader.ReadLine() - - if err != nil { - break - } - - if len(line) == 0 { - continue - } - // 对每一行进行处理 - if line[0] == '#' || line[1] == '#' { - continue - } - lineStr := string(line) - - // 找到第一个=位置 - pos := strings.Index(lineStr, "=") - - if pos < 0 { - continue - } - - key := string(line[0:pos]) - value := string(line[pos+1:]) - - // fmt.Println(lineStr) - // fmt.Println(value) - - msg[key] = value - } - - // JSON - b, _ := json.Marshal(msg) - str := string(b) - fmt.Println(str) - - targetName := targetBasePath + filename + ".js" - file2, err2 := os.OpenFile(targetName, os.O_RDWR|os.O_CREATE, 0644) - if err2 != nil { - file2, err2 = os.Create(targetName) - } - file2.WriteString("var MSG = " + str + ";" + ` -function getMsg(key, data) { - var msg = MSG[key] - if(msg) { - if(data) { - if(!isArray(data)) { - data = [data]; - } - for(var i = 0; i < data.length; ++i) { - msg = msg.replace("%s", data[i]); - } - } - return msg; - } - return key; -}`) -} - -// 生成js的i18n文件 -func main() { - parse("msg.en") - parse("msg.zh") - parse("msg.fr") - parse("msg.pt") - parse("blog.zh") - parse("blog.en") - parse("blog.fr") - parse("blog.pt") -} diff --git a/app/init.go b/app/init.go index 1bf7c62..5798a3f 100644 --- a/app/init.go +++ b/app/init.go @@ -10,6 +10,7 @@ import ( "github.com/leanote/leanote/app/db" . "github.com/leanote/leanote/app/lea" _ "github.com/leanote/leanote/app/lea/binder" + "github.com/leanote/leanote/app/lea/i18n" "github.com/leanote/leanote/app/lea/route" "github.com/leanote/leanote/app/service" "github.com/revel/revel" @@ -38,9 +39,10 @@ func init() { // session.SessionFilter, // leanote session // session.MSessionFilter, // leanote memcache session - revel.FlashFilter, // Restore and write the flash cookie. - revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie. - revel.I18nFilter, // Resolve the requested language + revel.FlashFilter, // Restore and write the flash cookie. + revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie. + // revel.I18nFilter, // Resolve the requested language + i18n.I18nFilter, // Resolve the requested language by leanote revel.InterceptorFilter, // Run interceptors around the action. revel.CompressFilter, // Compress the result. revel.ActionInvoker, // Invoke the action. @@ -180,10 +182,18 @@ func init() { return template.HTML(tagStr) } + revel.TemplateFuncs["msg"] = func(renderArgs map[string]interface{}, message string, args ...interface{}) template.HTML { + str, ok := renderArgs[revel.CurrentLocaleRenderArg].(string) + if !ok { + return "" + } + return template.HTML(i18n.Message(str, message, args...)) + } + // 不用revel的msg revel.TemplateFuncs["leaMsg"] = func(renderArgs map[string]interface{}, key string) template.HTML { locale, _ := renderArgs[revel.CurrentLocaleRenderArg].(string) - str := revel.Message(locale, key) + str := i18n.Message(locale, key) if strings.HasPrefix(str, "???") { str = key } @@ -392,9 +402,9 @@ func init() { */ /* - {{range $i := N 1 10}} -
{{$i}}
- {{end}} + {{range $i := N 1 10}} +
{{$i}}
+ {{end}} */ revel.TemplateFuncs["N"] = func(start, end int) (stream chan int) { stream = make(chan int) diff --git a/app/lea/i18n/i18n.go b/app/lea/i18n/i18n.go new file mode 100644 index 0000000..e646027 --- /dev/null +++ b/app/lea/i18n/i18n.go @@ -0,0 +1,208 @@ +package i18n + +import ( + "fmt" + "github.com/revel/revel" + "github.com/robfig/config" + "os" + "path/filepath" + "regexp" + "strings" +) + +const ( + CurrentLocaleRenderArg = "currentLocale" // The key for the current locale render arg value + + messageFilesDirectory = "messages" + messageFilePattern = `^\w+\.[a-zA-Z]{2}\-[a-zA-Z]{2}$` + 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, loadMessageFile); error != nil && !os.IsNotExist(error) { + // ERROR.Println("Error reading messages files:", error) + } +} + +// 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 { + 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.RenderArgs[CurrentLocaleRenderArg] = 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, "" +} diff --git a/app/views/home/header.html b/app/views/home/header.html index c6c5167..0302778 100644 --- a/app/views/home/header.html +++ b/app/views/home/header.html @@ -41,7 +41,8 @@ function log(o) { English Français 简体中文 - Português + 繁体中文 + Português