From e25b84c6867275eb696d17d4593d44fd03f45c9a Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Tue, 10 Oct 2017 12:17:38 -0400 Subject: [PATCH] dynamic translations --- .../libnntpchan/nntp_handler.cpp | 2 +- .../nntpchan-daemon/libnntpchan/server.cpp | 6 +- .../srndv2/src/srnd/cache_interface.go | 12 +++- .../backends/srndv2/src/srnd/file_cache.go | 34 +++++---- contrib/backends/srndv2/src/srnd/frontend.go | 2 + .../backends/srndv2/src/srnd/frontend_http.go | 14 ++-- .../srndv2/src/srnd/frontend_multi.go | 5 ++ contrib/backends/srndv2/src/srnd/i18n.go | 59 ++++++++------- contrib/backends/srndv2/src/srnd/installer.go | 2 +- contrib/backends/srndv2/src/srnd/mod_http.go | 6 +- contrib/backends/srndv2/src/srnd/model.go | 7 +- contrib/backends/srndv2/src/srnd/model_mem.go | 50 +++++++++++-- .../backends/srndv2/src/srnd/null_cache.go | 63 +++++++++++----- contrib/backends/srndv2/src/srnd/templates.go | 72 +++++++++++-------- .../srndv2/src/srnd/templates_test.go | 4 +- .../backends/srndv2/src/srnd/varnish_cache.go | 6 +- 16 files changed, 235 insertions(+), 109 deletions(-) diff --git a/contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp b/contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp index ef8c310..dabcdd4 100644 --- a/contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp +++ b/contrib/backends/nntpchan-daemon/libnntpchan/nntp_handler.cpp @@ -113,7 +113,7 @@ namespace nntpchan QueueLine("STREAMING"); QueueLine("."); } else if (cmd == "CHECK") { - if(cmdlen == 2) { + if(cmdlen >= 2) { const std::string & msgid = command[1]; if(IsValidMessageID(msgid) && m_store.Accept(msgid)) { diff --git a/contrib/backends/nntpchan-daemon/libnntpchan/server.cpp b/contrib/backends/nntpchan-daemon/libnntpchan/server.cpp index d08e9b1..7888305 100644 --- a/contrib/backends/nntpchan-daemon/libnntpchan/server.cpp +++ b/contrib/backends/nntpchan-daemon/libnntpchan/server.cpp @@ -90,15 +90,16 @@ namespace nntpchan }, [] (uv_stream_t * s, ssize_t nread, const uv_buf_t * b) { IServerConn * self = (IServerConn*) s->data; if(self == nullptr) { - delete [] b->base; + if(b->base) + delete [] b->base; return; } if(nread > 0) { - b->base[nread] = 0; self->m_handler->OnData(b->base, nread); self->SendNextReply(); if(self->m_handler->ShouldClose()) self->Close(); + delete [] b->base; } else { if (nread != UV_EOF) { std::cerr << "error in nntp server conn alloc: "; @@ -108,7 +109,6 @@ namespace nntpchan // got eof or error self->Close(); } - delete [] b->base; }); } diff --git a/contrib/backends/srndv2/src/srnd/cache_interface.go b/contrib/backends/srndv2/src/srnd/cache_interface.go index dd10af8..46c0978 100644 --- a/contrib/backends/srndv2/src/srnd/cache_interface.go +++ b/contrib/backends/srndv2/src/srnd/cache_interface.go @@ -5,6 +5,11 @@ import ( "net/http" ) +type CacheHandler interface { + http.Handler + GetI18N(r *http.Request) *I18N +} + type CacheInterface interface { RegenAll() RegenFrontPage() @@ -17,7 +22,7 @@ type CacheInterface interface { Start() Close() - GetHandler() http.Handler + GetHandler() CacheHandler SetRequireCaptcha(required bool) } @@ -26,6 +31,7 @@ type CacheInterface interface { func NewCache(cache_type, host, port, user, password string, cache_config, config map[string]string, db Database, store ArticleStore) CacheInterface { prefix := config["prefix"] webroot := config["webroot"] + translations := config["translations"] threads := mapGetInt(config, "regen_threads", 1) name := config["name"] attachments := mapGetInt(config, "allow_files", 1) == 1 @@ -34,12 +40,12 @@ func NewCache(cache_type, host, port, user, password string, cache_config, confi return NewFileCache(prefix, webroot, name, threads, attachments, db, store) } if cache_type == "null" { - return NewNullCache(prefix, webroot, name, attachments, db, store) + return NewNullCache(prefix, webroot, name, translations, attachments, db, store) } if cache_type == "varnish" { url := cache_config["url"] bind_addr := cache_config["bind"] - return NewVarnishCache(url, bind_addr, prefix, webroot, name, attachments, db, store) + return NewVarnishCache(url, bind_addr, prefix, webroot, name, translations, attachments, db, store) } log.Fatalf("invalid cache type: %s", cache_type) diff --git a/contrib/backends/srndv2/src/srnd/file_cache.go b/contrib/backends/srndv2/src/srnd/file_cache.go index ed79378..2d33616 100644 --- a/contrib/backends/srndv2/src/srnd/file_cache.go +++ b/contrib/backends/srndv2/src/srnd/file_cache.go @@ -15,6 +15,8 @@ type FileCache struct { database Database store ArticleStore + handler http.Handler + webroot_dir string name string @@ -112,7 +114,7 @@ func (self *FileCache) regenLongTerm() { log.Println("cannot render history graph", err) return } - template.genGraphs(self.prefix, wr, self.database) + template.genGraphs(self.prefix, wr, self.database, nil) } func (self *FileCache) pollLongTerm() { @@ -191,7 +193,7 @@ func (self *FileCache) regenerateThread(root ArticleEntry, json bool) { log.Println("did not write", fname, err) return } - template.genThread(self.attachments, self.requireCaptcha, root, self.prefix, self.name, wr, self.database, json) + template.genThread(self.attachments, self.requireCaptcha, root, self.prefix, self.name, wr, self.database, json, nil) } else { log.Println("don't have root post", msgid, "not regenerating thread") } @@ -206,7 +208,7 @@ func (self *FileCache) regenerateBoardPage(board string, page int, json bool) { log.Println("error generating board page", page, "for", board, err) return } - template.genBoardPage(self.attachments, self.requireCaptcha, self.prefix, self.name, board, page, wr, self.database, json) + template.genBoardPage(self.attachments, self.requireCaptcha, self.prefix, self.name, board, page, wr, self.database, json, nil) } // regenerate the catalog for a board @@ -218,7 +220,7 @@ func (self *FileCache) regenerateCatalog(board string) { log.Println("error generating catalog for", board, err) return } - template.genCatalog(self.prefix, self.name, board, wr, self.database) + template.genCatalog(self.prefix, self.name, board, wr, self.database, nil) } // regenerate the front page @@ -236,7 +238,7 @@ func (self *FileCache) RegenFrontPage() { return } - template.genFrontPage(10, self.prefix, self.name, indexwr, boardswr, self.database) + template.genFrontPage(10, self.prefix, self.name, indexwr, boardswr, self.database, nil) j_boardswr, err2 := os.Create(filepath.Join(self.webroot_dir, "boards.json")) g := self.database.GetAllNewsgroups() @@ -258,7 +260,7 @@ func (self *FileCache) regenUkko() { log.Println("error generating ukko markup", err) return } - template.genUkko(self.prefix, self.name, wr, self.database, false) + template.genUkko(self.prefix, self.name, wr, self.database, false, nil) // json fname = filepath.Join(self.webroot_dir, "ukko.json") @@ -268,7 +270,7 @@ func (self *FileCache) regenUkko() { log.Println("error generating ukko json", err) return } - template.genUkko(self.prefix, self.name, wr, self.database, true) + template.genUkko(self.prefix, self.name, wr, self.database, true, nil) i := 0 for i < 10 { fname := fmt.Sprintf("ukko-%d.html", i) @@ -279,14 +281,14 @@ func (self *FileCache) regenUkko() { return } defer f.Close() - template.genUkkoPaginated(self.prefix, self.name, f, self.database, i, false) + template.genUkkoPaginated(self.prefix, self.name, f, self.database, i, false, nil) j, err := os.Create(jname) if err != nil { log.Printf("failed to create json ukko", i, err) return } defer j.Close() - template.genUkkoPaginated(self.prefix, self.name, j, self.database, i, true) + template.genUkkoPaginated(self.prefix, self.name, j, self.database, i, true, nil) } } @@ -334,8 +336,16 @@ func (self *FileCache) GetGroupChan() chan groupRegenRequest { return self.regenGroupChan } -func (self *FileCache) GetHandler() http.Handler { - return http.FileServer(http.Dir(self.webroot_dir)) +func (self *FileCache) GetI18N(r *http.Request) *I18N { + return nil +} + +func (self *FileCache) GetHandler() CacheHandler { + return self +} + +func (self *FileCache) ServeHTTP(w http.ResponseWriter, r *http.Request) { + self.handler.ServeHTTP(w, r) } func (self *FileCache) Close() { @@ -348,7 +358,7 @@ func (self *FileCache) SetRequireCaptcha(require bool) { func NewFileCache(prefix, webroot, name string, threads int, attachments bool, db Database, store ArticleStore) CacheInterface { cache := new(FileCache) - + cache.handler = http.FileServer(http.Dir(webroot)) cache.regenBoardTicker = time.NewTicker(time.Second * 10) cache.longTermTicker = time.NewTicker(time.Hour) cache.ukkoTicker = time.NewTicker(time.Second * 30) diff --git a/contrib/backends/srndv2/src/srnd/frontend.go b/contrib/backends/srndv2/src/srnd/frontend.go index d750b4f..af8dfc8 100644 --- a/contrib/backends/srndv2/src/srnd/frontend.go +++ b/contrib/backends/srndv2/src/srnd/frontend.go @@ -39,4 +39,6 @@ type Frontend interface { // regenerate on mod event RegenOnModEvent(newsgroup, msgid, root string, page int) + + GetCacheHandler() CacheHandler } diff --git a/contrib/backends/srndv2/src/srnd/frontend_http.go b/contrib/backends/srndv2/src/srnd/frontend_http.go index f813104..ed057b7 100644 --- a/contrib/backends/srndv2/src/srnd/frontend_http.go +++ b/contrib/backends/srndv2/src/srnd/frontend_http.go @@ -440,7 +440,7 @@ func (self *httpFrontend) new_captcha_json(wr http.ResponseWriter, r *http.Reque func (self *httpFrontend) handle_newboard(wr http.ResponseWriter, r *http.Request) { param := make(map[string]interface{}) param["prefix"] = self.prefix - io.WriteString(wr, template.renderTemplate("newboard.mustache", param)) + io.WriteString(wr, template.renderTemplate("newboard.mustache", param, self.cache.GetHandler().GetI18N(r))) } // handle new post via http request for a board @@ -616,7 +616,7 @@ func (self *httpFrontend) handle_postform(wr http.ResponseWriter, r *http.Reques resp_map["prefix"] = self.prefix resp_map["redirect_url"] = url resp_map["reason"] = "captcha incorrect" - io.WriteString(wr, template.renderTemplate("post_fail.mustache", resp_map)) + io.WriteString(wr, template.renderTemplate("post_fail.mustache", resp_map, self.cache.GetHandler().GetI18N(r))) } return } @@ -650,7 +650,7 @@ func (self *httpFrontend) handle_postform(wr http.ResponseWriter, r *http.Reques resp_map["reason"] = err.Error() resp_map["prefix"] = self.prefix resp_map["redirect_url"] = url - io.WriteString(wr, template.renderTemplate("post_fail.mustache", resp_map)) + io.WriteString(wr, template.renderTemplate("post_fail.mustache", resp_map, self.cache.GetHandler().GetI18N(r))) } } @@ -664,7 +664,7 @@ func (self *httpFrontend) handle_postform(wr http.ResponseWriter, r *http.Reques if sendJson { json.NewEncoder(wr).Encode(map[string]interface{}{"message_id": nntp.MessageID(), "url": url, "error": nil}) } else { - io.WriteString(wr, template.renderTemplate("post_success.mustache", map[string]interface{}{"prefix": self.prefix, "message_id": nntp.MessageID(), "redirect_url": url})) + template.writeTemplate("post_success.mustache", map[string]interface{}{"prefix": self.prefix, "message_id": nntp.MessageID(), "redirect_url": url}, wr, self.cache.GetHandler().GetI18N(r)) } } self.handle_postRequest(pr, b, e, s, self.enableBoardCreation) @@ -1473,7 +1473,7 @@ func (self *httpFrontend) Mainloop() { m.Path("/live").HandlerFunc(self.handle_liveui).Methods("GET") // live ui page m.Path("/livechan/").HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - template.writeTemplate("live.mustache", map[string]interface{}{"prefix": self.prefix}, w) + template.writeTemplate("live.mustache", map[string]interface{}{"prefix": self.prefix}, w, self.cache.GetHandler().GetI18N(r)) })).Methods("GET", "HEAD") // live ui api endpoint m.Path("/livechan/api/{meth}").HandlerFunc(self.handle_liveapi).Methods("GET", "POST") @@ -1521,6 +1521,10 @@ func (self *httpFrontend) RegenOnModEvent(newsgroup, msgid, root string, page in self.cache.RegenOnModEvent(newsgroup, msgid, root, page) } +func (self *httpFrontend) GetCacheHandler() CacheHandler { + return self.cache.GetHandler() +} + // create a new http based frontend func NewHTTPFrontend(daemon *NNTPDaemon, cache CacheInterface, config map[string]string, url string) Frontend { template.Minimize = config["minimize_html"] == "1" diff --git a/contrib/backends/srndv2/src/srnd/frontend_multi.go b/contrib/backends/srndv2/src/srnd/frontend_multi.go index aae0e1f..4272b31 100644 --- a/contrib/backends/srndv2/src/srnd/frontend_multi.go +++ b/contrib/backends/srndv2/src/srnd/frontend_multi.go @@ -10,6 +10,11 @@ type multiFrontend struct { frontends []Frontend } +func (self multiFrontend) GetCacheHandler() CacheHandler { + // TODO: fixme :^) + return nil +} + func (self multiFrontend) AllowNewsgroup(newsgroup string) bool { return true } diff --git a/contrib/backends/srndv2/src/srnd/i18n.go b/contrib/backends/srndv2/src/srnd/i18n.go index 2a81c97..e540e78 100644 --- a/contrib/backends/srndv2/src/srnd/i18n.go +++ b/contrib/backends/srndv2/src/srnd/i18n.go @@ -9,27 +9,37 @@ import ( "strings" ) -type i18n struct { +type I18N struct { locale language.Tag // loaded translations - translations map[string]string + Translations map[string]string // loaded formats - formats map[string]string + Formats map[string]string // root directory for translations translation_dir string + // name of locale + name string } -var i18nProvider *i18n = nil +var I18nProvider *I18N = nil //Read all .ini files in dir, where the filenames are BCP 47 tags //Use the language matcher to get the best match for the locale preference func InitI18n(locale, dir string) { - pref := language.Make(locale) // falls back to en-US on parse error - log.Println("using locale", pref) - files, err := ioutil.ReadDir(dir) + var err error + I18nProvider, err = NewI18n(locale, dir) if err != nil { log.Fatal(err) } +} + +func NewI18n(locale, dir string) (*I18N, error) { + log.Println("get locale", locale) + pref := language.Make(locale) // falls back to en-US on parse error + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } serverLangs := make([]language.Tag, 1) serverLangs[0] = language.AmericanEnglish // en-US fallback @@ -48,39 +58,40 @@ func InitI18n(locale, dir string) { fname := filepath.Join(dir, tag.String()+".ini") conf, err := configparser.Read(fname) if err != nil { - log.Fatal("cannot read translation file for", tag.String(), err) + return nil, err } formats, err := conf.Section("formats") if err != nil { - log.Fatal("Cannot read formats sections in translations for", tag.String(), err) + return nil, err } translations, err := conf.Section("strings") if err != nil { - log.Fatal("Cannot read strings sections in translations for", tag.String(), err) + return nil, err } - i18nProvider = &i18n{ + return &I18N{ + name: locale, translation_dir: dir, - formats: formats.Options(), - translations: translations.Options(), + Formats: formats.Options(), + Translations: translations.Options(), locale: tag, - } + }, nil } -func (self *i18n) Translate(key string) string { - return self.translations[key] +func (self *I18N) Translate(key string) string { + return self.Translations[key] } -func (self *i18n) Format(key string) string { - return self.formats[key] +func (self *I18N) Format(key string) string { + return self.Formats[key] } //this signature seems to be expected by mustache -func (self *i18n) Translations() (map[string]string, error) { - return self.translations, nil -} +//func (self *I18N) Translations() (map[string]string, error) { +// return self._translations, nil +//} -func (self *i18n) Formats() (map[string]string, error) { - return self.formats, nil -} +//func (self *I18N) Formats() (map[string]string, error) { +// return self.formats, nil +//} diff --git a/contrib/backends/srndv2/src/srnd/installer.go b/contrib/backends/srndv2/src/srnd/installer.go index 5512ec8..4a9a40a 100644 --- a/contrib/backends/srndv2/src/srnd/installer.go +++ b/contrib/backends/srndv2/src/srnd/installer.go @@ -316,7 +316,7 @@ func (self *Installer) HandleInstallerGet(wr http.ResponseWriter, r *http.Reques wr.WriteHeader(404) } else { m := self.currentNode.model(self.currentNode, self.currentErr, self.config) - template.writeTemplate(self.currentNode.templateName, m, wr) + template.writeTemplate(self.currentNode.templateName, m, wr, nil) } } diff --git a/contrib/backends/srndv2/src/srnd/mod_http.go b/contrib/backends/srndv2/src/srnd/mod_http.go index 4fca5ce..f67a6eb 100644 --- a/contrib/backends/srndv2/src/srnd/mod_http.go +++ b/contrib/backends/srndv2/src/srnd/mod_http.go @@ -32,10 +32,11 @@ type httpModUI struct { store *sessions.CookieStore prefix string mod_prefix string + cache CacheHandler } func createHttpModUI(frontend *httpFrontend) httpModUI { - return httpModUI{frontend.regenAll, frontend.Regen, frontend.RegenerateBoard, frontend.deleteThreadMarkup, frontend.deleteBoardMarkup, make(chan NNTPMessage), frontend.daemon, frontend.daemon.store, frontend.store, frontend.prefix, frontend.prefix + "mod/"} + return httpModUI{frontend.regenAll, frontend.Regen, frontend.RegenerateBoard, frontend.deleteThreadMarkup, frontend.deleteBoardMarkup, make(chan NNTPMessage), frontend.daemon, frontend.daemon.store, frontend.store, frontend.prefix, frontend.prefix + "mod/", frontend.GetCacheHandler()} } @@ -474,7 +475,8 @@ func (self httpModUI) writeTemplateParam(wr http.ResponseWriter, r *http.Request param[csrf.TemplateTag] = csrf.TemplateField(r) param["prefix"] = self.prefix param["mod_prefix"] = self.mod_prefix - io.WriteString(wr, template.renderTemplate(name, param)) + i18n := self.cache.GetI18N(r) + io.WriteString(wr, template.renderTemplate(name, param, i18n)) } // do a function as authenticated diff --git a/contrib/backends/srndv2/src/srnd/model.go b/contrib/backends/srndv2/src/srnd/model.go index 844cb64..4a52b21 100644 --- a/contrib/backends/srndv2/src/srnd/model.go +++ b/contrib/backends/srndv2/src/srnd/model.go @@ -19,6 +19,9 @@ type BaseModel interface { // to json string JSON() string + + // inject I18N + I18N(i *I18N) } type ThumbInfo struct { @@ -253,11 +256,11 @@ func (p *postsGraphRow) GraphRune(r string) (s string) { } func (p postsGraphRow) Date() (s string) { - return p.day.Format(i18nProvider.Format("month_date_format")) + return p.day.Format(I18nProvider.Format("month_date_format")) } func (p postsGraphRow) Day() (s string) { - return p.day.Format(i18nProvider.Format("day_date_format")) + return p.day.Format(I18nProvider.Format("day_date_format")) } func (p postsGraphRow) RegularGraph() (s string) { diff --git a/contrib/backends/srndv2/src/srnd/model_mem.go b/contrib/backends/srndv2/src/srnd/model_mem.go index 258e425..809c909 100644 --- a/contrib/backends/srndv2/src/srnd/model_mem.go +++ b/contrib/backends/srndv2/src/srnd/model_mem.go @@ -19,6 +19,7 @@ type catalogModel struct { prefix string board string threads []CatalogItemModel + _i18n *I18N } type catalogItemModel struct { @@ -27,6 +28,35 @@ type catalogItemModel struct { op PostModel } +func (self *catalogModel) I18N(i *I18N) { + self._i18n = i +} + +func (self *post) I18N(i *I18N) { + self._i18n = i + for idx := range self.Files { + self.Files[idx].I18N(i) + } +} + +func (self *thread) I18N(i *I18N) { + self._i18n = i + for idx := range self.Posts { + self.Posts[idx].I18N(i) + } +} + +func (self *boardModel) I18N(i *I18N) { + self._i18n = i + for idx := range self.threads { + self.threads[idx].I18N(i) + } +} + +func (self *attachment) I18N(i *I18N) { + self._i18n = i +} + func (self *catalogModel) Navbar() string { param := make(map[string]interface{}) param["name"] = fmt.Sprintf("Catalog for %s", self.board) @@ -38,7 +68,7 @@ func (self *catalogModel) Navbar() string { }) param["prefix"] = self.prefix param["links"] = links - return template.renderTemplate("navbar.mustache", param) + return template.renderTemplate("navbar.mustache", param, self._i18n) } func (self *catalogModel) MarshalJSON() (b []byte, err error) { @@ -78,6 +108,7 @@ func (self *catalogItemModel) ReplyCount() string { } type boardModel struct { + _i18n *I18N allowFiles bool frontend string prefix string @@ -131,7 +162,7 @@ func (self *boardModel) Navbar() string { param["frontend"] = self.frontend param["prefix"] = self.prefix param["links"] = self.PageList() - return template.renderTemplate("navbar.mustache", param) + return template.renderTemplate("navbar.mustache", param, self._i18n) } func (self *boardModel) Board() string { @@ -207,6 +238,7 @@ func (self *boardModel) Update(db Database) { } type post struct { + _i18n *I18N truncated bool prefix string board string @@ -286,6 +318,7 @@ func (self *post) JSON() string { } type attachment struct { + _i18n *I18N prefix string Path string Name string @@ -405,7 +438,11 @@ func (self *post) OP() bool { } func (self *post) Date() string { - return time.Unix(self.Posted, 0).Format(i18nProvider.Format("full_date_format")) + i18n := self._i18n + if i18n == nil { + i18n = I18nProvider + } + return time.Unix(self.Posted, 0).Format(i18n.Format("full_date_format")) } func (self *post) DateRFC() string { @@ -479,7 +516,7 @@ func (self *post) SetIndex(idx int) { func (self *post) RenderPost() string { param := make(map[string]interface{}) param["post"] = self - return template.renderTemplate("post.mustache", param) + return template.renderTemplate("post.mustache", param, self._i18n) } func (self *post) RenderTruncatedPost() string { @@ -505,6 +542,7 @@ func (self *post) Truncate() PostModel { } return &post{ + _i18n: self._i18n, truncated: true, prefix: self.prefix, board: self.board, @@ -541,6 +579,7 @@ func (self *post) RenderBody() string { } type thread struct { + _i18n *I18N allowFiles bool prefix string links []LinkModel @@ -583,7 +622,7 @@ func (self *thread) Navbar() string { param["frontend"] = self.Board() param["links"] = self.links param["prefix"] = self.prefix - return template.renderTemplate("navbar.mustache", param) + return template.renderTemplate("navbar.mustache", param, self._i18n) } func (self *thread) Board() string { @@ -658,6 +697,7 @@ func (self *thread) Truncate() ThreadModel { trunc := 5 if len(self.Posts) > trunc { t := &thread{ + _i18n: self._i18n, allowFiles: self.allowFiles, links: self.links, Posts: append([]PostModel{self.Posts[0]}, self.Posts[len(self.Posts)-trunc:]...), diff --git a/contrib/backends/srndv2/src/srnd/null_cache.go b/contrib/backends/srndv2/src/srnd/null_cache.go index a0ab710..cc9d49d 100644 --- a/contrib/backends/srndv2/src/srnd/null_cache.go +++ b/contrib/backends/srndv2/src/srnd/null_cache.go @@ -3,10 +3,12 @@ package srnd import ( "encoding/json" "io/ioutil" + "log" "net/http" "path/filepath" "strconv" "strings" + "sync" ) type NullCache struct { @@ -19,9 +21,36 @@ type nullHandler struct { requireCaptcha bool name string prefix string + translations string + i18n map[string]*I18N + access sync.Mutex +} + +func (self *nullHandler) GetI18N(r *http.Request) *I18N { + lang := r.URL.Query().Get("lang") + if lang == "" { + lang = I18nProvider.name + } + self.access.Lock() + i, ok := self.i18n[lang] + if !ok { + var err error + i, err = NewI18n(lang, self.translations) + if err != nil { + log.Println(err) + } + if i != nil { + self.i18n[lang] = i + } + } + self.access.Unlock() + return i } func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + + i18n := self.GetI18N(r) + path := r.URL.Path _, file := filepath.Split(path) @@ -38,7 +67,7 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { goto notfound } - template.genThread(self.attachments, self.requireCaptcha, msg, self.prefix, self.name, w, self.database, isjson) + template.genThread(self.attachments, self.requireCaptcha, msg, self.prefix, self.name, w, self.database, isjson, i18n) return } else { goto notfound @@ -46,7 +75,7 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if strings.Trim(path, "/") == "overboard" { // generate ukko aka overboard - template.genUkko(self.prefix, self.name, w, self.database, isjson) + template.genUkko(self.prefix, self.name, w, self.database, isjson, i18n) return } @@ -76,7 +105,7 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if page >= int(pages) { goto notfound } - template.genBoardPage(self.attachments, self.requireCaptcha, self.prefix, self.name, group, page, w, self.database, isjson) + template.genBoardPage(self.attachments, self.requireCaptcha, self.prefix, self.name, group, page, w, self.database, isjson, i18n) return } @@ -90,12 +119,12 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { goto notfound } } - template.genUkkoPaginated(self.prefix, self.name, w, self.database, page, isjson) + template.genUkkoPaginated(self.prefix, self.name, w, self.database, page, isjson, i18n) return } if len(file) == 0 || file == "index.html" { - template.genFrontPage(10, self.prefix, self.name, w, ioutil.Discard, self.database) + template.genFrontPage(10, self.prefix, self.name, w, ioutil.Discard, self.database, i18n) return } @@ -104,11 +133,11 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { goto notfound } if strings.HasPrefix(file, "history.html") { - template.genGraphs(self.prefix, w, self.database) + template.genGraphs(self.prefix, w, self.database, i18n) return } if strings.HasPrefix(file, "boards.html") { - template.genBoardList(self.prefix, self.name, w, self.database) + template.genBoardList(self.prefix, self.name, w, self.database, i18n) return } @@ -119,17 +148,17 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if strings.HasPrefix(file, "ukko.html") { - template.genUkko(self.prefix, self.name, w, self.database, false) + template.genUkko(self.prefix, self.name, w, self.database, false, i18n) return } if strings.HasPrefix(file, "ukko.json") { - template.genUkko(self.prefix, self.name, w, self.database, true) + template.genUkko(self.prefix, self.name, w, self.database, true, i18n) return } if strings.HasPrefix(file, "ukko-") { page := getUkkoPage(file) - template.genUkkoPaginated(self.prefix, self.name, w, self.database, page, isjson) + template.genUkkoPaginated(self.prefix, self.name, w, self.database, page, isjson, i18n) return } if strings.HasPrefix(file, "thread-") { @@ -147,7 +176,7 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { goto notfound } - template.genThread(self.attachments, self.requireCaptcha, msg, self.prefix, self.name, w, self.database, isjson) + template.genThread(self.attachments, self.requireCaptcha, msg, self.prefix, self.name, w, self.database, isjson, i18n) return } if strings.HasPrefix(file, "catalog-") { @@ -159,7 +188,7 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !hasgroup { goto notfound } - template.genCatalog(self.prefix, self.name, group, w, self.database) + template.genCatalog(self.prefix, self.name, group, w, self.database, i18n) return } else { group, page := getGroupAndPage(file) @@ -174,12 +203,12 @@ func (self *nullHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if page >= int(pages) { goto notfound } - template.genBoardPage(self.attachments, self.requireCaptcha, self.prefix, self.name, group, page, w, self.database, isjson) + template.genBoardPage(self.attachments, self.requireCaptcha, self.prefix, self.name, group, page, w, self.database, isjson, i18n) return } notfound: - template.renderNotFound(w, r, self.prefix, self.name) + template.renderNotFound(w, r, self.prefix, self.name, i18n) } func (self *NullCache) DeleteBoardMarkup(group string) { @@ -214,7 +243,7 @@ func (self *NullCache) Start() { func (self *NullCache) Regen(msg ArticleEntry) { } -func (self *NullCache) GetHandler() http.Handler { +func (self *NullCache) GetHandler() CacheHandler { return self.handler } @@ -222,7 +251,7 @@ func (self *NullCache) Close() { //nothig to do } -func NewNullCache(prefix, webroot, name string, attachments bool, db Database, store ArticleStore) CacheInterface { +func NewNullCache(prefix, webroot, name, translations string, attachments bool, db Database, store ArticleStore) CacheInterface { cache := new(NullCache) cache.handler = &nullHandler{ prefix: prefix, @@ -230,6 +259,8 @@ func NewNullCache(prefix, webroot, name string, attachments bool, db Database, s attachments: attachments, requireCaptcha: true, database: db, + i18n: make(map[string]*I18N), + translations: translations, } return cache diff --git a/contrib/backends/srndv2/src/srnd/templates.go b/contrib/backends/srndv2/src/srnd/templates.go index 8357f05..104ad35 100644 --- a/contrib/backends/srndv2/src/srnd/templates.go +++ b/contrib/backends/srndv2/src/srnd/templates.go @@ -173,9 +173,12 @@ func (self *templateEngine) getTemplate(name string) (t string) { } // render a template, self explanitory -func (self *templateEngine) renderTemplate(name string, obj map[string]interface{}) string { +func (self *templateEngine) renderTemplate(name string, obj map[string]interface{}, i18n *I18N) string { t := self.getTemplate(name) - obj["i18n"] = i18nProvider + if i18n == nil { + i18n = I18nProvider + } + obj["i18n"] = i18n s, err := mustache.Render(t, obj) if err == nil { return s @@ -185,8 +188,8 @@ func (self *templateEngine) renderTemplate(name string, obj map[string]interface } // write a template to an io.Writer -func (self *templateEngine) writeTemplate(name string, obj map[string]interface{}, wr io.Writer) (err error) { - str := self.renderTemplate(name, obj) +func (self *templateEngine) writeTemplate(name string, obj map[string]interface{}, wr io.Writer, i18n *I18N) (err error) { + str := self.renderTemplate(name, obj, i18n) var r io.Reader r = bytes.NewBufferString(str) if self.Minimize { @@ -222,47 +225,49 @@ func (self *templateEngine) obtainBoard(prefix, frontend, group string, db Datab return } -func (self *templateEngine) genCatalog(prefix, frontend, group string, wr io.Writer, db Database) { +func (self *templateEngine) genCatalog(prefix, frontend, group string, wr io.Writer, db Database, i18n *I18N) { board := self.obtainBoard(prefix, frontend, group, db) catalog := new(catalogModel) catalog.prefix = prefix catalog.frontend = frontend catalog.board = group - + catalog.I18N(i18n) for page, bm := range board { for _, th := range bm.Threads() { th.Update(db) catalog.threads = append(catalog.threads, &catalogItemModel{op: th.OP(), page: page, replycount: len(th.Replies())}) } } - self.writeTemplate("catalog.mustache", map[string]interface{}{"board": catalog}, wr) + self.writeTemplate("catalog.mustache", map[string]interface{}{"board": catalog}, wr, i18n) } // generate a board page -func (self *templateEngine) genBoardPage(allowFiles, requireCaptcha bool, prefix, frontend, newsgroup string, page int, wr io.Writer, db Database, json bool) { +func (self *templateEngine) genBoardPage(allowFiles, requireCaptcha bool, prefix, frontend, newsgroup string, page int, wr io.Writer, db Database, json bool, i18n *I18N) { // get the board page model perpage, _ := db.GetThreadsPerPage(newsgroup) boardPage := db.GetGroupForPage(prefix, frontend, newsgroup, page, int(perpage)) boardPage.Update(db) + boardPage.I18N(i18n) // render it if json { self.renderJSON(wr, boardPage) } else { - form := renderPostForm(prefix, newsgroup, "", allowFiles, requireCaptcha) - self.writeTemplate("board.mustache", map[string]interface{}{"board": boardPage, "page": page, "form": form}, wr) + form := renderPostForm(prefix, newsgroup, "", allowFiles, requireCaptcha, i18n) + self.writeTemplate("board.mustache", map[string]interface{}{"board": boardPage, "page": page, "form": form}, wr, i18n) } } -func (self *templateEngine) genUkko(prefix, frontend string, wr io.Writer, database Database, json bool) { - self.genUkkoPaginated(prefix, frontend, wr, database, 0, json) +func (self *templateEngine) genUkko(prefix, frontend string, wr io.Writer, database Database, json bool, i18n *I18N) { + self.genUkkoPaginated(prefix, frontend, wr, database, 0, json, i18n) } -func (self *templateEngine) genUkkoPaginated(prefix, frontend string, wr io.Writer, database Database, page int, json bool) { +func (self *templateEngine) genUkkoPaginated(prefix, frontend string, wr io.Writer, database Database, page int, json bool, i18n *I18N) { var threads []ThreadModel for _, article := range database.GetLastBumpedThreadsPaginated("", 10, page*10) { root := article[0] thread, err := database.GetThreadModel(prefix, root) if err == nil { + thread.I18N(i18n) threads = append(threads, thread) } } @@ -282,13 +287,13 @@ func (self *templateEngine) genUkkoPaginated(prefix, frontend string, wr io.Writ navbar["frontend"] = frontend navbar["prefix"] = prefix // inject navbar - obj["navbar"] = self.renderTemplate("navbar.mustache", navbar) + obj["navbar"] = self.renderTemplate("navbar.mustache", navbar, i18n) // render - self.writeTemplate("ukko.mustache", obj, wr) + self.writeTemplate("ukko.mustache", obj, wr, i18n) } } -func (self *templateEngine) genThread(allowFiles, requireCaptcha bool, root ArticleEntry, prefix, frontend string, wr io.Writer, db Database, json bool) { +func (self *templateEngine) genThread(allowFiles, requireCaptcha bool, root ArticleEntry, prefix, frontend string, wr io.Writer, db Database, json bool, i18n *I18N) { newsgroup := root.Newsgroup() msgid := root.MessageID() @@ -303,8 +308,9 @@ func (self *templateEngine) genThread(allowFiles, requireCaptcha bool, root Arti if json { self.renderJSON(wr, t) } else { - form := renderPostForm(prefix, newsgroup, msgid, allowFiles, requireCaptcha) - self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": map[string]interface{}{"Name": newsgroup, "Frontend": frontend, "AllowFiles": allowFiles}, "form": form, "prefix": prefix}, wr) + t.I18N(i18n) + form := renderPostForm(prefix, newsgroup, msgid, allowFiles, requireCaptcha, i18n) + self.writeTemplate("thread.mustache", map[string]interface{}{"thread": t, "board": map[string]interface{}{"Name": newsgroup, "Frontend": frontend, "AllowFiles": allowFiles}, "form": form, "prefix": prefix}, wr, i18n) } } else { log.Println("templates: error getting thread for ", msgid, err.Error()) @@ -368,18 +374,18 @@ func (self *templateEngine) changeTemplateDir(dirname string) { func (self *templateEngine) createNotFoundHandler(prefix, frontend string) (h http.Handler) { h = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - self.renderNotFound(w, r, prefix, frontend) + self.renderNotFound(w, r, prefix, frontend, nil) }) return } // default renderer of 404 pages -func (self *templateEngine) renderNotFound(wr http.ResponseWriter, r *http.Request, prefix, frontend string) { +func (self *templateEngine) renderNotFound(wr http.ResponseWriter, r *http.Request, prefix, frontend string, i18n *I18N) { wr.WriteHeader(404) opts := make(map[string]interface{}) opts["prefix"] = prefix opts["frontend"] = frontend - self.writeTemplate("404.mustache", opts, wr) + self.writeTemplate("404.mustache", opts, wr, i18n) } func newTemplateEngine(dir string) *templateEngine { @@ -399,17 +405,17 @@ func (self *templateEngine) findLink(prefix, hash string) (url string) { var template = newTemplateEngine(defaultTemplateDir()) -func renderPostForm(prefix, board, op_msg_id string, files, captcha bool) string { +func renderPostForm(prefix, board, op_msg_id string, files, captcha bool, i18n *I18N) string { url := prefix + "post/" + board button := "New Thread" if op_msg_id != "" { button = "Reply" } - return template.renderTemplate("postform.mustache", map[string]interface{}{"post_url": url, "reference": op_msg_id, "button": button, "files": files, "prefix": prefix, "DisableCaptcha": !captcha}) + return template.renderTemplate("postform.mustache", map[string]interface{}{"post_url": url, "reference": op_msg_id, "button": button, "files": files, "prefix": prefix, "DisableCaptcha": !captcha}, i18n) } // generate misc graphs -func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database) { +func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database, i18n *I18N) { // // begin gen history.html @@ -433,7 +439,7 @@ func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database) } sort.Sort(all_posts) - _, err := io.WriteString(wr, self.renderTemplate("graph_history.mustache", map[string]interface{}{"history": all_posts})) + _, err := io.WriteString(wr, self.renderTemplate("graph_history.mustache", map[string]interface{}{"history": all_posts}, i18n)) if err != nil { log.Println("error writing history graph", err) } @@ -444,7 +450,7 @@ func (self *templateEngine) genGraphs(prefix string, wr io.Writer, db Database) } -func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db Database) { +func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db Database, i18n *I18N) { // the graph for the front page var frontpage_graph boardPageRows @@ -470,22 +476,26 @@ func (self *templateEngine) genBoardList(prefix, name string, wr io.Writer, db D } sort.Sort(frontpage_graph) param["graph"] = frontpage_graph - _, err := io.WriteString(wr, self.renderTemplate("boardlist.mustache", param)) + _, err := io.WriteString(wr, self.renderTemplate("boardlist.mustache", param, i18n)) if err != nil { log.Println("error writing board list page", err) } } // generate front page -func (self *templateEngine) genFrontPage(top_count int, prefix, frontend_name string, indexwr, boardswr io.Writer, db Database) { +func (self *templateEngine) genFrontPage(top_count int, prefix, frontend_name string, indexwr, boardswr io.Writer, db Database, i18n *I18N) { models := db.GetLastPostedPostModels(prefix, 20) + for idx := range models { + models[idx].I18N(i18n) + } + wr := indexwr param := make(map[string]interface{}) - param["overview"] = self.renderTemplate("overview.mustache", map[string]interface{}{"overview": overviewModel(models)}) + param["overview"] = self.renderTemplate("overview.mustache", map[string]interface{}{"overview": overviewModel(models)}, i18n) /* sort.Sort(posts_graph) param["postsgraph"] = self.renderTemplate("posts_graph.mustache", map[string]interface{}{"graph": posts_graph}) @@ -500,9 +510,9 @@ func (self *templateEngine) genFrontPage(top_count int, prefix, frontend_name st param["totalposts"] = db.ArticleCount() param["prefix"] = prefix // render and inject navbar - param["navbar"] = self.renderTemplate("navbar.mustache", map[string]interface{}{"name": "Front Page", "frontend": frontend_name, "prefix": prefix}) + param["navbar"] = self.renderTemplate("navbar.mustache", map[string]interface{}{"name": "Front Page", "frontend": frontend_name, "prefix": prefix}, i18n) - _, err := io.WriteString(wr, self.renderTemplate("frontpage.mustache", param)) + _, err := io.WriteString(wr, self.renderTemplate("frontpage.mustache", param, i18n)) if err != nil { log.Println("error writing front page", err) } diff --git a/contrib/backends/srndv2/src/srnd/templates_test.go b/contrib/backends/srndv2/src/srnd/templates_test.go index 451188e..2c170a9 100644 --- a/contrib/backends/srndv2/src/srnd/templates_test.go +++ b/contrib/backends/srndv2/src/srnd/templates_test.go @@ -18,7 +18,7 @@ func BenchmarkRenderBoardPage(b *testing.B) { for pb.Next() { wr, err := os.Create("boardpage.html") if err == nil { - template.genBoardPage(true, true, "prefix", "test", "overchan.random", 0, wr, db, false) + template.genBoardPage(true, true, "prefix", "test", "overchan.random", 0, wr, db, false, nil) } else { log.Println("did not write", "boardpage.html", err) } @@ -35,7 +35,7 @@ func BenchmarkRenderThread(b *testing.B) { for pb.Next() { wr, err := os.Create("thread.html") if err == nil { - template.genThread(true, true, ArticleEntry{"", "overchan.random"}, "prefix", "frontend", wr, db, false) + template.genThread(true, true, ArticleEntry{"", "overchan.random"}, "prefix", "frontend", wr, db, false, nil) } else { log.Println("did not write", "thread.html", err) } diff --git a/contrib/backends/srndv2/src/srnd/varnish_cache.go b/contrib/backends/srndv2/src/srnd/varnish_cache.go index be870ab..b250f7d 100644 --- a/contrib/backends/srndv2/src/srnd/varnish_cache.go +++ b/contrib/backends/srndv2/src/srnd/varnish_cache.go @@ -112,7 +112,7 @@ func (self *VarnishCache) Regen(msg ArticleEntry) { self.DeleteThreadMarkup(msg.MessageID()) } -func (self *VarnishCache) GetHandler() http.Handler { +func (self *VarnishCache) GetHandler() CacheHandler { return self.handler } @@ -124,7 +124,7 @@ func (self *VarnishCache) SetRequireCaptcha(required bool) { self.handler.requireCaptcha = required } -func NewVarnishCache(varnish_url, bind_addr, prefix, webroot, name string, attachments bool, db Database, store ArticleStore) CacheInterface { +func NewVarnishCache(varnish_url, bind_addr, prefix, webroot, name, translations string, attachments bool, db Database, store ArticleStore) CacheInterface { cache := new(VarnishCache) cache.threadsRegenChan = make(chan ArticleEntry) local_addr, err := net.ResolveTCPAddr("tcp", bind_addr) @@ -150,6 +150,8 @@ func NewVarnishCache(varnish_url, bind_addr, prefix, webroot, name string, attac attachments: attachments, database: db, requireCaptcha: true, + i18n: make(map[string]*I18N), + translations: translations, } cache.varnish_url = varnish_url return cache