From b1e8f534eff7569dc2e8dab4dee851d1b709f71b Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Sat, 22 Sep 2018 15:04:55 -0700 Subject: Simplify locale package usage (refactoring) --- daemon/daemon.go | 6 +- daemon/routes.go | 7 +- daemon/server.go | 5 +- errors/errors.go | 4 +- locale/catalog.go | 36 +++++++++ locale/catalog_test.go | 34 +++++++++ locale/language.go | 50 ------------- locale/locale.go | 16 +--- locale/locale_test.go | 105 ++++---------------------- locale/parser.go | 21 ------ locale/parser_test.go | 34 --------- locale/plural.go | 103 ++++++++++++++++++++++++++ locale/plural_test.go | 63 ++++++++++++++++ locale/plurals.go | 101 ------------------------- locale/printer.go | 67 +++++++++++++++++ locale/printer_test.go | 174 ++++++++++++++++++++++++++++++++++++++++++++ locale/translations_test.go | 12 +-- locale/translator.go | 31 -------- reader/feed/handler.go | 17 ++--- template/engine.go | 28 ++++--- template/functions.go | 22 +++--- template/functions_test.go | 30 ++++---- ui/controller.go | 5 +- ui/integration_pocket.go | 9 ++- ui/integration_update.go | 6 +- ui/oauth2_callback.go | 6 +- ui/oauth2_unlink.go | 6 +- ui/settings_update.go | 2 +- 28 files changed, 572 insertions(+), 428 deletions(-) create mode 100644 locale/catalog.go create mode 100644 locale/catalog_test.go delete mode 100644 locale/language.go delete mode 100644 locale/parser.go delete mode 100644 locale/parser_test.go create mode 100644 locale/plural.go create mode 100644 locale/plural_test.go delete mode 100644 locale/plurals.go create mode 100644 locale/printer.go create mode 100644 locale/printer_test.go delete mode 100644 locale/translator.go diff --git a/daemon/daemon.go b/daemon/daemon.go index b33d882..a1fbf8e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -13,7 +13,6 @@ import ( "time" "miniflux.app/config" - "miniflux.app/locale" "miniflux.app/logger" "miniflux.app/reader/feed" "miniflux.app/scheduler" @@ -39,10 +38,9 @@ func Run(cfg *config.Config, store *storage.Storage) { } }() - translator := locale.Load() - feedHandler := feed.NewFeedHandler(store, translator) + feedHandler := feed.NewFeedHandler(store) pool := scheduler.NewWorkerPool(feedHandler, cfg.WorkerPoolSize()) - server := newServer(cfg, store, pool, feedHandler, translator) + server := newServer(cfg, store, pool, feedHandler) scheduler.NewFeedScheduler( store, diff --git a/daemon/routes.go b/daemon/routes.go index 891207e..88b8c20 100644 --- a/daemon/routes.go +++ b/daemon/routes.go @@ -10,7 +10,6 @@ import ( "miniflux.app/api" "miniflux.app/config" "miniflux.app/fever" - "miniflux.app/locale" "miniflux.app/middleware" "miniflux.app/reader/feed" "miniflux.app/scheduler" @@ -21,12 +20,12 @@ import ( "github.com/gorilla/mux" ) -func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool, translator *locale.Translator) *mux.Router { +func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *scheduler.WorkerPool) *mux.Router { router := mux.NewRouter() - templateEngine := template.NewEngine(cfg, router, translator) + templateEngine := template.NewEngine(cfg, router) apiController := api.NewController(store, feedHandler) feverController := fever.NewController(cfg, store) - uiController := ui.NewController(cfg, store, pool, feedHandler, templateEngine, translator, router) + uiController := ui.NewController(cfg, store, pool, feedHandler, templateEngine, router) middleware := middleware.New(cfg, store, router) if cfg.BasePath() != "" { diff --git a/daemon/server.go b/daemon/server.go index 841e3b3..38afd0a 100644 --- a/daemon/server.go +++ b/daemon/server.go @@ -10,7 +10,6 @@ import ( "time" "miniflux.app/config" - "miniflux.app/locale" "miniflux.app/logger" "miniflux.app/reader/feed" "miniflux.app/scheduler" @@ -19,7 +18,7 @@ import ( "golang.org/x/crypto/acme/autocert" ) -func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, translator *locale.Translator) *http.Server { +func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler) *http.Server { certFile := cfg.CertFile() keyFile := cfg.KeyFile() certDomain := cfg.CertDomain() @@ -29,7 +28,7 @@ func newServer(cfg *config.Config, store *storage.Storage, pool *scheduler.Worke WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, Addr: cfg.ListenAddr(), - Handler: routes(cfg, store, feedHandler, pool, translator), + Handler: routes(cfg, store, feedHandler, pool), } if certDomain != "" && certCache != "" { diff --git a/errors/errors.go b/errors/errors.go index b3c5530..e6a979a 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -22,8 +22,8 @@ func (l LocalizedError) Error() string { } // Localize returns the translated error message. -func (l LocalizedError) Localize(translation *locale.Language) string { - return translation.Get(l.message, l.args...) +func (l LocalizedError) Localize(printer *locale.Printer) string { + return printer.Printf(l.message, l.args...) } // NewLocalizedError returns a new LocalizedError. diff --git a/locale/catalog.go b/locale/catalog.go new file mode 100644 index 0000000..926298c --- /dev/null +++ b/locale/catalog.go @@ -0,0 +1,36 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package locale // import "miniflux.app/locale" + +import ( + "encoding/json" + "fmt" +) + +type translationDict map[string]interface{} +type catalog map[string]translationDict + +var defaultCatalog catalog + +func init() { + defaultCatalog = make(catalog) + + for language, data := range translations { + messages, err := parseTranslationDict(data) + if err != nil { + panic(err) + } + + defaultCatalog[language] = messages + } +} + +func parseTranslationDict(data string) (translationDict, error) { + var translations translationDict + if err := json.Unmarshal([]byte(data), &translations); err != nil { + return nil, fmt.Errorf("invalid translation file: %v", err) + } + return translations, nil +} diff --git a/locale/catalog_test.go b/locale/catalog_test.go new file mode 100644 index 0000000..0be9995 --- /dev/null +++ b/locale/catalog_test.go @@ -0,0 +1,34 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package locale // import "miniflux.app/locale" + +import "testing" + +func TestParserWithInvalidData(t *testing.T) { + _, err := parseTranslationDict(`{`) + if err == nil { + t.Fatal(`An error should be returned when parsing invalid data`) + } +} + +func TestParser(t *testing.T) { + translations, err := parseTranslationDict(`{"k": "v"}`) + if err != nil { + t.Fatalf(`Unexpected parsing error: %v`, err) + } + + if translations == nil { + t.Fatal(`Translations should not be nil`) + } + + value, found := translations["k"] + if !found { + t.Fatal(`The translation should contains the defined key`) + } + + if value.(string) != "v" { + t.Fatal(`The translation key should contains the defined value`) + } +} diff --git a/locale/language.go b/locale/language.go deleted file mode 100644 index 5738163..0000000 --- a/locale/language.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package locale // import "miniflux.app/locale" - -import "fmt" - -// Language represents a language in the system. -type Language struct { - language string - translations catalogMessages -} - -// Get fetch the translation for the given key. -func (l *Language) Get(key string, args ...interface{}) string { - var translation string - - str, found := l.translations[key] - if !found { - translation = key - } else { - translation = str.(string) - } - - return fmt.Sprintf(translation, args...) -} - -// Plural returns the translation of the given key by using the language plural form. -func (l *Language) Plural(key string, n int, args ...interface{}) string { - translation := key - slices, found := l.translations[key] - - if found { - pluralForm, found := pluralForms[l.language] - if !found { - pluralForm = pluralForms["default"] - } - - index := pluralForm(n) - translations := slices.([]interface{}) - translation = key - - if len(translations) > index { - translation = translations[index].(string) - } - } - - return fmt.Sprintf(translation, args...) -} diff --git a/locale/locale.go b/locale/locale.go index 59174b0..a59d4c4 100755 --- a/locale/locale.go +++ b/locale/locale.go @@ -1,23 +1,9 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. +// Copyright 2018 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. package locale // import "miniflux.app/locale" -import "miniflux.app/logger" - -// Load loads all translations. -func Load() *Translator { - translator := NewTranslator() - - for language, tr := range translations { - logger.Debug("Loading translation: %s", language) - translator.AddLanguage(language, tr) - } - - return translator -} - // AvailableLanguages returns the list of available languages. func AvailableLanguages() map[string]string { return map[string]string{ diff --git a/locale/locale_test.go b/locale/locale_test.go index 28e2a80..9f49b60 100644 --- a/locale/locale_test.go +++ b/locale/locale_test.go @@ -1,103 +1,24 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. +// Copyright 2018 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. + package locale // import "miniflux.app/locale" import "testing" -func TestTranslateWithMissingLanguage(t *testing.T) { - translator := NewTranslator() - translation := translator.GetLanguage("en_US").Get("auth.username") - - if translation != "auth.username" { - t.Errorf("Wrong translation, got %s", translation) - } -} - -func TestTranslateWithExistingKey(t *testing.T) { - data := `{"auth.username": "Username"}` - translator := NewTranslator() - translator.AddLanguage("en_US", data) - translation := translator.GetLanguage("en_US").Get("auth.username") +func TestAvailableLanguages(t *testing.T) { + results := AvailableLanguages() + for k, v := range results { + if k == "" { + t.Errorf(`Empty language key detected`) + } - if translation != "Username" { - t.Errorf("Wrong translation, got %s", translation) + if v == "" { + t.Errorf(`Empty language value detected`) + } } -} - -func TestTranslateWithMissingKey(t *testing.T) { - data := `{"auth.username": "Username"}` - translator := NewTranslator() - translator.AddLanguage("en_US", data) - translation := translator.GetLanguage("en_US").Get("auth.password") - - if translation != "auth.password" { - t.Errorf("Wrong translation, got %s", translation) - } -} - -func TestTranslateWithMissingKeyAndPlaceholder(t *testing.T) { - translator := NewTranslator() - translator.AddLanguage("fr_FR", "") - translation := translator.GetLanguage("fr_FR").Get("Status: %s", "ok") - - if translation != "Status: ok" { - t.Errorf("Wrong translation, got %s", translation) - } -} - -func TestTranslatePluralWithDefaultRule(t *testing.T) { - data := `{"number_of_users": ["Il y a %d utilisateur (%s)", "Il y a %d utilisateurs (%s)"]}` - translator := NewTranslator() - translator.AddLanguage("fr_FR", data) - language := translator.GetLanguage("fr_FR") - - translation := language.Plural("number_of_users", 1, 1, "some text") - expected := "Il y a 1 utilisateur (some text)" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } - - translation = language.Plural("number_of_users", 2, 2, "some text") - expected = "Il y a 2 utilisateurs (some text)" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } -} - -func TestTranslatePluralWithRussianRule(t *testing.T) { - data := `{"key": ["из %d книги за %d день", "из %d книг за %d дня", "из %d книг за %d дней"]}` - translator := NewTranslator() - translator.AddLanguage("ru_RU", data) - language := translator.GetLanguage("ru_RU") - - translation := language.Plural("key", 1, 1, 1) - expected := "из 1 книги за 1 день" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } - - translation = language.Plural("key", 2, 2, 2) - expected = "из 2 книг за 2 дня" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } - - translation = language.Plural("key", 5, 5, 5) - expected = "из 5 книг за 5 дней" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) - } -} - -func TestTranslatePluralWithMissingTranslation(t *testing.T) { - translator := NewTranslator() - translator.AddLanguage("fr_FR", "") - language := translator.GetLanguage("fr_FR") - translation := language.Plural("number_of_users", 2) - expected := "number_of_users" - if translation != expected { - t.Errorf(`Wrong translation, got "%s" instead of "%s"`, translation, expected) + if _, found := results["en_US"]; !found { + t.Errorf(`We must have at least the default language (en_US)`) } } diff --git a/locale/parser.go b/locale/parser.go deleted file mode 100644 index 03591c1..0000000 --- a/locale/parser.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package locale // import "miniflux.app/locale" - -import ( - "encoding/json" - "fmt" -) - -type catalogMessages map[string]interface{} -type catalog map[string]catalogMessages - -func parseCatalogMessages(data string) (catalogMessages, error) { - var translations catalogMessages - if err := json.Unmarshal([]byte(data), &translations); err != nil { - return nil, fmt.Errorf("invalid translation file: %v", err) - } - return translations, nil -} diff --git a/locale/parser_test.go b/locale/parser_test.go deleted file mode 100644 index 619d8db..0000000 --- a/locale/parser_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package locale // import "miniflux.app/locale" - -import "testing" - -func TestParserWithInvalidData(t *testing.T) { - _, err := parseCatalogMessages(`{`) - if err == nil { - t.Fatal(`An error should be returned when parsing invalid data`) - } -} - -func TestParser(t *testing.T) { - translations, err := parseCatalogMessages(`{"k": "v"}`) - if err != nil { - t.Fatalf(`Unexpected parsing error: %v`, err) - } - - if translations == nil { - t.Fatal(`Translations should not be nil`) - } - - value, found := translations["k"] - if !found { - t.Fatal(`The translation should contains the defined key`) - } - - if value.(string) != "v" { - t.Fatal(`The translation key should contains the defined value`) - } -} diff --git a/locale/plural.go b/locale/plural.go new file mode 100644 index 0000000..3b14c38 --- /dev/null +++ b/locale/plural.go @@ -0,0 +1,103 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package locale // import "miniflux.app/locale" + +type pluralFormFunc func(n int) int + +// See https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html +// And http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html +var pluralForms = map[string]pluralFormFunc{ + // nplurals=2; plural=(n != 1); + "default": func(n int) int { + if n != 1 { + return 1 + } + + return 0 + }, + // nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); + "ar_AR": func(n int) int { + if n == 0 { + return 0 + } + + if n == 1 { + return 1 + } + + if n == 2 { + return 2 + } + + if n%100 >= 3 && n%100 <= 10 { + return 3 + } + + if n%100 >= 11 { + return 4 + } + + return 5 + }, + // nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; + "cs_CZ": func(n int) int { + if n == 1 { + return 0 + } + + if n >= 2 && n <= 4 { + return 1 + } + + return 2 + }, + // nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + "pl_PL": func(n int) int { + if n == 1 { + return 0 + } + + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return 1 + } + + return 2 + }, + // nplurals=2; plural=(n > 1); + "pt_BR": func(n int) int { + if n > 1 { + return 1 + } + return 0 + }, + // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + "ru_RU": func(n int) int { + if n%10 == 1 && n%100 != 11 { + return 0 + } + + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return 1 + } + + return 2 + }, + // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + "sr_RS": func(n int) int { + if n%10 == 1 && n%100 != 11 { + return 0 + } + + if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { + return 1 + } + + return 2 + }, + // nplurals=1; plural=0; + "zh_CN": func(n int) int { + return 0 + }, +} diff --git a/locale/plural_test.go b/locale/plural_test.go new file mode 100644 index 0000000..e7694bd --- /dev/null +++ b/locale/plural_test.go @@ -0,0 +1,63 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package locale // import "miniflux.app/locale" + +import "testing" + +func TestPluralRules(t *testing.T) { + scenarios := map[string]map[int]int{ + "default": map[int]int{ + 1: 0, + 2: 1, + 5: 1, + }, + "ar_AR": map[int]int{ + 0: 0, + 1: 1, + 2: 2, + 5: 3, + 11: 4, + 200: 5, + }, + "cs_CZ": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "pl_PL": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "pt_BR": map[int]int{ + 1: 0, + 2: 1, + 5: 1, + }, + "ru_RU": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "sr_RS": map[int]int{ + 1: 0, + 2: 1, + 5: 2, + }, + "zh_CN": map[int]int{ + 1: 0, + 5: 0, + }, + } + + for rule, values := range scenarios { + for input, expected := range values { + result := pluralForms[rule](input) + if result != expected { + t.Errorf(`Unexpected result for %q rule, got %d instead of %d for %d as input`, rule, result, expected, input) + } + } + } +} diff --git a/locale/plurals.go b/locale/plurals.go deleted file mode 100644 index c4e47cf..0000000 --- a/locale/plurals.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package locale // import "miniflux.app/locale" - -// See https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html -// And http://www.unicode.org/cldr/charts/29/supplemental/language_plural_rules.html -var pluralForms = map[string]func(n int) int{ - // nplurals=2; plural=(n != 1); - "default": func(n int) int { - if n != 1 { - return 1 - } - - return 0 - }, - // nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5); - "ar_AR": func(n int) int { - if n == 0 { - return 0 - } - - if n == 1 { - return 1 - } - - if n == 2 { - return 2 - } - - if n%100 >= 3 && n%100 <= 10 { - return 3 - } - - if n%100 >= 11 { - return 4 - } - - return 5 - }, - // nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2; - "cs_CZ": func(n int) int { - if n == 1 { - return 0 - } - - if n >= 2 && n <= 4 { - return 1 - } - - return 2 - }, - // nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); - "pl_PL": func(n int) int { - if n == 1 { - return 0 - } - - if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { - return 1 - } - - return 2 - }, - // nplurals=2; plural=(n > 1); - "pt_BR": func(n int) int { - if n > 1 { - return 1 - } - return 0 - }, - // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); - "ru_RU": func(n int) int { - if n%10 == 1 && n%100 != 11 { - return 0 - } - - if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { - return 1 - } - - return 2 - }, - // nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); - "sr_RS": func(n int) int { - if n%10 == 1 && n%100 != 11 { - return 0 - } - - if n%10 >= 2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20) { - return 1 - } - - return 2 - }, - // nplurals=1; plural=0; - "zh_CN": func(n int) int { - return 0 - }, -} diff --git a/locale/printer.go b/locale/printer.go new file mode 100644 index 0000000..ef04e05 --- /dev/null +++ b/locale/printer.go @@ -0,0 +1,67 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package locale // import "miniflux.app/locale" + +import "fmt" + +// Printer converts translation keys to language-specific strings. +type Printer struct { + language string +} + +// Printf is like fmt.Printf, but using language-specific formatting. +func (p *Printer) Printf(key string, args ...interface{}) string { + var translation string + + str, found := defaultCatalog[p.language][key] + if !found { + translation = key + } else { + var valid bool + translation, valid = str.(string) + if !valid { + translation = key + } + } + + return fmt.Sprintf(translation, args...) +} + +// Plural returns the translation of the given key by using the language plural form. +func (p *Printer) Plural(key string, n int, args ...interface{}) string { + choices, found := defaultCatalog[p.language][key] + + if found { + var plurals []string + + switch v := choices.(type) { + case []interface{}: + for _, v := range v { + plurals = append(plurals, fmt.Sprint(v)) + } + case []string: + plurals = v + default: + return key + } + + pluralForm, found := pluralForms[p.language] + if !found { + pluralForm = pluralForms["default"] + } + + index := pluralForm(n) + if len(plurals) > index { + return fmt.Sprintf(plurals[index], args...) + } + } + + return key +} + +// NewPrinter creates a new Printer. +func NewPrinter(language string) *Printer { + return &Printer{language} +} diff --git a/locale/printer_test.go b/locale/printer_test.go new file mode 100644 index 0000000..1d8f58d --- /dev/null +++ b/locale/printer_test.go @@ -0,0 +1,174 @@ +// Copyright 2018 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. +package locale // import "miniflux.app/locale" + +import "testing" + +func TestTranslateWithMissingLanguage(t *testing.T) { + defaultCatalog = catalog{} + translation := NewPrinter("invalid").Printf("missing.key") + + if translation != "missing.key" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithMissingKey(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "k": "v", + }, + } + + translation := NewPrinter("en_US").Printf("missing.key") + if translation != "missing.key" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithExistingKey(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "auth.username": "Login", + }, + } + + translation := NewPrinter("en_US").Printf("auth.username") + if translation != "Login" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithExistingKeyAndPlaceholder(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "key": "Test: %s", + }, + "fr_FR": translationDict{ + "key": "Test : %s", + }, + } + + translation := NewPrinter("fr_FR").Printf("key", "ok") + if translation != "Test : ok" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithMissingKeyAndPlaceholder(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "auth.username": "Login", + }, + "fr_FR": translationDict{ + "auth.username": "Identifiant", + }, + } + + translation := NewPrinter("fr_FR").Printf("Status: %s", "ok") + if translation != "Status: ok" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslateWithInvalidValue(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "auth.username": "Login", + }, + "fr_FR": translationDict{ + "auth.username": true, + }, + } + + translation := NewPrinter("fr_FR").Printf("auth.username") + if translation != "auth.username" { + t.Errorf(`Wrong translation, got %q`, translation) + } +} + +func TestTranslatePluralWithDefaultRule(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "number_of_users": []string{"%d user (%s)", "%d users (%s)"}, + }, + "fr_FR": translationDict{ + "number_of_users": []string{"%d utilisateur (%s)", "%d utilisateurs (%s)"}, + }, + } + + printer := NewPrinter("fr_FR") + translation := printer.Plural("number_of_users", 1, 1, "some text") + expected := "1 utilisateur (some text)" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } + + translation = printer.Plural("number_of_users", 2, 2, "some text") + expected = "2 utilisateurs (some text)" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} + +func TestTranslatePluralWithRussianRule(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "time_elapsed.years": []string{"%d year", "%d years"}, + }, + "ru_RU": translationDict{ + "time_elapsed.years": []string{"%d год назад", "%d года назад", "%d лет назад"}, + }, + } + + printer := NewPrinter("ru_RU") + + translation := printer.Plural("time_elapsed.years", 1, 1) + expected := "1 год назад" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } + + translation = printer.Plural("time_elapsed.years", 2, 2) + expected = "2 года назад" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } + + translation = printer.Plural("time_elapsed.years", 5, 5) + expected = "5 лет назад" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} + +func TestTranslatePluralWithMissingTranslation(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "number_of_users": []string{"%d user (%s)", "%d users (%s)"}, + }, + "fr_FR": translationDict{}, + } + translation := NewPrinter("fr_FR").Plural("number_of_users", 2) + expected := "number_of_users" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} + +func TestTranslatePluralWithInvalidValues(t *testing.T) { + defaultCatalog = catalog{ + "en_US": translationDict{ + "number_of_users": []string{"%d user (%s)", "%d users (%s)"}, + }, + "fr_FR": translationDict{ + "number_of_users": "must be a slice", + }, + } + translation := NewPrinter("fr_FR").Plural("number_of_users", 2) + expected := "number_of_users" + if translation != expected { + t.Errorf(`Wrong translation, got %q instead of %q`, translation, expected) + } +} diff --git a/locale/translations_test.go b/locale/translations_test.go index f196a28..55775ba 100644 --- a/locale/translations_test.go +++ b/locale/translations_test.go @@ -9,16 +9,16 @@ import "testing" func TestAllLanguagesHaveCatalog(t *testing.T) { for language := range AvailableLanguages() { if _, found := translations[language]; !found { - t.Fatalf(`This language do not have a catalog: %s`, language) + t.Fatalf(`This language do not have a catalog: %q`, language) } } } func TestAllKeysHaveValue(t *testing.T) { for language := range AvailableLanguages() { - messages, err := parseCatalogMessages(translations[language]) + messages, err := parseTranslationDict(translations[language]) if err != nil { - t.Fatalf(`Parsing error language %s`, language) + t.Fatalf(`Parsing error for language %q`, language) } if len(messages) == 0 { @@ -42,7 +42,7 @@ func TestAllKeysHaveValue(t *testing.T) { func TestMissingTranslations(t *testing.T) { refLang := "en_US" - references, err := parseCatalogMessages(translations[refLang]) + references, err := parseTranslationDict(translations[refLang]) if err != nil { t.Fatal(`Unable to parse reference language`) } @@ -52,9 +52,9 @@ func TestMissingTranslations(t *testing.T) { continue } - messages, err := parseCatalogMessages(translations[language]) + messages, err := parseTranslationDict(translations[language]) if err != nil { - t.Fatalf(`Parsing error language %s`, language) + t.Fatalf(`Parsing error for language %q`, language) } for key := range references { diff --git a/locale/translator.go b/locale/translator.go deleted file mode 100644 index 0c38c56..0000000 --- a/locale/translator.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package locale // import "miniflux.app/locale" - -// Translator manage supported locales. -type Translator struct { - locales catalog -} - -// AddLanguage loads a new language into the system. -func (t *Translator) AddLanguage(language, data string) (err error) { - t.locales[language], err = parseCatalogMessages(data) - return err -} - -// GetLanguage returns the given language handler. -func (t *Translator) GetLanguage(language string) *Language { - translations, found := t.locales[language] - if !found { - return &Language{language: language} - } - - return &Language{language: language, translations: translations} -} - -// NewTranslator creates a new Translator. -func NewTranslator() *Translator { - return &Translator{locales: make(catalog)} -} diff --git a/reader/feed/handler.go b/reader/feed/handler.go index 252d178..0b81c67 100644 --- a/reader/feed/handler.go +++ b/reader/feed/handler.go @@ -33,7 +33,6 @@ var ( // Handler contains all the logic to create and refresh feeds. type Handler struct { store *storage.Storage - translator *locale.Translator } // CreateFeed fetch, parse and store a new feed. @@ -124,7 +123,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { userLanguage = "en_US" } - currentLanguage := h.translator.GetLanguage(userLanguage) + printer := locale.NewPrinter(userLanguage) originalFeed, err := h.store.FeedByID(userID, feedID) if err != nil { @@ -149,7 +148,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { } originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = customErr.Localize(currentLanguage) + originalFeed.ParsingErrorMsg = customErr.Localize(printer) h.store.UpdateFeed(originalFeed) return customErr } @@ -159,7 +158,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { if response.IsNotFound() { err := errors.NewLocalizedError(errResourceNotFound) originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = err.Localize(currentLanguage) + originalFeed.ParsingErrorMsg = err.Localize(printer) h.store.UpdateFeed(originalFeed) return err } @@ -167,7 +166,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { if response.HasServerFailure() { err := errors.NewLocalizedError(errServerFailure, response.StatusCode) originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = err.Localize(currentLanguage) + originalFeed.ParsingErrorMsg = err.Localize(printer) h.store.UpdateFeed(originalFeed) return err } @@ -179,7 +178,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { if response.ContentLength == 0 { err := errors.NewLocalizedError(errEmptyFeed) originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = err.Localize(currentLanguage) + originalFeed.ParsingErrorMsg = err.Localize(printer) h.store.UpdateFeed(originalFeed) return err } @@ -192,7 +191,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { subscription, parseErr := parseFeed(body) if parseErr != nil { originalFeed.ParsingErrorCount++ - originalFeed.ParsingErrorMsg = parseErr.Localize(currentLanguage) + originalFeed.ParsingErrorMsg = parseErr.Localize(printer) h.store.UpdateFeed(originalFeed) return err } @@ -236,6 +235,6 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error { } // NewFeedHandler returns a feed handler. -func NewFeedHandler(store *storage.Storage, translator *locale.Translator) *Handler { - return &Handler{store, translator} +func NewFeedHandler(store *storage.Storage) *Handler { + return &Handler{store} } diff --git a/template/engine.go b/template/engine.go index bd99742..e4cdc96 100644 --- a/template/engine.go +++ b/template/engine.go @@ -19,9 +19,8 @@ import ( // Engine handles the templating system. type Engine struct { - templates map[string]*template.Template - translator *locale.Translator - funcMap *funcMap + templates map[string]*template.Template + funcMap *funcMap } func (e *Engine) parseAll() { @@ -43,29 +42,29 @@ func (e *Engine) Render(name, language string, data interface{}) []byte { logger.Fatal("[Template] The template %s does not exists", name) } - lang := e.translator.GetLanguage(language) + printer := locale.NewPrinter(language) // Functions that need to be declared at runtime. tpl.Funcs(template.FuncMap{ "elapsed": func(timezone string, t time.Time) string { - return elapsedTime(lang, timezone, t) + return elapsedTime(printer, timezone, t) }, "t": func(key interface{}, args ...interface{}) string { - switch key.(type) { + switch k := key.(type) { case string: - return lang.Get(key.(string), args...) + return printer.Printf(k, args...) case errors.LocalizedError: - return key.(errors.LocalizedError).Localize(lang) + return k.Localize(printer) case *errors.LocalizedError: - return key.(*errors.LocalizedError).Localize(lang) + return k.Localize(printer) case error: - return key.(error).Error() + return k.Error() default: return "" } }, "plural": func(key string, n int, args ...interface{}) string { - return lang.Plural(key, n, args...) + return printer.Plural(key, n, args...) }, }) @@ -79,11 +78,10 @@ func (e *Engine) Render(name, language string, data interface{}) []byte { } // NewEngine returns a new template engine. -func NewEngine(cfg *config.Config, router *mux.Router, translator *locale.Translator) *Engine { +func NewEngine(cfg *config.Config, router *mux.Router) *Engine { tpl := &Engine{ - templates: make(map[string]*template.Template), - translator: translator, - funcMap: newFuncMap(cfg, router), + templates: make(map[string]*template.Template), + funcMap: newFuncMap(cfg, router), } tpl.parseAll() diff --git a/template/functions.go b/template/functions.go index aac6c75..289de3d 100644 --- a/template/functions.go +++ b/template/functions.go @@ -135,15 +135,15 @@ func isEmail(str string) bool { return true } -func elapsedTime(language *locale.Language, tz string, t time.Time) string { +func elapsedTime(printer *locale.Printer, tz string, t time.Time) string { if t.IsZero() { - return language.Get("time_elapsed.not_yet") + return printer.Printf("time_elapsed.not_yet") } now := timezone.Now(tz) t = timezone.Convert(tz, t) if now.Before(t) { - return language.Get("time_elapsed.not_yet") + return printer.Printf("time_elapsed.not_yet") } diff := now.Sub(t) @@ -153,25 +153,25 @@ func elapsedTime(language *locale.Language, tz string, t time.Time) string { d := int(s / 86400) switch { case s < 60: - return language.Get("time_elapsed.now") + return printer.Printf("time_elapsed.now") case s < 3600: minutes := int(diff.Minutes()) - return language.Plural("time_elapsed.minutes", minutes, minutes) + return printer.Plural("time_elapsed.minutes", minutes, minutes) case s < 86400: hours := int(diff.Hours()) - return language.Plural("time_elapsed.hours", hours, hours) + return printer.Plural("time_elapsed.hours", hours, hours) case d == 1: - return language.Get("time_elapsed.yesterday") + return printer.Printf("time_elapsed.yesterday") case d < 7: - return language.Plural("time_elapsed.days", d, d) + return printer.Plural("time_elapsed.days", d, d) case d < 31: weeks := int(math.Ceil(float64(d) / 7)) - return language.Plural("time_elapsed.weeks", weeks, weeks) + return printer.Plural("time_elapsed.weeks", weeks, weeks) case d < 365: months := int(math.Ceil(float64(d) / 30)) - return language.Plural("time_elapsed.months", months, months) + return printer.Plural("time_elapsed.months", months, months) default: years := int(math.Ceil(float64(d) / 365)) - return language.Plural("time_elapsed.years", years, years) + return printer.Plural("time_elapsed.years", years, years) } } diff --git a/template/functions_test.go b/template/functions_test.go index 04982c7..10d5535 100644 --- a/template/functions_test.go +++ b/template/functions_test.go @@ -97,28 +97,26 @@ func TestIsEmail(t *testing.T) { } func TestElapsedTime(t *testing.T) { - translator := locale.Load() - language := translator.GetLanguage("fr_FR") - + printer := locale.NewPrinter("en_US") var dt = []struct { in time.Time out string }{ - {time.Time{}, language.Get("time_elapsed.not_yet")}, - {time.Now().Add(time.Hour), language.Get("time_elapsed.not_yet")}, - {time.Now(), language.Get("time_elapsed.now")}, - {time.Now().Add(-time.Minute), language.Plural("time_elapsed.minutes", 1, 1)}, - {time.Now().Add(-time.Minute * 40), language.Plural("time_elapsed.minutes", 40, 40)}, - {time.Now().Add(-time.Hour), language.Plural("time_elapsed.hours", 1, 1)}, - {time.Now().Add(-time.Hour * 3), language.Plural("time_elapsed.hours", 3, 3)}, - {time.Now().Add(-time.Hour * 32), language.Get("time_elapsed.yesterday")}, - {time.Now().Add(-time.Hour * 24 * 3), language.Plural("time_elapsed.days", 3, 3)}, - {time.Now().Add(-time.Hour * 24 * 14), language.Plural("time_elapsed.weeks", 2, 2)}, - {time.Now().Add(-time.Hour * 24 * 60), language.Plural("time_elapsed.months", 2, 2)}, - {time.Now().Add(-time.Hour * 24 * 365 * 3), language.Plural("time_elapsed.years", 3, 3)}, + {time.Time{}, printer.Printf("time_elapsed.not_yet")}, + {time.Now().Add(time.Hour), printer.Printf("time_elapsed.not_yet")}, + {time.Now(), printer.Printf("time_elapsed.now")}, + {time.Now().Add(-time.Minute), printer.Plural("time_elapsed.minutes", 1, 1)}, + {time.Now().Add(-time.Minute * 40), printer.Plural("time_elapsed.minutes", 40, 40)}, + {time.Now().Add(-time.Hour), printer.Plural("time_elapsed.hours", 1, 1)}, + {time.Now().Add(-time.Hour * 3), printer.Plural("time_elapsed.hours", 3, 3)}, + {time.Now().Add(-time.Hour * 32), printer.Printf("time_elapsed.yesterday")}, + {time.Now().Add(-time.Hour * 24 * 3), printer.Plural("time_elapsed.days", 3, 3)}, + {time.Now().Add(-time.Hour * 24 * 14), printer.Plural("time_elapsed.weeks", 2, 2)}, + {time.Now().Add(-time.Hour * 24 * 60), printer.Plural("time_elapsed.months", 2, 2)}, + {time.Now().Add(-time.Hour * 24 * 365 * 3), printer.Plural("time_elapsed.years", 3, 3)}, } for i, tt := range dt { - if out := elapsedTime(language, "Local", tt.in); out != tt.out { + if out := elapsedTime(printer, "Local", tt.in); out != tt.out { t.Errorf(`%d. content mismatch for "%v": expected=%q got=%q`, i, tt.in, tt.out, out) } } diff --git a/ui/controller.go b/ui/controller.go index 005b863..b08c253 100644 --- a/ui/controller.go +++ b/ui/controller.go @@ -6,7 +6,6 @@ package ui // import "miniflux.app/ui" import ( "miniflux.app/config" - "miniflux.app/locale" "miniflux.app/reader/feed" "miniflux.app/scheduler" "miniflux.app/storage" @@ -23,18 +22,16 @@ type Controller struct { feedHandler *feed.Handler tpl *template.Engine router *mux.Router - translator *locale.Translator } // NewController returns a new Controller. -func NewController(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, tpl *template.Engine, translator *locale.Translator, router *mux.Router) *Controller { +func NewController(cfg *config.Config, store *storage.Storage, pool *scheduler.WorkerPool, feedHandler *feed.Handler, tpl *template.Engine, router *mux.Router) *Controller { return &Controller{ cfg: cfg, store: store, pool: pool, feedHandler: feedHandler, tpl: tpl, - translator: translator, router: router, } } diff --git a/ui/integration_pocket.go b/ui/integration_pocket.go index 432069a..407731e 100644 --- a/ui/integration_pocket.go +++ b/ui/integration_pocket.go @@ -12,12 +12,14 @@ import ( "miniflux.app/http/response/html" "miniflux.app/http/route" "miniflux.app/integration/pocket" + "miniflux.app/locale" "miniflux.app/logger" "miniflux.app/ui/session" ) // PocketAuthorize redirects the end-user to Pocket website to authorize the application. func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) { + printer := locale.NewPrinter(request.UserLanguage(r)) user, err := c.store.UserByID(request.UserID(r)) if err != nil { html.ServerError(w, err) @@ -36,7 +38,7 @@ func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) { requestToken, err := connector.RequestToken(redirectURL) if err != nil { logger.Error("[Pocket:Authorize] %v", err) - sess.NewFlashErrorMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("error.pocket_request_token")) + sess.NewFlashErrorMessage(printer.Printf("error.pocket_request_token")) response.Redirect(w, r, route.Path(c.router, "integrations")) return } @@ -47,6 +49,7 @@ func (c *Controller) PocketAuthorize(w http.ResponseWriter, r *http.Request) { // PocketCallback saves the personal access token after the authorization step. func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) { + printer := locale.NewPrinter(request.UserLanguage(r)) sess := session.New(c.store, request.SessionID(r)) user, err := c.store.UserByID(request.UserID(r)) @@ -65,7 +68,7 @@ func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) { accessToken, err := connector.AccessToken(request.PocketRequestToken(r)) if err != nil { logger.Error("[Pocket:Callback] %v", err) - sess.NewFlashErrorMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("error.pocket_access_token")) + sess.NewFlashErrorMessage(printer.Printf("error.pocket_access_token")) response.Redirect(w, r, route.Path(c.router, "integrations")) return } @@ -79,6 +82,6 @@ func (c *Controller) PocketCallback(w http.ResponseWriter, r *http.Request) { return } - sess.NewFlashMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("alert.pocket_linked")) + sess.NewFlashMessage(printer.Printf("alert.pocket_linked")) response.Redirect(w, r, route.Path(c.router, "integrations")) } diff --git a/ui/integration_update.go b/ui/integration_update.go index 2d10413..f470e4b 100644 --- a/ui/integration_update.go +++ b/ui/integration_update.go @@ -13,12 +13,14 @@ import ( "miniflux.app/http/request" "miniflux.app/http/response/html" "miniflux.app/http/route" + "miniflux.app/locale" "miniflux.app/ui/form" "miniflux.app/ui/session" ) // UpdateIntegration updates integration settings. func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) { + printer := locale.NewPrinter(request.UserLanguage(r)) sess := session.New(c.store, request.SessionID(r)) user, err := c.store.UserByID(request.UserID(r)) if err != nil { @@ -36,7 +38,7 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) { integrationForm.Merge(integration) if integration.FeverUsername != "" && c.store.HasDuplicateFeverUsername(user.ID, integration.FeverUsername) { - sess.NewFlashErrorMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("error.duplicate_fever_username")) + sess.NewFlashErrorMessage(printer.Printf("error.duplicate_fever_username")) response.Redirect(w, r, route.Path(c.router, "integrations")) return } @@ -53,6 +55,6 @@ func (c *Controller) UpdateIntegration(w http.ResponseWriter, r *http.Request) { return } - sess.NewFlashMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("alert.prefs_saved")) + sess.NewFlashMessage(printer.Printf("alert.prefs_saved")) response.Redirect(w, r, route.Path(c.router, "integrations")) } diff --git a/ui/oauth2_callback.go b/ui/oauth2_callback.go index 546158f..9f51d0a 100644 --- a/ui/oauth2_callback.go +++ b/ui/oauth2_callback.go @@ -12,6 +12,7 @@ import ( "miniflux.app/http/response" "miniflux.app/http/response/html" "miniflux.app/http/route" + "miniflux.app/locale" "miniflux.app/logger" "miniflux.app/model" "miniflux.app/ui/session" @@ -19,6 +20,7 @@ import ( // OAuth2Callback receives the authorization code and create a new session. func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) { + printer := locale.NewPrinter(request.UserLanguage(r)) sess := session.New(c.store, request.SessionID(r)) provider := request.Param(r, "provider", "") @@ -65,7 +67,7 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) { if user != nil { logger.Error("[OAuth2] User #%d cannot be associated because %s is already associated", request.UserID(r), user.Username) - sess.NewFlashErrorMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("error.duplicate_linked_account")) + sess.NewFlashErrorMessage(printer.Printf("error.duplicate_linked_account")) response.Redirect(w, r, route.Path(c.router, "settings")) return } @@ -75,7 +77,7 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) { return } - sess.NewFlashMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("alert.account_linked")) + sess.NewFlashMessage(printer.Printf("alert.account_linked")) response.Redirect(w, r, route.Path(c.router, "settings")) return } diff --git a/ui/oauth2_unlink.go b/ui/oauth2_unlink.go index 191d78d..4435733 100644 --- a/ui/oauth2_unlink.go +++ b/ui/oauth2_unlink.go @@ -11,12 +11,14 @@ import ( "miniflux.app/http/response" "miniflux.app/http/response/html" "miniflux.app/http/route" + "miniflux.app/locale" "miniflux.app/logger" "miniflux.app/ui/session" ) // OAuth2Unlink unlink an account from the external provider. func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) { + printer := locale.NewPrinter(request.UserLanguage(r)) provider := request.Param(r, "provider", "") if provider == "" { logger.Info("[OAuth2] Invalid or missing provider") @@ -40,7 +42,7 @@ func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) { } if !hasPassword { - sess.NewFlashErrorMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("error.unlink_account_without_password")) + sess.NewFlashErrorMessage(printer.Printf("error.unlink_account_without_password")) response.Redirect(w, r, route.Path(c.router, "settings")) return } @@ -50,6 +52,6 @@ func (c *Controller) OAuth2Unlink(w http.ResponseWriter, r *http.Request) { return } - sess.NewFlashMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("alert.account_unlinked")) + sess.NewFlashMessage(printer.Printf("alert.account_unlinked")) response.Redirect(w, r, route.Path(c.router, "settings")) } diff --git a/ui/settings_update.go b/ui/settings_update.go index 6daf28c..ed424e3 100644 --- a/ui/settings_update.go +++ b/ui/settings_update.go @@ -69,6 +69,6 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) { sess.SetLanguage(user.Language) sess.SetTheme(user.Theme) - sess.NewFlashMessage(c.translator.GetLanguage(request.UserLanguage(r)).Get("alert.prefs_saved")) + sess.NewFlashMessage(locale.NewPrinter(request.UserLanguage(r)).Printf("alert.prefs_saved")) response.Redirect(w, r, route.Path(c.router, "settings")) } -- cgit v1.2.3