diff --git a/daemon/daemon.go b/daemon/daemon.go index 9e1576d4..f1001593 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -12,6 +12,7 @@ import ( "time" "github.com/miniflux/miniflux/config" + "github.com/miniflux/miniflux/locale" "github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/reader/feed" "github.com/miniflux/miniflux/scheduler" @@ -26,9 +27,10 @@ func Run(cfg *config.Config, store *storage.Storage) { signal.Notify(stop, os.Interrupt) signal.Notify(stop, syscall.SIGTERM) - feedHandler := feed.NewFeedHandler(store) + translator := locale.Load() + feedHandler := feed.NewFeedHandler(store, translator) pool := scheduler.NewWorkerPool(feedHandler, cfg.WorkerPoolSize()) - server := newServer(cfg, store, pool, feedHandler) + server := newServer(cfg, store, pool, feedHandler, translator) scheduler.NewFeedScheduler( store, diff --git a/daemon/routes.go b/daemon/routes.go index 35cec9eb..bb141f79 100644 --- a/daemon/routes.go +++ b/daemon/routes.go @@ -23,9 +23,8 @@ import ( "github.com/gorilla/mux" ) -func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool) *mux.Router { +func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool, translator *locale.Translator) *mux.Router { router := mux.NewRouter() - translator := locale.Load() templateEngine := template.NewEngine(cfg, router, translator) apiController := api.NewController(store, feedHandler) diff --git a/daemon/server.go b/daemon/server.go index 4e8ff32a..6ac49bda 100644 --- a/daemon/server.go +++ b/daemon/server.go @@ -10,6 +10,7 @@ import ( "time" "github.com/miniflux/miniflux/config" + "github.com/miniflux/miniflux/locale" "github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/reader/feed" "github.com/miniflux/miniflux/scheduler" @@ -18,7 +19,7 @@ import ( "golang.org/x/crypto/acme/autocert" ) -func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler) *http.Server { +func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, translator *locale.Translator) *http.Server { certFile := cfg.CertFile() keyFile := cfg.KeyFile() certDomain := cfg.CertDomain() @@ -28,7 +29,7 @@ func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.Worke WriteTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, Addr: cfg.ListenAddr(), - Handler: routes(cfg, store, feedHandler, pool), + Handler: routes(cfg, store, feedHandler, pool, translator), } if certDomain != "" && certCache != "" { diff --git a/errors/errors.go b/errors/errors.go index 71805ade..e7af7b10 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -27,6 +27,6 @@ func (l LocalizedError) Localize(translation *locale.Language) string { } // NewLocalizedError returns a new LocalizedError. -func NewLocalizedError(message string, args ...interface{}) LocalizedError { - return LocalizedError{message: message, args: args} +func NewLocalizedError(message string, args ...interface{}) *LocalizedError { + return &LocalizedError{message: message, args: args} } diff --git a/reader/atom/parser.go b/reader/atom/parser.go index 94b50c74..9a41dac0 100644 --- a/reader/atom/parser.go +++ b/reader/atom/parser.go @@ -14,7 +14,7 @@ import ( ) // Parse returns a normalized feed struct from a Atom feed. -func Parse(data io.Reader) (*model.Feed, error) { +func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { atomFeed := new(atomFeed) decoder := xml.NewDecoder(data) decoder.CharsetReader = encoding.CharsetReader diff --git a/reader/atom/parser_test.go b/reader/atom/parser_test.go index ec9186c4..bfd3d3a2 100644 --- a/reader/atom/parser_test.go +++ b/reader/atom/parser_test.go @@ -8,8 +8,6 @@ import ( "bytes" "testing" "time" - - "github.com/miniflux/miniflux/errors" ) func TestParseAtomSample(t *testing.T) { @@ -430,8 +428,4 @@ func TestParseInvalidXml(t *testing.T) { if err == nil { t.Error("Parse should returns an error") } - - if _, ok := err.(errors.LocalizedError); !ok { - t.Error("The error returned must be a LocalizedError") - } } diff --git a/reader/feed/handler.go b/reader/feed/handler.go index d6ac84ed..516593c8 100644 --- a/reader/feed/handler.go +++ b/reader/feed/handler.go @@ -10,6 +10,7 @@ import ( "github.com/miniflux/miniflux/errors" "github.com/miniflux/miniflux/http" + "github.com/miniflux/miniflux/locale" "github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/model" "github.com/miniflux/miniflux/reader/icon" @@ -30,7 +31,8 @@ var ( // Handler contains all the logic to create and refresh feeds. type Handler struct { - store *storage.Storage + store *storage.Storage + translator *locale.Translator } // CreateFeed fetch, parse and store a new feed. @@ -44,7 +46,7 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool) client := http.NewClient(url) response, err := client.Get() if err != nil { - if _, ok := err.(errors.LocalizedError); ok { + if _, ok := err.(*errors.LocalizedError); ok { return nil, err } return nil, errors.NewLocalizedError(errRequestFailed, err) @@ -68,9 +70,9 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool) return nil, errors.NewLocalizedError(errEncoding, err) } - subscription, err := parseFeed(body) - if err != nil { - return nil, err + subscription, feedErr := parseFeed(body) + if feedErr != nil { + return nil, feedErr } feedProcessor := processor.NewFeedProcessor(userID, h.store, subscription) @@ -110,6 +112,13 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool) // RefreshFeed fetch and update a feed if necessary. func (h *Handler) RefreshFeed(userID, feedID int64) error { defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Handler:RefreshFeed] feedID=%d", feedID)) + userLanguage, err := h.store.UserLanguage(userID) + if err != nil { + logger.Error("[Handler:RefreshFeed] %v", err) + userLanguage = "en_US" + } + + currentLanguage := h.translator.GetLanguage(userLanguage) originalFeed, err := h.store.FeedByID(userID, feedID) if err != nil { @@ -124,14 +133,14 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { response, err := client.Get() if err != nil { var customErr errors.LocalizedError - if lerr, ok := err.(errors.LocalizedError); ok { - customErr = lerr + if lerr, ok := err.(*errors.LocalizedError); ok { + customErr = *lerr } else { - customErr = errors.NewLocalizedError(errRequestFailed, err) + customErr = *errors.NewLocalizedError(errRequestFailed, err) } originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = customErr.Error() + originalFeed.ParsingErrorMsg = customErr.Localize(currentLanguage) h.store.UpdateFeed(originalFeed) return customErr } @@ -141,7 +150,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { if response.HasServerFailure() { err := errors.NewLocalizedError(errServerFailure, response.StatusCode) originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = err.Error() + originalFeed.ParsingErrorMsg = err.Localize(currentLanguage) h.store.UpdateFeed(originalFeed) return err } @@ -153,7 +162,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { if response.ContentLength == 0 { err := errors.NewLocalizedError(errEmptyFeed) originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = err.Error() + originalFeed.ParsingErrorMsg = err.Localize(currentLanguage) h.store.UpdateFeed(originalFeed) return err } @@ -163,10 +172,10 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { return errors.NewLocalizedError(errEncoding, err) } - subscription, err := parseFeed(body) - if err != nil { + subscription, parseErr := parseFeed(body) + if parseErr != nil { originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = err.Error() + originalFeed.ParsingErrorMsg = parseErr.Localize(currentLanguage) h.store.UpdateFeed(originalFeed) return err } @@ -209,6 +218,6 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { } // NewFeedHandler returns a feed handler. -func NewFeedHandler(store *storage.Storage) *Handler { - return &Handler{store: store} +func NewFeedHandler(store *storage.Storage, translator *locale.Translator) *Handler { + return &Handler{store, translator} } diff --git a/reader/feed/parser.go b/reader/feed/parser.go index 70c81e3f..f76a071a 100644 --- a/reader/feed/parser.go +++ b/reader/feed/parser.go @@ -7,11 +7,11 @@ package feed import ( "bytes" "encoding/xml" - "errors" "io" "strings" "time" + "github.com/miniflux/miniflux/errors" "github.com/miniflux/miniflux/logger" "github.com/miniflux/miniflux/model" "github.com/miniflux/miniflux/reader/atom" @@ -66,13 +66,13 @@ func DetectFeedFormat(r io.Reader) string { return FormatUnknown } -func parseFeed(r io.Reader) (*model.Feed, error) { +func parseFeed(r io.Reader) (*model.Feed, *errors.LocalizedError) { defer timer.ExecutionTime(time.Now(), "[Feed:ParseFeed]") var buffer bytes.Buffer size, _ := io.Copy(&buffer, r) if size == 0 { - return nil, errors.New("This feed is empty") + return nil, errors.NewLocalizedError("This feed is empty") } str := stripInvalidXMLCharacters(buffer.String()) @@ -90,7 +90,7 @@ func parseFeed(r io.Reader) (*model.Feed, error) { case FormatRDF: return rdf.Parse(reader) default: - return nil, errors.New("Unsupported feed format") + return nil, errors.NewLocalizedError("Unsupported feed format") } } diff --git a/reader/json/parser.go b/reader/json/parser.go index 52b4809a..ddd3e4fa 100644 --- a/reader/json/parser.go +++ b/reader/json/parser.go @@ -13,7 +13,7 @@ import ( ) // Parse returns a normalized feed struct from a JON feed. -func Parse(data io.Reader) (*model.Feed, error) { +func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { feed := new(jsonFeed) decoder := json.NewDecoder(data) if err := decoder.Decode(&feed); err != nil { diff --git a/reader/json/parser_test.go b/reader/json/parser_test.go index a3d5b5be..19c12c93 100644 --- a/reader/json/parser_test.go +++ b/reader/json/parser_test.go @@ -9,8 +9,6 @@ import ( "strings" "testing" "time" - - "github.com/miniflux/miniflux/errors" ) func TestParseJsonFeed(t *testing.T) { @@ -377,8 +375,4 @@ func TestParseInvalidJSON(t *testing.T) { if err == nil { t.Error("Parse should returns an error") } - - if _, ok := err.(errors.LocalizedError); !ok { - t.Error("The error returned must be a LocalizedError") - } } diff --git a/reader/opml/parser.go b/reader/opml/parser.go index 140acc57..b167d47c 100644 --- a/reader/opml/parser.go +++ b/reader/opml/parser.go @@ -13,7 +13,7 @@ import ( ) // Parse reads an OPML file and returns a SubcriptionList. -func Parse(data io.Reader) (SubcriptionList, error) { +func Parse(data io.Reader) (SubcriptionList, *errors.LocalizedError) { feeds := new(opml) decoder := xml.NewDecoder(data) decoder.CharsetReader = encoding.CharsetReader diff --git a/reader/opml/parser_test.go b/reader/opml/parser_test.go index 12c80487..e00d5784 100644 --- a/reader/opml/parser_test.go +++ b/reader/opml/parser_test.go @@ -7,8 +7,6 @@ package opml import ( "bytes" "testing" - - "github.com/miniflux/miniflux/errors" ) func TestParseOpmlWithoutCategories(t *testing.T) { @@ -130,8 +128,4 @@ func TestParseInvalidXML(t *testing.T) { if err == nil { t.Error("Parse should generate an error") } - - if _, ok := err.(errors.LocalizedError); !ok { - t.Error("The error returned must be a LocalizedError") - } } diff --git a/reader/rdf/parser.go b/reader/rdf/parser.go index da826397..06cacec6 100644 --- a/reader/rdf/parser.go +++ b/reader/rdf/parser.go @@ -14,7 +14,7 @@ import ( ) // Parse returns a normalized feed struct from a RDF feed. -func Parse(data io.Reader) (*model.Feed, error) { +func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { feed := new(rdfFeed) decoder := xml.NewDecoder(data) decoder.CharsetReader = encoding.CharsetReader diff --git a/reader/rdf/parser_test.go b/reader/rdf/parser_test.go index e025e537..52c11a5f 100644 --- a/reader/rdf/parser_test.go +++ b/reader/rdf/parser_test.go @@ -9,8 +9,6 @@ import ( "strings" "testing" "time" - - "github.com/miniflux/miniflux/errors" ) func TestParseRDFSample(t *testing.T) { @@ -330,8 +328,4 @@ func TestParseInvalidXml(t *testing.T) { if err == nil { t.Error("Parse should returns an error") } - - if _, ok := err.(errors.LocalizedError); !ok { - t.Error("The error returned must be a LocalizedError") - } } diff --git a/reader/rss/parser.go b/reader/rss/parser.go index f5de5618..328cde28 100644 --- a/reader/rss/parser.go +++ b/reader/rss/parser.go @@ -14,7 +14,7 @@ import ( ) // Parse returns a normalized feed struct from a RSS feed. -func Parse(data io.Reader) (*model.Feed, error) { +func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { feed := new(rssFeed) decoder := xml.NewDecoder(data) decoder.CharsetReader = encoding.CharsetReader diff --git a/reader/rss/parser_test.go b/reader/rss/parser_test.go index 7d724177..7bf9f755 100644 --- a/reader/rss/parser_test.go +++ b/reader/rss/parser_test.go @@ -8,8 +8,6 @@ import ( "bytes" "testing" "time" - - "github.com/miniflux/miniflux/errors" ) func TestParseRss2Sample(t *testing.T) { @@ -564,8 +562,4 @@ func TestParseInvalidXml(t *testing.T) { if err == nil { t.Error("Parse should returns an error") } - - if _, ok := err.(errors.LocalizedError); !ok { - t.Error("The error returned must be a LocalizedError") - } } diff --git a/storage/user.go b/storage/user.go index 5d64b656..41e565af 100644 --- a/storage/user.go +++ b/storage/user.go @@ -175,6 +175,18 @@ func (s *Storage) UpdateUser(user *model.User) error { return nil } +// UserLanguage returns the language of the given user. +func (s *Storage) UserLanguage(userID int64) (language string, err error) { + defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserLanguage] userID=%d", userID)) + err = s.db.QueryRow(`SELECT language FROM users WHERE id = $1`, userID).Scan(&language) + if err == sql.ErrNoRows { + return "en_US", nil + } else if err != nil { + return "", fmt.Errorf("unable to fetch user language: %v", err) + } + return language, nil +} + // UserByID finds a user by the ID. func (s *Storage) UserByID(userID int64) (*model.User, error) { defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByID] userID=%d", userID)) diff --git a/template/functions.go b/template/functions.go index 08a46175..81ce1db9 100644 --- a/template/functions.go +++ b/template/functions.go @@ -85,8 +85,9 @@ func (f *funcMap) Map() template.FuncMap { case string: return f.Language.Get(key.(string), args...) case errors.LocalizedError: - err := key.(errors.LocalizedError) - return err.Localize(f.Language) + return key.(errors.LocalizedError).Localize(f.Language) + case *errors.LocalizedError: + return key.(*errors.LocalizedError).Localize(f.Language) case error: return key.(error).Error() default: