diff --git a/go.mod b/go.mod index ebc9bc5..4f53f03 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/labstack/echo/v4 v4.13.4 github.com/labstack/gommon v0.4.2 github.com/sirupsen/logrus v1.9.3 + golang.org/x/text v0.30.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -30,6 +31,5 @@ require ( golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index 4a11746..017d727 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/ui/dialog.go b/ui/dialog.go index 3104df0..547da8b 100644 --- a/ui/dialog.go +++ b/ui/dialog.go @@ -170,8 +170,13 @@ func (d *Dialog) Render(ctxt AmContext) (string, any, error) { if fld.Required { required = true // display the "required" blurb } - if fld.Type == "password" { // clear all "password" fields as a security measure + switch fld.Type { + case "password": // clear all "password" fields as a security measure d.Fields[i].Value = "" + case "localelist": // default locale to en-US if we don't have one + if d.Fields[i].Value == "" { + d.Fields[i].Value = "en-US" + } } } ctxt.VarMap().Set("amsterdam_required", required) diff --git a/ui/languages.txt b/ui/languages.txt new file mode 100644 index 0000000..9bc576e --- /dev/null +++ b/ui/languages.txt @@ -0,0 +1,150 @@ +ja-JP +es-PE +en +es-PA +sr-BA +mk +es-GT +ar-AE +no-NO +sq-AL +bg +ar-IQ +ar-YE +hu +pt-PT +el-CY +ar-QA +mk-MK +sv +de-CH +en-US +fi-FI +is +cs +en-MT +sl-SI +sk-SK +it +tr-TR +zh +th +ar-SA +no +en-GB +sr-CS +lt +ro +en-NZ +nn-NO +lt-LT +es-NI +nl +ga-IE +fr-BE +es-ES +ar-LB +ko +fr-CA +et-EE +ar-KW +sr-RS +es-US +es-MX +ar-SD +in-ID +ru +lv +es-UY +lv-LV +iw +pt-BR +ar-SY +hr +et +es-DO +fr-CH +hi-IN +es-VE +ar-BH +en-PH +ar-TN +fi +de-AT +es +nl-NL +es-EC +zh-TW +ar-JO +be +is-IS +es-CO +es-CR +es-CL +ar-EG +en-ZA +th-TH +el-GR +it-IT +ca +hu-HU +fr +en-IE +uk-UA +pl-PL +fr-LU +nl-BE +en-IN +ca-ES +ar-MA +es-BO +en-AU +sr +zh-SG +pt +uk +es-SV +ru-RU +ko-KR +vi +ar-DZ +vi-VN +sr-ME +sq +ar-LY +ar +zh-CN +be-BY +zh-HK +ja +iw-IL +bg-BG +in +mt-MT +es-PY +sl +fr-FR +cs-CZ +it-CH +ro-RO +es-PR +en-CA +de-DE +ga +de-LU +de +es-AR +sk +ms-MY +hr-HR +en-SG +da +mt +pl +ar-OM +tr +el +ms +sv-SE +da-DK +es-HN \ No newline at end of file diff --git a/ui/templates.go b/ui/templates.go index 5275508..0e71877 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -22,6 +22,7 @@ import ( "sync" "time" "unicode" + _ "unsafe" // HACK HACK HACK "git.erbosoft.com/amy/amsterdam/config" "github.com/CloudyKit/jet/v6" @@ -30,6 +31,8 @@ import ( "github.com/biter777/countries" "github.com/labstack/echo/v4" log "github.com/sirupsen/logrus" + "golang.org/x/text/language" + "golang.org/x/text/language/display" ) //go:embed views/* @@ -38,6 +41,56 @@ 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) + }) + log.Infof("internalGetLanguageList() loaded %d languages", len(cachedLanguageList)) + } + return cachedLanguageList +} + +// getLanguageList is a wrapper around the list of languages that can be added to the template context. +func getLanguageList(a jet.Arguments) reflect.Value { + rc := internalGetLanguageList() + log.Infof("GetLanguageList() provides %d items", len(rc)) + return reflect.ValueOf(rc) +} + // cachedCountryList is the cached country list after sorting. var cachedCountryList []countries.CountryCode = nil @@ -170,6 +223,7 @@ func SetupTemplates() { views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT) views.AddGlobal("GlobalConfig", config.GlobalConfig) views.AddGlobalFunc("GetCountryList", getCountryList) + views.AddGlobalFunc("GetLanguageList", getLanguageList) views.AddGlobalFunc("GetMonthList", getMonthList) views.AddGlobalFunc("MakeIntRange", makeIntRange) views.AddGlobalFunc("MakeYearRange", makeYearRange) @@ -179,8 +233,9 @@ func SetupTemplates() { return reflect.ValueOf(CapitalizeString(s)) }) - // preload the country list in the background + // preload the lists in the background go internalGetCountryList() + go internalGetLanguageList() } // TemplateRenderer is the Renderer instance set into the Echo context at creation time, to render Jet templates. diff --git a/ui/views/dialog.jet b/ui/views/dialog.jet index 7da4c24..b7ddf32 100644 --- a/ui/views/dialog.jet +++ b/ui/views/dialog.jet @@ -130,9 +130,12 @@ {{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }} {{ if .Required }}*{{ end }} + {{ v := .Value }} {{ else if .Type == "tzlist" }}