From b5e28b4b401047452d5b09e534770a44ea6a06b6 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Sun, 8 Dec 2024 22:10:32 +0100 Subject: [PATCH] Delay parsing of translations until they're used While doing some profiling for #2900, I noticed that `miniflux.app/v2/internal/locale.LoadCatalogMessages` is responsible for more than 10% of the consumed memory. As most miniflux instances won't have enough diverse users to use all the available translations at the same time, it makes sense to load them on demand. The overhead is a single function call and a check in a map, per call to translation-related functions. This should close #2975 --- internal/cli/cli.go | 5 ----- internal/locale/catalog.go | 13 +++++++++++-- internal/locale/printer.go | 33 +++++++++++++++++++-------------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index ca4f47bd48b..fc0747171d4 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -13,7 +13,6 @@ import ( "miniflux.app/v2/internal/config" "miniflux.app/v2/internal/database" - "miniflux.app/v2/internal/locale" "miniflux.app/v2/internal/storage" "miniflux.app/v2/internal/ui/static" "miniflux.app/v2/internal/version" @@ -153,10 +152,6 @@ func Parse() { slog.Info("The default value for DATABASE_URL is used") } - if err := locale.LoadCatalogMessages(); err != nil { - printErrorAndExit(fmt.Errorf("unable to load translations: %v", err)) - } - if err := static.CalculateBinaryFileChecksums(); err != nil { printErrorAndExit(fmt.Errorf("unable to calculate binary file checksums: %v", err)) } diff --git a/internal/locale/catalog.go b/internal/locale/catalog.go index 61f5f27d0fe..92914de7948 100644 --- a/internal/locale/catalog.go +++ b/internal/locale/catalog.go @@ -12,15 +12,24 @@ import ( type translationDict map[string]interface{} type catalog map[string]translationDict -var defaultCatalog catalog +var defaultCatalog = make(catalog, len(AvailableLanguages())) //go:embed translations/*.json var translationFiles embed.FS +func GetTranslationDict(language string) (translationDict, error) { + if _, ok := defaultCatalog[language]; !ok { + var err error + if defaultCatalog[language], err = loadTranslationFile(language); err != nil { + return nil, err + } + } + return defaultCatalog[language], nil +} + // LoadCatalogMessages loads and parses all translations encoded in JSON. func LoadCatalogMessages() error { var err error - defaultCatalog = make(catalog, len(AvailableLanguages())) for language := range AvailableLanguages() { defaultCatalog[language], err = loadTranslationFile(language) diff --git a/internal/locale/printer.go b/internal/locale/printer.go index f85960fae04..d997c1a75bc 100644 --- a/internal/locale/printer.go +++ b/internal/locale/printer.go @@ -11,9 +11,11 @@ type Printer struct { } func (p *Printer) Print(key string) string { - if str, ok := defaultCatalog[p.language][key]; ok { - if translation, ok := str.(string); ok { - return translation + if dict, err := GetTranslationDict(p.language); err == nil { + if str, ok := dict[key]; ok { + if translation, ok := str.(string); ok { + return translation + } } } return key @@ -21,16 +23,16 @@ func (p *Printer) Print(key string) string { // Printf is like fmt.Printf, but using language-specific formatting. func (p *Printer) Printf(key string, args ...interface{}) string { - var translation string + translation := key - str, found := defaultCatalog[p.language][key] - if !found { - translation = key - } else { - var valid bool - translation, valid = str.(string) - if !valid { - translation = key + if dict, err := GetTranslationDict(p.language); err == nil { + str, found := dict[key] + if found { + var valid bool + translation, valid = str.(string) + if !valid { + translation = key + } } } @@ -39,9 +41,12 @@ func (p *Printer) Printf(key string, args ...interface{}) string { // 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] + dict, err := GetTranslationDict(p.language) + if err != nil { + return key + } - if found { + if choices, found := dict[key]; found { var plurals []string switch v := choices.(type) {