From 8b00cfce1f5d33e6e6a39447d8feecf725992ed8 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Mon, 20 Oct 2025 15:53:23 -0600 Subject: [PATCH] split a bunch of lists free of UI templates, and also fixed profile language display --- community.go | 6 +- ui/templates.go | 128 ++----------------------------------- util/countrylist.go | 56 ++++++++++++++++ util/languagelist.go | 94 +++++++++++++++++++++++++++ {ui => util}/languages.txt | 0 util/timelist.go | 57 +++++++++++++++++ 6 files changed, 215 insertions(+), 126 deletions(-) create mode 100644 util/countrylist.go create mode 100644 util/languagelist.go rename {ui => util}/languages.txt (100%) create mode 100644 util/timelist.go diff --git a/community.go b/community.go index 87bce45..c90d63e 100644 --- a/community.go +++ b/community.go @@ -15,8 +15,8 @@ import ( "git.erbosoft.com/amy/amsterdam/database" "git.erbosoft.com/amy/amsterdam/ui" + "git.erbosoft.com/amy/amsterdam/util" "github.com/biter777/countries" - "golang.org/x/text/language/display" ) /* ShowCommunity renders the community profile display. @@ -119,9 +119,7 @@ func ShowCommunity(ctxt ui.AmContext) (string, any, error) { } tag, err := comm.LanguageTag() if err == nil && tag != nil { - my_lang := prefs.LanguageTag() - disp := display.Languages(*my_lang) - ctxt.VarMap().Set("language", disp.Name(tag)) + ctxt.VarMap().Set("language", util.AmLanguageInLanguage(*tag, *prefs.LanguageTag())) } if comm.Rules != nil && *comm.Rules != "" { ctxt.VarMap().Set("rules", *comm.Rules) diff --git a/ui/templates.go b/ui/templates.go index cd2ab04..6bbb121 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -16,10 +16,7 @@ import ( "io" "reflect" "regexp" - "slices" "strconv" - "strings" - "sync" "time" "git.erbosoft.com/amy/amsterdam/config" @@ -28,12 +25,8 @@ import ( "github.com/CloudyKit/jet/v6" "github.com/CloudyKit/jet/v6/loaders/embedfs" "github.com/CloudyKit/jet/v6/loaders/multi" - "github.com/biter777/countries" "github.com/labstack/echo/v4" log "github.com/sirupsen/logrus" - "github.com/tkuchiki/go-timezone" - "golang.org/x/text/language" - "golang.org/x/text/language/display" ) //go:embed views/* @@ -42,112 +35,6 @@ var static_views embed.FS // views is the main Jet template repository. var views *jet.Set -//go:embed languages.txt -var knownLanguages string - -// Language is a type for a list of all supportred languages. -type Language struct { - Tag string // the BCP 47 tag, such as "en-US" - Name string // the human-readable name, like "American English" -} - -// cachedLanguageList is the cached language list. -var cachedLanguageList []Language = nil - -// languageListMutex controls access to internalGetLanguageList. -var languageListMutex sync.Mutex - -// internalGetLanguageList is a wrapper around "allTags" that sorts it by language name. -func internalGetLanguageList() []Language { - languageListMutex.Lock() - defer languageListMutex.Unlock() - if cachedLanguageList == nil { - langs := strings.Split(knownLanguages, "\n") - enNamer := display.English.Tags() - cachedLanguageList = make([]Language, 0, len(langs)) - for _, l := range langs { - tag, err := language.Parse(l) - if err == nil { - cachedLanguageList = append(cachedLanguageList, Language{ - Tag: tag.String(), - Name: enNamer.Name(tag), - }) - } else { - log.Errorf("*** PUKE on parsing language tag %s: %v", l, err) - } - } - - slices.SortFunc(cachedLanguageList, func(a Language, b Language) int { - return strings.Compare(a.Name, b.Name) - }) - } - return cachedLanguageList -} - -// cachedTimeZoneList is a wrapper around timezone.Timezones() that produces it by timezone name. -var cachedTimeZoneList []string = nil - -// timeZoneListMutex controls access to internalGetTimeZoneList. -var timeZoneListMutex sync.Mutex - -// internalGetTimeZoneList is a wrapper around TimeZone.TimeZones() that sorts and compacts the list. -func internalGetTimeZoneList() []string { - timeZoneListMutex.Lock() - defer timeZoneListMutex.Unlock() - if cachedTimeZoneList == nil { - timezones := timezone.New().Timezones() - ilist := make([]string, 0, len(timezones)*5) - for k, v := range timezones { - ilist = append(ilist, k) - ilist = append(ilist, v...) - } - - slices.Sort(ilist) - cachedTimeZoneList = slices.Compact(ilist) - } - return cachedTimeZoneList -} - -// cachedCountryList is the cached country list after sorting. -var cachedCountryList []countries.CountryCode = nil - -// countryListMutex control access to internalGetCountryList. -var countryListMutex sync.Mutex - -// internalGetCountryList is a wrapper around countries.All() that sorts it by country name. -func internalGetCountryList() []countries.CountryCode { - countryListMutex.Lock() - defer countryListMutex.Unlock() - if cachedCountryList == nil { - countryList := countries.All() - slices.SortFunc(countryList, func(a countries.CountryCode, b countries.CountryCode) int { - return strings.Compare(a.Info().Name, b.Info().Name) - }) - if config.GlobalConfig.Rendering.CountryList.Prioritize != "" { - for i, c := range countryList { - if c.Info().Alpha2 == config.GlobalConfig.Rendering.CountryList.Prioritize { - newList := make([]countries.CountryCode, len(countryList)) - newList[0] = c - copy(newList[1:], countryList[:i]) - copy(newList[i+1:], countryList[i+1:]) - countryList = newList - } - } - } - cachedCountryList = countryList - } - return cachedCountryList -} - -// getMonthList is a simple wrapper that returns the names of the months to the template context. -func getMonthList(a jet.Arguments) reflect.Value { - rc := make([]string, 12) - for m := time.January; m <= time.December; m++ { - rc[m-time.January] = m.String() - } - return reflect.ValueOf(rc) -} - // countRanger is a Ranger that can count from one value to another with a certain step. type countRanger struct { i int @@ -232,19 +119,21 @@ func SetupTemplates() { views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION) views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT) views.AddGlobal("GlobalConfig", config.GlobalConfig) - views.AddGlobalFunc("GetMonthList", getMonthList) views.AddGlobalFunc("MakeIntRange", makeIntRange) views.AddGlobalFunc("MakeYearRange", makeYearRange) views.AddGlobalFunc("ExtractCommunityLogo", extractCommunityLogo) views.AddGlobalFunc("GetCountryList", func(a jet.Arguments) reflect.Value { - return reflect.ValueOf(internalGetCountryList()) + return reflect.ValueOf(util.AmCountryList()) }) views.AddGlobalFunc("GetLanguageList", func(a jet.Arguments) reflect.Value { - return reflect.ValueOf(internalGetLanguageList()) + return reflect.ValueOf(util.AmLanguageList()) }) views.AddGlobalFunc("GetTimeZoneList", func(a jet.Arguments) reflect.Value { - return reflect.ValueOf(internalGetTimeZoneList()) + return reflect.ValueOf(util.AmTimeZoneList()) + }) + views.AddGlobalFunc("GetMonthList", func(a jet.Arguments) reflect.Value { + return reflect.ValueOf(util.AmMonthList()) }) views.AddGlobalFunc("AmMenu", func(a jet.Arguments) reflect.Value { s := a.Get(0).Convert(reflect.TypeFor[string]()).String() @@ -258,11 +147,6 @@ func SetupTemplates() { s := a.Get(0).Convert(reflect.TypeFor[string]()).String() return reflect.ValueOf(util.CapitalizeString(s)) }) - - // preload the lists in the background - go internalGetCountryList() - go internalGetLanguageList() - go internalGetTimeZoneList() } // TemplateRenderer is the Renderer instance set into the Echo context at creation time, to render Jet templates. diff --git a/util/countrylist.go b/util/countrylist.go new file mode 100644 index 0000000..01dbd0d --- /dev/null +++ b/util/countrylist.go @@ -0,0 +1,56 @@ +/* + * Amsterdam Web Communities System + * Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package util contains utility definitions. +package util + +import ( + "slices" + "strings" + "sync" + + "git.erbosoft.com/amy/amsterdam/config" + "github.com/biter777/countries" +) + +// cachedCountryList is the cached country list after sorting. +var cachedCountryList []countries.CountryCode = nil + +// countryListMutex control access to internalGetCountryList. +var countryListMutex sync.Mutex + +// AmCountryList is a wrapper around countries.All() that sorts it by country name. +func AmCountryList() []countries.CountryCode { + countryListMutex.Lock() + defer countryListMutex.Unlock() + if cachedCountryList == nil { + countryList := countries.All() + slices.SortFunc(countryList, func(a countries.CountryCode, b countries.CountryCode) int { + return strings.Compare(a.Info().Name, b.Info().Name) + }) + if config.GlobalConfig.Rendering.CountryList.Prioritize != "" { + for i, c := range countryList { + if c.Info().Alpha2 == config.GlobalConfig.Rendering.CountryList.Prioritize { + newList := make([]countries.CountryCode, len(countryList)) + newList[0] = c + copy(newList[1:], countryList[:i]) + copy(newList[i+1:], countryList[i+1:]) + countryList = newList + } + } + } + cachedCountryList = countryList + } + return cachedCountryList +} + +// init preloads the country list. +func init() { + go AmCountryList() +} diff --git a/util/languagelist.go b/util/languagelist.go new file mode 100644 index 0000000..55fca8b --- /dev/null +++ b/util/languagelist.go @@ -0,0 +1,94 @@ +/* + * Amsterdam Web Communities System + * Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package util contains utility definitions. +package util + +import ( + _ "embed" + "slices" + "strings" + "sync" + + log "github.com/sirupsen/logrus" + "golang.org/x/text/language" + "golang.org/x/text/language/display" +) + +//go:embed languages.txt +var knownLanguages string + +// Language is a type for a list of all supported languages. +type Language struct { + Tag string // the BCP 47 tag, such as "en-US" + Name string // the human-readable name, like "American English" +} + +// cachedLanguageList is the cached language list. +var cachedLanguageList []Language = nil + +// mapping from language tag names to actual language entries +var languageTagMapper map[string]*Language + +// languageListMutex controls access to internalGetLanguageList. +var languageListMutex sync.Mutex + +// AmLanguageList returns a list of all known languages. +func AmLanguageList() []Language { + languageListMutex.Lock() + defer languageListMutex.Unlock() + if cachedLanguageList == nil { + langs := strings.Split(knownLanguages, "\n") + enNamer := display.English.Tags() + cachedLanguageList = make([]Language, 0, len(langs)) + for _, l := range langs { + tag, err := language.Parse(l) + if err == nil { + cachedLanguageList = append(cachedLanguageList, Language{ + Tag: tag.String(), + Name: enNamer.Name(tag), + }) + } else { + log.Errorf("*** PUKE on parsing language tag %s: %v", l, err) + } + } + + slices.SortFunc(cachedLanguageList, func(a Language, b Language) int { + return strings.Compare(a.Name, b.Name) + }) + languageTagMapper = make(map[string]*Language) + for i := range cachedLanguageList { + languageTagMapper[strings.ToLower(cachedLanguageList[i].Tag)] = &(cachedLanguageList[i]) + } + } + return cachedLanguageList +} + +/* AmLanguageInLanguage displays a language name in any other language. + * Parameters: + * lang - The language to be displayed. + * inLang - The language to display the other language's name in. + * Returns: + * The full translated language name. + */ +func AmLanguageInLanguage(lang language.Tag, inLang language.Tag) string { + namer := display.Tags(inLang) + if namer == nil { + namer = display.English.Tags() + } + s := namer.Name(lang) + if s == "" { + s = display.English.Tags().Name(lang) + } + return s +} + +func init() { + go AmLanguageList() // preload the list in the background +} diff --git a/ui/languages.txt b/util/languages.txt similarity index 100% rename from ui/languages.txt rename to util/languages.txt diff --git a/util/timelist.go b/util/timelist.go new file mode 100644 index 0000000..e60bd01 --- /dev/null +++ b/util/timelist.go @@ -0,0 +1,57 @@ +/* + * Amsterdam Web Communities System + * Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// Package util contains utility definitions. +package util + +import ( + "slices" + "sync" + "time" + + "github.com/tkuchiki/go-timezone" +) + +// cachedTimeZoneList is a wrapper around timezone.Timezones() that produces it by timezone name. +var cachedTimeZoneList []string = nil + +// timeZoneListMutex controls access to internalGetTimeZoneList. +var timeZoneListMutex sync.Mutex + +// AmTimeZoneList is a wrapper around TimeZone.TimeZones() that sorts and compacts the list. +func AmTimeZoneList() []string { + timeZoneListMutex.Lock() + defer timeZoneListMutex.Unlock() + if cachedTimeZoneList == nil { + timezones := timezone.New().Timezones() + ilist := make([]string, 0, len(timezones)*5) + for k, v := range timezones { + ilist = append(ilist, k) + ilist = append(ilist, v...) + } + + slices.Sort(ilist) + cachedTimeZoneList = slices.Compact(ilist) + } + return cachedTimeZoneList +} + +// AmMonthList is a simple wrapper that returns the names of the months to the template context. +func AmMonthList() []string { + rc := make([]string, 12) + for m := time.January; m <= time.December; m++ { + rc[m-time.January] = m.String() + } + return rc +} + +// init preloads the time zone list. +func init() { + go AmTimeZoneList() +}