From 548090e67f66acf84385c4152ca464e52d3e3319 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Fri, 13 Sep 2024 13:01:48 +0200 Subject: Migrate away from templ and towards html/template --- src/countries.go | 46 ++++ src/email/email.go | 79 +++++++ src/http.go | 190 ++++++++++++++++ src/i18n.go | 253 +++++++++++++++++++++ src/mintage/parser.go | 297 ++++++++++++++++++++++++ src/mintage/parser_test.go | 233 +++++++++++++++++++ src/rosetta/bg/messages.gotext.json | 315 ++++++++++++++++++++++++++ src/rosetta/el/messages.gotext.json | 315 ++++++++++++++++++++++++++ src/rosetta/en/messages.gotext.json | 439 ++++++++++++++++++++++++++++++++++++ src/rosetta/nl/messages.gotext.json | 315 ++++++++++++++++++++++++++ src/templates.go | 56 +++++ src/templates/404.html.tmpl | 11 + src/templates/about.html.tmpl | 41 ++++ src/templates/base.html.tmpl | 43 ++++ src/templates/error.html.tmpl | 14 ++ src/templates/index.html.tmpl | 18 ++ src/templates/language.html.tmpl | 48 ++++ src/templates/navbar.html.tmpl | 228 +++++++++++++++++++ 18 files changed, 2941 insertions(+) create mode 100644 src/countries.go create mode 100644 src/email/email.go create mode 100644 src/http.go create mode 100644 src/i18n.go create mode 100644 src/mintage/parser.go create mode 100644 src/mintage/parser_test.go create mode 100644 src/rosetta/bg/messages.gotext.json create mode 100644 src/rosetta/el/messages.gotext.json create mode 100644 src/rosetta/en/messages.gotext.json create mode 100644 src/rosetta/nl/messages.gotext.json create mode 100644 src/templates.go create mode 100644 src/templates/404.html.tmpl create mode 100644 src/templates/about.html.tmpl create mode 100644 src/templates/base.html.tmpl create mode 100644 src/templates/error.html.tmpl create mode 100644 src/templates/index.html.tmpl create mode 100644 src/templates/language.html.tmpl create mode 100644 src/templates/navbar.html.tmpl (limited to 'src') diff --git a/src/countries.go b/src/countries.go new file mode 100644 index 0000000..de1c919 --- /dev/null +++ b/src/countries.go @@ -0,0 +1,46 @@ +package src + +import ( + "slices" + + "golang.org/x/text/collate" + "golang.org/x/text/language" +) + +type country struct { + code, name string +} + +func sortedCountries(p Printer) []country { + xs := []country{ + {code: "ad", name: p.T("Andorra")}, + {code: "at", name: p.T("Austria")}, + {code: "be", name: p.T("Belgium")}, + {code: "cy", name: p.T("Cyprus")}, + {code: "de", name: p.T("Germany")}, + {code: "ee", name: p.T("Estonia")}, + {code: "es", name: p.T("Spain")}, + {code: "fi", name: p.T("Finland")}, + {code: "fr", name: p.T("France")}, + {code: "gr", name: p.T("Greece")}, + {code: "hr", name: p.T("Croatia")}, + {code: "ie", name: p.T("Ireland")}, + {code: "it", name: p.T("Italy")}, + {code: "lt", name: p.T("Lithuania")}, + {code: "lu", name: p.T("Luxembourg")}, + {code: "lv", name: p.T("Latvia")}, + {code: "mc", name: p.T("Monaco")}, + {code: "mt", name: p.T("Malta")}, + {code: "nl", name: p.T("Netherlands")}, + {code: "pt", name: p.T("Portugal")}, + {code: "si", name: p.T("Slovenia")}, + {code: "sk", name: p.T("Slovakia")}, + {code: "sm", name: p.T("San Marino")}, + {code: "va", name: p.T("Vatican City")}, + } + c := collate.New(language.MustParse(p.Locale.Bcp)) + slices.SortFunc(xs, func(x, y country) int { + return c.CompareString(x.name, y.name) + }) + return xs +} diff --git a/src/email/email.go b/src/email/email.go new file mode 100644 index 0000000..0f2c93d --- /dev/null +++ b/src/email/email.go @@ -0,0 +1,79 @@ +package email + +import ( + "crypto/tls" + "fmt" + "math/rand/v2" + "net/smtp" + "strconv" + "time" +) + +var Config struct { + Disabled bool + Host string + Port int + ToAddr, FromAddr string + Password string +} + +const emailTemplate = `From: %s +To: %s +Subject: %s +Date: %s +Content-Type: text/plain; charset=UTF-8 +MIME-Version: 1.0 +Message-ID: <%s> + +%s` + +func ServerError(fault error) error { + if Config.Disabled { + return fault + } + + msgid := strconv.FormatInt(rand.Int64(), 10) + "@" + Config.Host + msg := fmt.Sprintf(emailTemplate, Config.FromAddr, Config.ToAddr, + "Error Report", time.Now().Format(time.RFC1123Z), msgid, fault) + + tlsConfig := &tls.Config{ + InsecureSkipVerify: false, + ServerName: Config.Host, + } + + hostWithPort := Config.Host + ":" + strconv.Itoa(Config.Port) + conn, err := tls.Dial("tcp", hostWithPort, tlsConfig) + if err != nil { + return err + } + + client, err := smtp.NewClient(conn, Config.Host) + if err != nil { + return err + } + defer client.Close() + + auth := smtp.PlainAuth("", Config.FromAddr, Config.Password, Config.Host) + if err := client.Auth(auth); err != nil { + return err + } + + if err := client.Mail(Config.FromAddr); err != nil { + return err + } + + if err := client.Rcpt(Config.ToAddr); err != nil { + return err + } + + wc, err := client.Data() + if err != nil { + return err + } + defer wc.Close() + + if _, err = wc.Write([]byte(msg)); err != nil { + return err + } + return nil +} diff --git a/src/http.go b/src/http.go new file mode 100644 index 0000000..8ca7564 --- /dev/null +++ b/src/http.go @@ -0,0 +1,190 @@ +package src + +import ( + "cmp" + "context" + "errors" + "fmt" + "log" + "math" + "net/http" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + + "git.thomasvoss.com/euro-cash.eu/src/email" + "git.thomasvoss.com/euro-cash.eu/src/mintage" +) + +type middleware = func(http.Handler) http.Handler + +func Run(port int) { + fs := http.FileServer(http.Dir("static")) + final := http.HandlerFunc(finalHandler) + mux := http.NewServeMux() + mux.Handle("GET /designs/", fs) + mux.Handle("GET /favicon.ico", fs) + mux.Handle("GET /fonts/", fs) + mux.Handle("GET /style.css", fs) + mux.Handle("GET /coins/mintages", chain( + firstHandler, + i18nHandler, + mintageHandler, + )(final)) + mux.Handle("GET /", chain( + firstHandler, + i18nHandler, + )(final)) + mux.Handle("POST /language", http.HandlerFunc(setUserLanguage)) + + portStr := ":" + strconv.Itoa(port) + log.Println("Listening on", portStr) + log.Fatal(http.ListenAndServe(portStr, mux)) +} + +func chain(xs ...middleware) middleware { + return func(next http.Handler) http.Handler { + for i := len(xs) - 1; i >= 0; i-- { + next = xs[i](next) + } + return next + } +} + +func firstHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), "td", &templateData{}) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func finalHandler(w http.ResponseWriter, r *http.Request) { + /* Strip trailing slash from the URL */ + path := r.URL.Path + if path != "/" && path[len(path)-1] == '/' { + path = path[:len(path)-1] + } + + t, ok := templates[path] + if !ok { + w.WriteHeader(http.StatusNotFound) + t = notFoundTmpl + } + + /* When a user clicks on the language button to be taken to the + language selection page, we need to set a redirect cookie so + that after selecting a language they are taken back to the + original page they came from. */ + if path == "/language" { + http.SetCookie(w, &http.Cookie{ + Name: "redirect", + Value: cmp.Or(r.Referer(), "/"), + }) + } + + data := r.Context().Value("td").(*templateData) + t.Execute(w, data) +} + +func i18nHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var p, pZero Printer + + if c, err := r.Cookie("locale"); err == nil { + p = printers[strings.ToLower(c.Value)] + } + + td := r.Context().Value("td").(*templateData) + td.Printer = cmp.Or(p, defaultPrinter) + + if p == pZero { + http.SetCookie(w, &http.Cookie{ + Name: "redirect", + Value: r.URL.Path, + }) + templates["/language"].Execute(w, td) + } else { + next.ServeHTTP(w, r) + } + }) +} + +func mintageHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + td := r.Context().Value("td").(*templateData) + td.Countries = sortedCountries(td.Printer) + + td.Code = strings.ToLower(r.FormValue("code")) + if !slices.ContainsFunc(td.Countries, func(c country) bool { + return c.code == td.Code + }) { + td.Code = td.Countries[0].code + } + + td.Type = strings.ToLower(r.FormValue("type")) + switch td.Type { + case "circ", "nifc", "proof": + default: + td.Type = "circ" + } + + path := filepath.Join("data", "mintages", td.Code) + f, err := os.Open(path) + if err != nil { + throwError(http.StatusInternalServerError, err, w, r) + return + } + defer f.Close() + + td.Mintages, err = mintage.Parse(f, path) + if err != nil { + throwError(http.StatusInternalServerError, err, w, r) + return + } + + next.ServeHTTP(w, r) + }) +} + +func setUserLanguage(w http.ResponseWriter, r *http.Request) { + loc := r.FormValue("locale") + _, ok := printers[strings.ToLower(loc)] + if !ok { + w.WriteHeader(http.StatusUnprocessableEntity) + fmt.Fprintf(w, "Locale ‘%s’ is invalid or unsupported", loc) + return + } + http.SetCookie(w, &http.Cookie{ + Name: "locale", + Value: loc, + MaxAge: math.MaxInt32, + }) + + if c, err := r.Cookie("redirect"); errors.Is(err, http.ErrNoCookie) { + http.Redirect(w, r, "/", http.StatusFound) + } else { + http.SetCookie(w, &http.Cookie{ + Name: "redirect", + MaxAge: -1, + }) + http.Redirect(w, r, c.Value, http.StatusFound) + } +} + +func throwError(status int, err error, w http.ResponseWriter, r *http.Request) { + w.WriteHeader(status) + go func() { + if err := email.ServerError(err); err != nil { + log.Print(err) + } + }() + errorTmpl.Execute(w, struct { + Code int + Msg string + }{ + Code: status, + Msg: http.StatusText(status), + }) +} diff --git a/src/i18n.go b/src/i18n.go new file mode 100644 index 0000000..eaac4cf --- /dev/null +++ b/src/i18n.go @@ -0,0 +1,253 @@ +//go:generate gotext -srclang=en -dir=rosetta extract -lang=bg,el,en,nl . +//go:generate ../exttmpl + +package src + +import ( + "fmt" + "strings" + "time" + + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +type Printer struct { + Locale locale + inner *message.Printer +} + +type locale struct { + Bcp, Name string + Eurozone, Enabled bool + dateFmt, moneyFmt string +} + +var ( + Locales = [...]locale{ + { + Bcp: "ca", + Name: "català", + dateFmt: "2/1/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "de", + Name: "Deutsch", + dateFmt: "2.1.2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "el", + Name: "ελληνικά", + dateFmt: "2/1/2006", + Eurozone: true, + Enabled: true, + }, + { + Bcp: "en", + Name: "English", + dateFmt: "02/01/2006", + Eurozone: true, + Enabled: true, + }, + { + Bcp: "es", + Name: "español", + dateFmt: "2/1/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "et", + Name: "eesti", + dateFmt: "2.1.2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "fi", + Name: "suomi", + dateFmt: "2.1.2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "fr", + Name: "français", + dateFmt: "02/01/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "ga", + Name: "Gaeilge", + dateFmt: "02/01/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "hr", + Name: "hrvatski", + dateFmt: "02. 01. 2006.", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "it", + Name: "italiano", + dateFmt: "02/01/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "lb", + Name: "lëtzebuergesch", + dateFmt: "2.1.2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "lt", + Name: "lietuvių", + dateFmt: "2006-01-02", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "lv", + Name: "latviešu", + dateFmt: "2.01.2006.", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "mt", + Name: "Malti", + dateFmt: "2/1/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "nl", + Name: "Nederlands", + dateFmt: "2-1-2006", + Eurozone: true, + Enabled: true, + }, + { + Bcp: "pt", + Name: "português", + dateFmt: "02/01/2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "sk", + Name: "slovenčina", + dateFmt: "2. 1. 2006", + Eurozone: true, + Enabled: false, + }, + { + Bcp: "sl", + Name: "slovenščina", + dateFmt: "2. 1. 2006", + Eurozone: true, + Enabled: false, + }, + + /* Non-Eurozone locales */ + { + Bcp: "bg", + Name: "български", + dateFmt: "2.01.2006 г.", + Eurozone: false, + Enabled: true, + }, + { + Bcp: "en-US", + Name: "English (US)", + dateFmt: "1/2/2006", + Eurozone: false, + Enabled: false, + }, + { + Bcp: "ro", + Name: "română", + dateFmt: "02.01.2006", + Eurozone: false, + Enabled: false, + }, + { + Bcp: "uk", + Name: "yкраїнська", + dateFmt: "02.01.2006", + Eurozone: false, + Enabled: false, + }, + } + /* Map of language codes to printers. We do this instead of just + using language.MustParse() directly so that we can easily see if a + language is supported or not. */ + printers map[string]Printer = make(map[string]Printer, len(Locales)) + defaultPrinter Printer +) + +func init() { + for _, loc := range Locales { + if loc.Enabled { + lang := language.MustParse(loc.Bcp) + printers[strings.ToLower(loc.Bcp)] = Printer{ + Locale: loc, + inner: message.NewPrinter(lang), + } + } + } + defaultPrinter = printers["en"] +} + +func (p Printer) T(fmt string, args ...any) string { + return p.inner.Sprintf(fmt, args...) +} + +func (p Printer) N(n int) string { + return p.inner.Sprint(n) +} + +func (p Printer) Date(d time.Time) string { + return d.Format(p.Locale.dateFmt) +} + +/* TODO: Try to use a decimal type here */ +func (p Printer) Money(val float64, round bool) string { + var valstr string + + /* Hack to avoid gotext writing these two ‘translations’ into the + translations file */ + f := p.inner.Sprintf + if round { + valstr = f("%d", int(val)) + } else { + valstr = f("%.2f", val) + } + + /* All Eurozone languages place the eurosign after the value except + for Dutch, English, Gaelic, and Maltese. Austrian German also + uses Dutch-style formatting, but we do not support that dialect. */ + switch p.Locale.Bcp { + case "en", "en-US", "ga", "mt": + return fmt.Sprintf("€%s", valstr) + case "nl": + return fmt.Sprintf("€ %s", valstr) + default: + return fmt.Sprintf("%s €", valstr) + } +} + +/* Transform ‘en-US’ to ‘en’ */ +func (l locale) Language() string { + return l.Bcp[:2] +} diff --git a/src/mintage/parser.go b/src/mintage/parser.go new file mode 100644 index 0000000..364b6e8 --- /dev/null +++ b/src/mintage/parser.go @@ -0,0 +1,297 @@ +package mintage + +import ( + "bufio" + "fmt" + "io" + "strconv" + "strings" + "time" +) + +type SyntaxError struct { + expected, got string + file string + linenr int +} + +func (e SyntaxError) Error() string { + return fmt.Sprintf("%s:%d: syntax error: expected %s but got %s", + e.file, e.linenr, e.expected, e.got) +} + +type Data struct { + Standard []SRow + Commemorative []CRow +} + +type SRow struct { + Year int + Mintmark string + Mintages [typeCount][denoms]int +} + +type CRow struct { + Year int + Mintmark string + Name string + Mintage [typeCount]int +} + +const ( + TypeCirc = iota + TypeNIFC + TypeProof + typeCount +) + +const ( + Unknown = -iota - 1 + Invalid +) + +const ( + denoms = 8 + ws = " \t" +) + +func Parse(r io.Reader, file string) (Data, error) { + yearsSince := time.Now().Year() - 1999 + 1 + data := Data{ + Standard: make([]SRow, 0, yearsSince), + Commemorative: make([]CRow, 0, yearsSince), + } + + scanner := bufio.NewScanner(r) + for linenr := 1; scanner.Scan(); linenr++ { + line := strings.Trim(scanner.Text(), ws) + if isBlankOrComment(line) { + continue + } + + if len(line) < 4 || !isNumeric(line[:4], false) { + return Data{}, SyntaxError{ + expected: "4-digit year", + got: line, + linenr: linenr, + file: file, + } + } + + var ( + commem bool + mintmark string + ) + year, _ := strconv.Atoi(line[:4]) + line = line[4:] + + if len(line) != 0 { + if strings.ContainsRune(ws, rune(line[0])) { + commem = true + goto out + } + if line[0] != '-' { + return Data{}, SyntaxError{ + expected: "end-of-line or mintmark", + got: line, + linenr: linenr, + file: file, + } + } + + if line = line[1:]; len(line) == 0 { + return Data{}, SyntaxError{ + expected: "mintmark", + got: "end-of-line", + linenr: linenr, + file: file, + } + } + + switch i := strings.IndexAny(line, ws); i { + case 0: + return Data{}, SyntaxError{ + expected: "mintmark", + got: "whitespace", + linenr: linenr, + file: file, + } + case -1: + mintmark = line + default: + mintmark, line = line[:i], line[i:] + commem = true + } + } + out: + + if !commem { + row := SRow{ + Year: year, + Mintmark: mintmark, + } + for i := range row.Mintages { + line = "" + for isBlankOrComment(line) { + if !scanner.Scan() { + return Data{}, SyntaxError{ + expected: "mintage row", + got: "end-of-file", + linenr: linenr, + file: file, + } + } + line = strings.Trim(scanner.Text(), ws) + linenr++ + } + + tokens := strings.FieldsFunc(line, func(r rune) bool { + return strings.ContainsRune(ws, r) + }) + if tokcnt := len(tokens); tokcnt != denoms { + word := "entries" + if tokcnt == 1 { + word = "entry" + } + return Data{}, SyntaxError{ + expected: fmt.Sprintf("%d mintage entries", denoms), + got: fmt.Sprintf("%d %s", tokcnt, word), + linenr: linenr, + file: file, + } + } + + for j, tok := range tokens { + if tok != "?" && !isNumeric(tok, true) { + return Data{}, SyntaxError{ + expected: "numeric mintage figure or ‘?’", + got: tok, + linenr: linenr, + file: file, + } + } + + if tok == "?" { + row.Mintages[i][j] = Unknown + } else { + row.Mintages[i][j] = atoiWithDots(tok) + } + } + } + + data.Standard = append(data.Standard, row) + } else { + row := CRow{ + Year: year, + Mintmark: mintmark, + } + line = strings.TrimLeft(line, ws) + if line[0] != '"' { + return Data{}, SyntaxError{ + expected: "string", + got: line, + linenr: linenr, + file: file, + } + } + + line = line[1:] + switch i := strings.IndexByte(line, '"'); i { + case -1: + return Data{}, SyntaxError{ + expected: "closing quote", + got: "end-of-line", + linenr: linenr, + file: file, + } + case 0: + return Data{}, SyntaxError{ + expected: "commemorated event", + got: "empty string", + linenr: linenr, + file: file, + } + default: + row.Name, line = line[:i], line[i+1:] + } + + if len(line) != 0 { + return Data{}, SyntaxError{ + expected: "end-of-line", + got: line, + linenr: linenr, + file: file, + } + } + + for isBlankOrComment(line) { + if !scanner.Scan() { + return Data{}, SyntaxError{ + expected: "mintage row", + got: "end-of-file", + linenr: linenr, + file: file, + } + } + line = strings.Trim(scanner.Text(), ws) + linenr++ + } + + tokens := strings.FieldsFunc(line, func(r rune) bool { + return strings.ContainsRune(ws, r) + }) + if tokcnt := len(tokens); tokcnt != typeCount { + word := "entries" + if tokcnt == 1 { + word = "entry" + } + return Data{}, SyntaxError{ + expected: fmt.Sprintf("%d mintage entries", typeCount), + got: fmt.Sprintf("%d %s", tokcnt, word), + linenr: linenr, + file: file, + } + } + + for i, tok := range tokens { + if tok == "?" { + row.Mintage[i] = Unknown + } else { + row.Mintage[i] = atoiWithDots(tok) + } + } + + data.Commemorative = append(data.Commemorative, row) + } + } + + return data, nil +} + +func isNumeric(s string, dot bool) bool { + for _, ch := range s { + switch ch { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + if ch != '.' || !dot { + return false + } + } + } + return true +} + +func atoiWithDots(s string) int { + n := 0 + for _, ch := range s { + if ch == '.' { + continue + } + n = n*10 + int(ch) - '0' + } + return n +} + +func isBlankOrComment(s string) bool { + return len(s) == 0 || s[0] == '#' +} diff --git a/src/mintage/parser_test.go b/src/mintage/parser_test.go new file mode 100644 index 0000000..76e0f01 --- /dev/null +++ b/src/mintage/parser_test.go @@ -0,0 +1,233 @@ +package mintage + +import ( + "bytes" + "errors" + "testing" +) + +func TestParserComplete(t *testing.T) { + data, err := Parse(bytes.NewBuffer([]byte(` + 2020 + 1000 1001 1002 1003 1004 1005 1006 1007 + 1100 1101 1102 1103 1104 1105 1106 1107 + 1200 1201 1202 1203 1204 1205 1206 1207 + 2021-KNM + 2.000 ? 2002 2003 2004 2005 2006 2007 + 2.100 ? 2102 2103 2104 2105 2106 2107 + 2.200 ? 2202 2203 2204 2205 2206 2207 + 2021-MdP + 3000 3001 3002 3003 3004 3005 3006 3007 + 3100 3101 3102 3103 3104 3105 3106 3107 + 3200 3201 3202 3203 3204 3205 3206 3207 + 2022 + 4000 4001 4.002 4003 4004 4005 4006 4007 + 4100 4101 4.102 4103 4104 4105 4106 4107 + 4200 4201 4.202 4203 4204 4205 4206 4207 + + 2009 "10th Anniversary of Economic and Monetary Union" + 1000 2000 3000 + 2022-⋆ "35th Anniversary of the Erasmus Programme" + 1001 ? 3001 + `)), "-") + + if err != nil { + t.Fatalf(`Expected err=nil; got "%s"`, err) + } + + for i, row := range data.Standard { + for k := TypeCirc; k <= TypeProof; k++ { + for j, col := range row.Mintages[k] { + n := 1000*(i+1) + 100*k + j + if i == 1 && j == 1 { + n = Unknown + } + if col != n { + t.Fatalf("Expected data.Standard[%d].Mintages[%d][%d]=%d; got %d", + i, k, j, col, n) + } + } + } + } + + for i, row := range data.Commemorative { + for k := TypeCirc; k <= TypeProof; k++ { + n := 1000*(k+1) + i + if i == 1 && k == 1 { + n = Unknown + } + if row.Mintage[k] != n { + t.Fatalf("Expected row.Mintage[%d]=%d; got %d", + k, n, row.Mintage[k]) + } + } + } + + if len(data.Standard) != 4 { + t.Fatalf("Expected len(data.Standard)=2; got %d", len(data.Standard)) + } + if len(data.Commemorative) != 2 { + t.Fatalf("Expected len(data.Commemorative)=2; got %d", len(data.Commemorative)) + } + + for i, x := range [...]struct { + year int + mintmark, name string + }{ + {2009, "", "10th Anniversary of Economic and Monetary Union"}, + {2022, "⋆", "35th Anniversary of the Erasmus Programme"}, + } { + if data.Commemorative[i].Year != x.year { + t.Fatalf("Expected data.Commemorative[%d].Year=%d; got %d", + i, x.year, data.Commemorative[i].Year) + } + if data.Commemorative[i].Mintmark != x.mintmark { + t.Fatalf(`Expected data.Commemorative[%d].Mintmark="%s"; got "%s"`, + i, x.mintmark, data.Commemorative[i].Mintmark) + } + if data.Commemorative[i].Name != x.name { + t.Fatalf(`Expected data.Commemorative[%d].Name="%s"; got "%s"`, + i, x.name, data.Commemorative[i].Name) + } + } +} + +func TestParserMintmarks(t *testing.T) { + data, err := Parse(bytes.NewBuffer([]byte(` + 2020 + 1000 1001 1002 1003 1004 1005 1006 1007 + 1100 1101 1102 1103 1104 1105 1106 1107 + 1200 1201 1202 1203 1204 1205 1206 1207 + 2021-KNM + 2.000 ? 2002 2003 2004 2005 2006 2007 + 2.100 ? 2102 2103 2104 2105 2106 2107 + 2.200 ? 2202 2203 2204 2205 2206 2207 + 2021-MdP + 3000 3001 3002 3003 3004 3005 3006 3007 + 3100 3101 3102 3103 3104 3105 3106 3107 + 3200 3201 3202 3203 3204 3205 3206 3207 + 2022 + 4000 4001 4.002 4003 4004 4005 4006 4007 + 4100 4101 4.102 4103 4104 4105 4106 4107 + 4200 4201 4.202 4203 4204 4205 4206 4207 + `)), "-") + + if err != nil { + t.Fatalf(`Expected err=nil; got "%s"`, err) + } + + for i, row := range data.Standard { + for j, col := range row.Mintages[TypeCirc] { + n := 1000*(i+1) + j + if i == 1 && j == 1 { + n = Unknown + } + if col != n { + t.Fatalf("Expected data.Standard[%d].Mintages[TypeCirc][%d]=%d; got %d", + i, j, col, n) + } + } + } + + for i, y := range [...]int{2020, 2021, 2021, 2022} { + if data.Standard[i].Year != y { + t.Fatalf("Expected data.Standard[%d].Year=%d; got %d", + i, y, data.Standard[i].Year) + } + } + + for i, m := range [...]string{"", "KNM", "MdP", ""} { + if data.Standard[i].Mintmark != m { + t.Fatalf(`Expected data.Standard[%d].Mintmark="%s"; got "%s"`, + i, m, data.Standard[i].Mintmark) + } + } +} + +func TestParserNoYear(t *testing.T) { + _, err := Parse(bytes.NewBuffer([]byte(` + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 2107 + 2200 ? 2202 2203 2204 2205 2206 2207 + `)), "-") + + var sErr SyntaxError + if !errors.As(err, &sErr) { + t.Fatalf("Expected err=SyntaxError; got %s", err) + } +} + +func TestParserBadToken(t *testing.T) { + _, err := Parse(bytes.NewBuffer([]byte(` + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 Naughty! + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 2107 + 2200 ? 2202 2203 2204 2205 2206 2207 + `)), "-") + + var sErr SyntaxError + if !errors.As(err, &sErr) { + t.Fatalf("Expected err=SyntaxError; got %s", err) + } +} + +func TestParserShortRow(t *testing.T) { + _, err := Parse(bytes.NewBuffer([]byte(` + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 + 2200 ? 2202 2203 2204 2205 2206 2207 + `)), "-") + + var sErr SyntaxError + if !errors.As(err, &sErr) { + t.Fatalf("Expected err=SyntaxError; got %s", err) + } +} + +func TestParserLongRow(t *testing.T) { + _, err := Parse(bytes.NewBuffer([]byte(` + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2100 ? 2102 2103 2104 2105 2106 2107 2108 + 2200 ? 2202 2203 2204 2205 2206 2207 + `)), "-") + + var sErr SyntaxError + if !errors.As(err, &sErr) { + t.Fatalf("Expected err=SyntaxError; got %s", err) + } +} + +func TestParserMissingRow(t *testing.T) { + _, err := Parse(bytes.NewBuffer([]byte(` + 2020 + 1.000 1001 1002 1003 1004 1005 1006 1007 + 1.100 1101 1102 1103 1104 1105 1106 1107 + 1.200 1201 1202 1203 1204 1205 1206 1207 + 2021 + 2000 ? 2002 2003 2004 2005 2006 2007 + 2200 ? 2202 2203 2204 2205 2206 2207 + `)), "-") + + var sErr SyntaxError + if !errors.As(err, &sErr) { + t.Fatalf("Expected err=SyntaxError; got %s", err) + } +} diff --git a/src/rosetta/bg/messages.gotext.json b/src/rosetta/bg/messages.gotext.json new file mode 100644 index 0000000..b1c3b47 --- /dev/null +++ b/src/rosetta/bg/messages.gotext.json @@ -0,0 +1,315 @@ +{ + "language": "bg", + "messages": [ + { + "id": "Andorra", + "message": "Andorra", + "translation": "" + }, + { + "id": "Austria", + "message": "Austria", + "translation": "" + }, + { + "id": "Belgium", + "message": "Belgium", + "translation": "" + }, + { + "id": "Cyprus", + "message": "Cyprus", + "translation": "" + }, + { + "id": "Germany", + "message": "Germany", + "translation": "" + }, + { + "id": "Estonia", + "message": "Estonia", + "translation": "" + }, + { + "id": "Spain", + "message": "Spain", + "translation": "" + }, + { + "id": "Finland", + "message": "Finland", + "translation": "" + }, + { + "id": "France", + "message": "France", + "translation": "" + }, + { + "id": "Greece", + "message": "Greece", + "translation": "" + }, + { + "id": "Croatia", + "message": "Croatia", + "translation": "" + }, + { + "id": "Ireland", + "message": "Ireland", + "translation": "" + }, + { + "id": "Italy", + "message": "Italy", + "translation": "" + }, + { + "id": "Lithuania", + "message": "Lithuania", + "translation": "" + }, + { + "id": "Luxembourg", + "message": "Luxembourg", + "translation": "" + }, + { + "id": "Latvia", + "message": "Latvia", + "translation": "" + }, + { + "id": "Monaco", + "message": "Monaco", + "translation": "" + }, + { + "id": "Malta", + "message": "Malta", + "translation": "" + }, + { + "id": "Netherlands", + "message": "Netherlands", + "translation": "" + }, + { + "id": "Portugal", + "message": "Portugal", + "translation": "" + }, + { + "id": "Slovenia", + "message": "Slovenia", + "translation": "" + }, + { + "id": "Slovakia", + "message": "Slovakia", + "translation": "" + }, + { + "id": "San Marino", + "message": "San Marino", + "translation": "" + }, + { + "id": "Vatican City", + "message": "Vatican City", + "translation": "" + }, + { + "id": "Page Not Found", + "message": "Page Not Found", + "translation": "" + }, + { + "id": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "message": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "translation": "" + }, + { + "id": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "message": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "translation": "" + }, + { + "id": "About Us", + "message": "About Us", + "translation": "" + }, + { + "id": "Open Source", + "message": "Open Source", + "translation": "" + }, + { + "id": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "message": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "translation": "" + }, + { + "id": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "message": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "translation": "" + }, + { + "id": "\u003c/a\u003e", + "message": "\u003c/a\u003e", + "translation": "" + }, + { + "id": "Contact Us", + "message": "Contact Us", + "translation": "" + }, + { + "id": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "message": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "translation": "" + }, + { + "id": "Special Thanks", + "message": "Special Thanks", + "translation": "" + }, + { + "id": "Development", + "message": "Development", + "translation": "" + }, + { + "id": "Research", + "message": "Research", + "translation": "" + }, + { + "id": "Translations", + "message": "Translations", + "translation": "" + }, + { + "id": "British- \u0026 American English", + "message": "British- \u0026 American English", + "translation": "" + }, + { + "id": "Icelandic", + "message": "Icelandic", + "translation": "" + }, + { + "id": "Found a mistake or want to contribute missing information?", + "message": "Found a mistake or want to contribute missing information?", + "translation": "" + }, + { + "id": "Feel free to contact us!", + "message": "Feel free to contact us!", + "translation": "" + }, + { + "id": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "message": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "translation": "" + }, + { + "id": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "translation": "" + }, + { + "id": "The Euro Cash Compendium", + "message": "The Euro Cash Compendium", + "translation": "" + }, + { + "id": "United in", + "message": "United in", + "translation": "" + }, + { + "id": "diversity", + "message": "diversity", + "translation": "" + }, + { + "id": "cash", + "message": "cash", + "translation": "" + }, + { + "id": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "message": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "translation": "" + }, + { + "id": "Select Your Language", + "message": "Select Your Language", + "translation": "" + }, + { + "id": "Select your preferred language to use on the site.", + "message": "Select your preferred language to use on the site.", + "translation": "" + }, + { + "id": "Eurozone Languages", + "message": "Eurozone Languages", + "translation": "" + }, + { + "id": "Other Languages", + "message": "Other Languages", + "translation": "" + }, + { + "id": "Home", + "message": "Home", + "translation": "" + }, + { + "id": "News", + "message": "News", + "translation": "" + }, + { + "id": "Coin Collecting", + "message": "Coin Collecting", + "translation": "" + }, + { + "id": "Coins", + "message": "Coins", + "translation": "" + }, + { + "id": "Banknotes", + "message": "Banknotes", + "translation": "" + }, + { + "id": "Jargon", + "message": "Jargon", + "translation": "" + }, + { + "id": "Discord", + "message": "Discord", + "translation": "" + }, + { + "id": "About", + "message": "About", + "translation": "" + }, + { + "id": "Language", + "message": "Language", + "translation": "" + } + ] +} \ No newline at end of file diff --git a/src/rosetta/el/messages.gotext.json b/src/rosetta/el/messages.gotext.json new file mode 100644 index 0000000..c903fa9 --- /dev/null +++ b/src/rosetta/el/messages.gotext.json @@ -0,0 +1,315 @@ +{ + "language": "el", + "messages": [ + { + "id": "Andorra", + "message": "Andorra", + "translation": "" + }, + { + "id": "Austria", + "message": "Austria", + "translation": "" + }, + { + "id": "Belgium", + "message": "Belgium", + "translation": "" + }, + { + "id": "Cyprus", + "message": "Cyprus", + "translation": "" + }, + { + "id": "Germany", + "message": "Germany", + "translation": "" + }, + { + "id": "Estonia", + "message": "Estonia", + "translation": "" + }, + { + "id": "Spain", + "message": "Spain", + "translation": "" + }, + { + "id": "Finland", + "message": "Finland", + "translation": "" + }, + { + "id": "France", + "message": "France", + "translation": "" + }, + { + "id": "Greece", + "message": "Greece", + "translation": "" + }, + { + "id": "Croatia", + "message": "Croatia", + "translation": "" + }, + { + "id": "Ireland", + "message": "Ireland", + "translation": "" + }, + { + "id": "Italy", + "message": "Italy", + "translation": "" + }, + { + "id": "Lithuania", + "message": "Lithuania", + "translation": "" + }, + { + "id": "Luxembourg", + "message": "Luxembourg", + "translation": "" + }, + { + "id": "Latvia", + "message": "Latvia", + "translation": "" + }, + { + "id": "Monaco", + "message": "Monaco", + "translation": "" + }, + { + "id": "Malta", + "message": "Malta", + "translation": "" + }, + { + "id": "Netherlands", + "message": "Netherlands", + "translation": "" + }, + { + "id": "Portugal", + "message": "Portugal", + "translation": "" + }, + { + "id": "Slovenia", + "message": "Slovenia", + "translation": "" + }, + { + "id": "Slovakia", + "message": "Slovakia", + "translation": "" + }, + { + "id": "San Marino", + "message": "San Marino", + "translation": "" + }, + { + "id": "Vatican City", + "message": "Vatican City", + "translation": "" + }, + { + "id": "Page Not Found", + "message": "Page Not Found", + "translation": "" + }, + { + "id": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "message": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "translation": "" + }, + { + "id": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "message": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "translation": "" + }, + { + "id": "About Us", + "message": "About Us", + "translation": "" + }, + { + "id": "Open Source", + "message": "Open Source", + "translation": "" + }, + { + "id": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "message": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "translation": "" + }, + { + "id": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "message": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "translation": "" + }, + { + "id": "\u003c/a\u003e", + "message": "\u003c/a\u003e", + "translation": "" + }, + { + "id": "Contact Us", + "message": "Contact Us", + "translation": "" + }, + { + "id": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "message": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "translation": "" + }, + { + "id": "Special Thanks", + "message": "Special Thanks", + "translation": "" + }, + { + "id": "Development", + "message": "Development", + "translation": "" + }, + { + "id": "Research", + "message": "Research", + "translation": "" + }, + { + "id": "Translations", + "message": "Translations", + "translation": "" + }, + { + "id": "British- \u0026 American English", + "message": "British- \u0026 American English", + "translation": "" + }, + { + "id": "Icelandic", + "message": "Icelandic", + "translation": "" + }, + { + "id": "Found a mistake or want to contribute missing information?", + "message": "Found a mistake or want to contribute missing information?", + "translation": "" + }, + { + "id": "Feel free to contact us!", + "message": "Feel free to contact us!", + "translation": "" + }, + { + "id": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "message": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "translation": "" + }, + { + "id": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "translation": "" + }, + { + "id": "The Euro Cash Compendium", + "message": "The Euro Cash Compendium", + "translation": "" + }, + { + "id": "United in", + "message": "United in", + "translation": "" + }, + { + "id": "diversity", + "message": "diversity", + "translation": "" + }, + { + "id": "cash", + "message": "cash", + "translation": "" + }, + { + "id": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "message": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "translation": "" + }, + { + "id": "Select Your Language", + "message": "Select Your Language", + "translation": "" + }, + { + "id": "Select your preferred language to use on the site.", + "message": "Select your preferred language to use on the site.", + "translation": "" + }, + { + "id": "Eurozone Languages", + "message": "Eurozone Languages", + "translation": "" + }, + { + "id": "Other Languages", + "message": "Other Languages", + "translation": "" + }, + { + "id": "Home", + "message": "Home", + "translation": "" + }, + { + "id": "News", + "message": "News", + "translation": "" + }, + { + "id": "Coin Collecting", + "message": "Coin Collecting", + "translation": "" + }, + { + "id": "Coins", + "message": "Coins", + "translation": "" + }, + { + "id": "Banknotes", + "message": "Banknotes", + "translation": "" + }, + { + "id": "Jargon", + "message": "Jargon", + "translation": "" + }, + { + "id": "Discord", + "message": "Discord", + "translation": "" + }, + { + "id": "About", + "message": "About", + "translation": "" + }, + { + "id": "Language", + "message": "Language", + "translation": "" + } + ] +} \ No newline at end of file diff --git a/src/rosetta/en/messages.gotext.json b/src/rosetta/en/messages.gotext.json new file mode 100644 index 0000000..0e09e33 --- /dev/null +++ b/src/rosetta/en/messages.gotext.json @@ -0,0 +1,439 @@ +{ + "language": "en", + "messages": [ + { + "id": "Andorra", + "message": "Andorra", + "translation": "Andorra", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Austria", + "message": "Austria", + "translation": "Austria", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Belgium", + "message": "Belgium", + "translation": "Belgium", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Cyprus", + "message": "Cyprus", + "translation": "Cyprus", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Germany", + "message": "Germany", + "translation": "Germany", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Estonia", + "message": "Estonia", + "translation": "Estonia", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Spain", + "message": "Spain", + "translation": "Spain", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Finland", + "message": "Finland", + "translation": "Finland", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "France", + "message": "France", + "translation": "France", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Greece", + "message": "Greece", + "translation": "Greece", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Croatia", + "message": "Croatia", + "translation": "Croatia", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Ireland", + "message": "Ireland", + "translation": "Ireland", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Italy", + "message": "Italy", + "translation": "Italy", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Lithuania", + "message": "Lithuania", + "translation": "Lithuania", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Luxembourg", + "message": "Luxembourg", + "translation": "Luxembourg", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Latvia", + "message": "Latvia", + "translation": "Latvia", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Monaco", + "message": "Monaco", + "translation": "Monaco", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Malta", + "message": "Malta", + "translation": "Malta", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Netherlands", + "message": "Netherlands", + "translation": "Netherlands", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Portugal", + "message": "Portugal", + "translation": "Portugal", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Slovenia", + "message": "Slovenia", + "translation": "Slovenia", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Slovakia", + "message": "Slovakia", + "translation": "Slovakia", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "San Marino", + "message": "San Marino", + "translation": "San Marino", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Vatican City", + "message": "Vatican City", + "translation": "Vatican City", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Page Not Found", + "message": "Page Not Found", + "translation": "Page Not Found", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "message": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "translation": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "message": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "translation": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "About Us", + "message": "About Us", + "translation": "About Us", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Open Source", + "message": "Open Source", + "translation": "Open Source", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "message": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "translation": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "message": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "translation": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "\u003c/a\u003e", + "message": "\u003c/a\u003e", + "translation": "\u003c/a\u003e", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Contact Us", + "message": "Contact Us", + "translation": "Contact Us", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "message": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "translation": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Special Thanks", + "message": "Special Thanks", + "translation": "Special Thanks", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Development", + "message": "Development", + "translation": "Development", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Research", + "message": "Research", + "translation": "Research", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Translations", + "message": "Translations", + "translation": "Translations", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "British- \u0026 American English", + "message": "British- \u0026 American English", + "translation": "British- \u0026 American English", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Icelandic", + "message": "Icelandic", + "translation": "Icelandic", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Found a mistake or want to contribute missing information?", + "message": "Found a mistake or want to contribute missing information?", + "translation": "Found a mistake or want to contribute missing information?", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Feel free to contact us!", + "message": "Feel free to contact us!", + "translation": "Feel free to contact us!", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "message": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "translation": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "translation": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "The Euro Cash Compendium", + "message": "The Euro Cash Compendium", + "translation": "The Euro Cash Compendium", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "United in", + "message": "United in", + "translation": "United in", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "diversity", + "message": "diversity", + "translation": "diversity", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "cash", + "message": "cash", + "translation": "cash", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "message": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "translation": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Select Your Language", + "message": "Select Your Language", + "translation": "Select Your Language", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Select your preferred language to use on the site.", + "message": "Select your preferred language to use on the site.", + "translation": "Select your preferred language to use on the site.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Eurozone Languages", + "message": "Eurozone Languages", + "translation": "Eurozone Languages", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Other Languages", + "message": "Other Languages", + "translation": "Other Languages", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Home", + "message": "Home", + "translation": "Home", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "News", + "message": "News", + "translation": "News", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Coin Collecting", + "message": "Coin Collecting", + "translation": "Coin Collecting", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Coins", + "message": "Coins", + "translation": "Coins", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Banknotes", + "message": "Banknotes", + "translation": "Banknotes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Jargon", + "message": "Jargon", + "translation": "Jargon", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Discord", + "message": "Discord", + "translation": "Discord", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "About", + "message": "About", + "translation": "About", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Language", + "message": "Language", + "translation": "Language", + "translatorComment": "Copied from source.", + "fuzzy": true + } + ] +} \ No newline at end of file diff --git a/src/rosetta/nl/messages.gotext.json b/src/rosetta/nl/messages.gotext.json new file mode 100644 index 0000000..ea7134a --- /dev/null +++ b/src/rosetta/nl/messages.gotext.json @@ -0,0 +1,315 @@ +{ + "language": "nl", + "messages": [ + { + "id": "Andorra", + "message": "Andorra", + "translation": "" + }, + { + "id": "Austria", + "message": "Austria", + "translation": "" + }, + { + "id": "Belgium", + "message": "Belgium", + "translation": "" + }, + { + "id": "Cyprus", + "message": "Cyprus", + "translation": "" + }, + { + "id": "Germany", + "message": "Germany", + "translation": "" + }, + { + "id": "Estonia", + "message": "Estonia", + "translation": "" + }, + { + "id": "Spain", + "message": "Spain", + "translation": "" + }, + { + "id": "Finland", + "message": "Finland", + "translation": "" + }, + { + "id": "France", + "message": "France", + "translation": "" + }, + { + "id": "Greece", + "message": "Greece", + "translation": "" + }, + { + "id": "Croatia", + "message": "Croatia", + "translation": "" + }, + { + "id": "Ireland", + "message": "Ireland", + "translation": "" + }, + { + "id": "Italy", + "message": "Italy", + "translation": "" + }, + { + "id": "Lithuania", + "message": "Lithuania", + "translation": "" + }, + { + "id": "Luxembourg", + "message": "Luxembourg", + "translation": "" + }, + { + "id": "Latvia", + "message": "Latvia", + "translation": "" + }, + { + "id": "Monaco", + "message": "Monaco", + "translation": "" + }, + { + "id": "Malta", + "message": "Malta", + "translation": "" + }, + { + "id": "Netherlands", + "message": "Netherlands", + "translation": "" + }, + { + "id": "Portugal", + "message": "Portugal", + "translation": "" + }, + { + "id": "Slovenia", + "message": "Slovenia", + "translation": "" + }, + { + "id": "Slovakia", + "message": "Slovakia", + "translation": "" + }, + { + "id": "San Marino", + "message": "San Marino", + "translation": "" + }, + { + "id": "Vatican City", + "message": "Vatican City", + "translation": "" + }, + { + "id": "Page Not Found", + "message": "Page Not Found", + "translation": "" + }, + { + "id": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "message": "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s.", + "translation": "" + }, + { + "id": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "message": "\u003ca href=\"mailto:mail@euro-cash.eu\"\u003email@euro-cash.eu\u003c/a\u003e", + "translation": "" + }, + { + "id": "About Us", + "message": "About Us", + "translation": "" + }, + { + "id": "Open Source", + "message": "Open Source", + "translation": "" + }, + { + "id": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "message": "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site.", + "translation": "" + }, + { + "id": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "message": "\u003ca href=\"https://git.thomasvoss.com/www.euro-cash.eu\" target=\"_blank\"\u003e", + "translation": "" + }, + { + "id": "\u003c/a\u003e", + "message": "\u003c/a\u003e", + "translation": "" + }, + { + "id": "Contact Us", + "message": "Contact Us", + "translation": "" + }, + { + "id": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "message": "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord.", + "translation": "" + }, + { + "id": "Special Thanks", + "message": "Special Thanks", + "translation": "" + }, + { + "id": "Development", + "message": "Development", + "translation": "" + }, + { + "id": "Research", + "message": "Research", + "translation": "" + }, + { + "id": "Translations", + "message": "Translations", + "translation": "" + }, + { + "id": "British- \u0026 American English", + "message": "British- \u0026 American English", + "translation": "" + }, + { + "id": "Icelandic", + "message": "Icelandic", + "translation": "" + }, + { + "id": "Found a mistake or want to contribute missing information?", + "message": "Found a mistake or want to contribute missing information?", + "translation": "" + }, + { + "id": "Feel free to contact us!", + "message": "Feel free to contact us!", + "translation": "" + }, + { + "id": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "message": "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience.", + "translation": "" + }, + { + "id": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", + "translation": "" + }, + { + "id": "The Euro Cash Compendium", + "message": "The Euro Cash Compendium", + "translation": "" + }, + { + "id": "United in", + "message": "United in", + "translation": "" + }, + { + "id": "diversity", + "message": "diversity", + "translation": "" + }, + { + "id": "cash", + "message": "cash", + "translation": "" + }, + { + "id": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "message": "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors.", + "translation": "" + }, + { + "id": "Select Your Language", + "message": "Select Your Language", + "translation": "" + }, + { + "id": "Select your preferred language to use on the site.", + "message": "Select your preferred language to use on the site.", + "translation": "" + }, + { + "id": "Eurozone Languages", + "message": "Eurozone Languages", + "translation": "" + }, + { + "id": "Other Languages", + "message": "Other Languages", + "translation": "" + }, + { + "id": "Home", + "message": "Home", + "translation": "" + }, + { + "id": "News", + "message": "News", + "translation": "" + }, + { + "id": "Coin Collecting", + "message": "Coin Collecting", + "translation": "" + }, + { + "id": "Coins", + "message": "Coins", + "translation": "" + }, + { + "id": "Banknotes", + "message": "Banknotes", + "translation": "" + }, + { + "id": "Jargon", + "message": "Jargon", + "translation": "" + }, + { + "id": "Discord", + "message": "Discord", + "translation": "" + }, + { + "id": "About", + "message": "About", + "translation": "" + }, + { + "id": "Language", + "message": "Language", + "translation": "" + } + ] +} \ No newline at end of file diff --git a/src/templates.go b/src/templates.go new file mode 100644 index 0000000..839a6fb --- /dev/null +++ b/src/templates.go @@ -0,0 +1,56 @@ +package src + +import ( + "embed" + "html/template" + "strings" + + "git.thomasvoss.com/euro-cash.eu/src/mintage" +) + +type templateData struct { + Printer Printer + Code, Type string + Mintages mintage.Data + Countries []country +} + +var ( + //go:embed templates/*.html.tmpl + templateFS embed.FS + notFoundTmpl = buildTemplate("404") + errorTmpl = buildTemplate("error") + templates = map[string]*template.Template{ + "/": buildTemplate("index"), + "/about": buildTemplate("about"), + "/language": buildTemplate("language"), + } + funcmap = map[string]any{ + "safe": asHTML, + "locales": locales, + "toUpper": strings.ToUpper, + } +) + +func buildTemplate(names ...string) *template.Template { + names = append([]string{"base", "navbar"}, names...) + for i, s := range names { + names[i] = "templates/" + s + ".html.tmpl" + } + return template.Must(template. + New("base.html.tmpl"). + Funcs(funcmap). + ParseFS(templateFS, names...)) +} + +func asHTML(s string) template.HTML { + return template.HTML(s) +} + +func locales() []locale { + return Locales[:] +} + +func (td templateData) T(fmt string, args ...any) string { + return td.Printer.T(fmt, args...) +} diff --git a/src/templates/404.html.tmpl b/src/templates/404.html.tmpl new file mode 100644 index 0000000..3771a0f --- /dev/null +++ b/src/templates/404.html.tmpl @@ -0,0 +1,11 @@ +{{ define "content" }} +
+ {{ template "navbar" . }} +

{{ .T "Page Not Found" }}

+
+
+

+ {{ .T "The page you were looking for does not exist. If you believe this is a mistake then don’t hesitate to contact @onetruemangoman on Discord or email us at %s." `mail@euro-cash.eu` | safe }} +

+
+{{ end }} diff --git a/src/templates/about.html.tmpl b/src/templates/about.html.tmpl new file mode 100644 index 0000000..aed356a --- /dev/null +++ b/src/templates/about.html.tmpl @@ -0,0 +1,41 @@ +{{ define "content" }} +
+ {{ template "navbar" . }} +

{{ .T "About Us" }}

+
+
+

{{ .T "Open Source" }}

+

+ {{ .T "This website is an open project, and a collaboration between developers, translators, and researchers. All source code, data, images, and more for the website are open source and can be found %shere%s. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with anyof the content on this site." `` `` | safe }} +

+

{{ .T "Contact Us" }}

+

+ {{ .T "While we try to stay as up-to-date as possible and to fact check our information, it is always possible that we get something wrong, lack a translation, or are missing some piece of data you may have. In such a case don’t hesitate to contact us; we’ll try to get the site updated or fixed as soon as possible. You are always free to contribute via a git patch if you are more technically included, but if not you can always send an email to %s or contact ‘@onetruemangoman’ on Discord." `mail@euro-cash.eu` | safe }} +

+

{{ .T "Special Thanks" }}

+ + + + + + + + + + + + + +
{{ .T "Development" }}{{ .T "Research" }}{{ .T "Translations" }}
+ Jessika Wexler, + Lyyli Savolainen, + Ralf Nadel + + Elín Hjartardóttir, + Storm Sørensen + + Thomas Voss, + Védís Indriðadóttir +
+
+{{ end }} diff --git a/src/templates/base.html.tmpl b/src/templates/base.html.tmpl new file mode 100644 index 0000000..0d5e731 --- /dev/null +++ b/src/templates/base.html.tmpl @@ -0,0 +1,43 @@ + + + + + + + + Euro Cash + + + + {{ template "content" . }} + + + diff --git a/src/templates/error.html.tmpl b/src/templates/error.html.tmpl new file mode 100644 index 0000000..28ef0a3 --- /dev/null +++ b/src/templates/error.html.tmpl @@ -0,0 +1,14 @@ +{{ define "content" }} +
+ {{ template "navbar" . }} +

{{ .Code }} {{ .Msg }}

+
+
+

+ {{ .T "If you’re seeing this page, it means that something went wrong on our end that we need to fix. Our team has been notified of this error, and we apologise for the inconvenience." }} +

+

+ {{ .T "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s." `` | safe }} +

+
+{{ end }} diff --git a/src/templates/index.html.tmpl b/src/templates/index.html.tmpl new file mode 100644 index 0000000..9a26046 --- /dev/null +++ b/src/templates/index.html.tmpl @@ -0,0 +1,18 @@ +{{ define "content" }} +
+ {{ template "navbar" . }} +
+

{{ .T "The Euro Cash Compendium" }}

+

+ {{ .T "United in" }} + {{ .T "diversity" }} + {{ .T "cash" }} +

+
+
+
+

+ {{ .T "Welcome to the Euro Cash Compendium. This sites aims to be a resource for you to discover everything there is to know about the coins and banknotes of the Euro, a currency that spans 26 countries and 350 million people. We also have dedicated sections of the site for collectors." }} +

+
+{{ end }} diff --git a/src/templates/language.html.tmpl b/src/templates/language.html.tmpl new file mode 100644 index 0000000..f7affa1 --- /dev/null +++ b/src/templates/language.html.tmpl @@ -0,0 +1,48 @@ +{{ define "content" }} +
+ {{ template "navbar" . }} +

{{ .T "Select Your Language" }}

+
+
+

+ {{ .T "Select your preferred language to use on the site." }} +

+

+ If you are an American user, it’s suggested that you select + American English instead of British English. This will ensure that + dates will be formatted with the month before the day. +

+
+

{{ .T "Eurozone Languages" }}

+ {{ template "langgrid" true }} +

{{ .T "Other Languages" }}

+ {{ template "langgrid" false }} +
+{{ end }} + +{{ define "langgrid" }} +{{ $ez := . }} +
+
+ {{ range locales }} + {{ if eq $ez .Eurozone }} + + {{ end }} + {{ end }} +
+
+{{ end }} diff --git a/src/templates/navbar.html.tmpl b/src/templates/navbar.html.tmpl new file mode 100644 index 0000000..90f3cc7 --- /dev/null +++ b/src/templates/navbar.html.tmpl @@ -0,0 +1,228 @@ +{{ define "navbar" }} +
+{{ end }} -- cgit v1.2.3