diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/countries.go | 5 | ||||
-rw-r--r-- | src/dbx/.gitignore | 1 | ||||
-rw-r--r-- | src/dbx/db.go | 239 | ||||
-rw-r--r-- | src/dbx/mintages.go | 65 | ||||
-rw-r--r-- | src/dbx/sql/000-genesis.sql | 46 | ||||
-rw-r--r-- | src/dbx/users.go | 68 | ||||
-rw-r--r-- | src/http.go | 57 | ||||
-rw-r--r-- | src/i18n.go | 83 | ||||
-rw-r--r-- | src/mintage/parser.go | 297 | ||||
-rw-r--r-- | src/mintage/parser_test.go | 233 | ||||
-rw-r--r-- | src/rosetta/bg/messages.gotext.json | 170 | ||||
-rw-r--r-- | src/rosetta/el/messages.gotext.json | 170 | ||||
-rw-r--r-- | src/rosetta/en/messages.gotext.json | 238 | ||||
-rw-r--r-- | src/rosetta/nl/messages.gotext.json | 170 | ||||
-rw-r--r-- | src/templates.go | 85 | ||||
-rw-r--r-- | src/templates/-navbar.html.tmpl | 2 | ||||
-rw-r--r-- | src/templates/banknotes-codes.html.tmpl | 352 | ||||
-rw-r--r-- | src/templates/banknotes.html.tmpl | 49 | ||||
-rw-r--r-- | src/templates/coins-designs-ad.html.tmpl | 2 | ||||
-rw-r--r-- | src/templates/coins-designs-be.html.tmpl | 5 | ||||
-rw-r--r-- | src/templates/coins-designs-hr.html.tmpl | 8 | ||||
-rw-r--r-- | src/templates/coins-mintages.html.tmpl | 8 |
22 files changed, 1680 insertions, 673 deletions
diff --git a/src/countries.go b/src/countries.go index b8a27bd..0068c8f 100644 --- a/src/countries.go +++ b/src/countries.go @@ -1,4 +1,4 @@ -package src +package app import ( "slices" @@ -12,11 +12,12 @@ type country struct { } func sortedCountries(p Printer) []country { - /* TODO: Add Kosovo and Montenegro */ xs := []country{ {Code: "ad", Name: p.T("Andorra")}, {Code: "at", Name: p.T("Austria")}, {Code: "be", Name: p.T("Belgium")}, + /* TODO(2026): Add Bulgaria */ + /* {Code: "bg", Name: p.T("Bulgaria")}, */ {Code: "cy", Name: p.T("Cyprus")}, {Code: "de", Name: p.T("Germany")}, {Code: "ee", Name: p.T("Estonia")}, diff --git a/src/dbx/.gitignore b/src/dbx/.gitignore new file mode 100644 index 0000000..d14a707 --- /dev/null +++ b/src/dbx/.gitignore @@ -0,0 +1 @@ +sql/last.sql
\ No newline at end of file diff --git a/src/dbx/db.go b/src/dbx/db.go new file mode 100644 index 0000000..fcb345e --- /dev/null +++ b/src/dbx/db.go @@ -0,0 +1,239 @@ +package dbx + +import ( + "database/sql" + "fmt" + "io/fs" + "log" + "reflect" + "sort" + "strings" + + "git.thomasvoss.com/euro-cash.eu/pkg/atexit" + . "git.thomasvoss.com/euro-cash.eu/pkg/try" + "github.com/mattn/go-sqlite3" +) + +var ( + db *sql.DB + DBName string +) + +func Init(sqlDir fs.FS) { + db = Try2(sql.Open("sqlite3", DBName)) + Try(db.Ping()) + atexit.Register(Close) + Try(applyMigrations(sqlDir)) + + /* TODO: Remove debug code */ + Try(CreateUser(User{ + Email: "mail@thomasvoss.com", + Username: "Thomas", + Password: "69", + AdminP: true, + })) + Try(CreateUser(User{ + Email: "foo@BAR.baz", + Username: "Foobar", + Password: "420", + AdminP: false, + })) + Try2(GetMintages("ad")) +} + +func Close() { + db.Close() +} + +func applyMigrations(dir fs.FS) error { + var latest int + migratedp := true + + rows, err := db.Query("SELECT latest FROM migration") + if err != nil { + e, ok := err.(sqlite3.Error) + /* IDK if there is a better way to do this… lol */ + if ok && e.Error() == "no such table: migration" { + migratedp = false + } else { + return err + } + } else { + defer rows.Close() + } + + if migratedp { + rows.Next() + if err := rows.Err(); err != nil { + return err + } + if err := rows.Scan(&latest); err != nil { + return err + } + } else { + latest = -1 + } + + files, err := fs.ReadDir(dir, ".") + if err != nil { + return err + } + + var ( + last string + scripts []string + ) + + for _, f := range files { + if n := f.Name(); n == "last.sql" { + last = n + } else { + scripts = append(scripts, f.Name()) + } + } + + sort.Strings(scripts) + for _, f := range scripts[latest+1:] { + qry, err := fs.ReadFile(dir, f) + if err != nil { + return err + } + + tx, err := db.Begin() + if err != nil { + return err + } + + if _, err := tx.Exec(string(qry)); err != nil { + tx.Rollback() + return fmt.Errorf("error in ‘%s’: %w", f, err) + } + + var n int + if _, err := fmt.Sscanf(f, "%d", &n); err != nil { + return err + } + _, err = tx.Exec("UPDATE migration SET latest = ? WHERE id = 1", n) + if err != nil { + return err + } + + if err := tx.Commit(); err != nil { + return err + } + log.Printf("Applied database migration ‘%s’\n", f) + } + + if last != "" { + qry, err := fs.ReadFile(dir, last) + if err != nil { + return err + } + if _, err := db.Exec(string(qry)); err != nil { + return fmt.Errorf("error in ‘%s’: %w", last, err) + } + log.Printf("Ran ‘%s’\n", last) + } + + return nil +} + +func scanToStruct[T any](rs *sql.Rows) (T, error) { + return scanToStruct2[T](rs, true) +} + +func scanToStructs[T any](rs *sql.Rows) ([]T, error) { + xs := []T{} + for rs.Next() { + x, err := scanToStruct2[T](rs, false) + if err != nil { + return nil, err + } + xs = append(xs, x) + } + return xs, rs.Err() +} + +func scanToStruct2[T any](rs *sql.Rows, callNextP bool) (T, error) { + var t, zero T + + cols, err := rs.Columns() + if err != nil { + return zero, err + } + + v := reflect.ValueOf(&t).Elem() + tType := v.Type() + + rawValues := make([]any, len(cols)) + for i := range rawValues { + var zero any + rawValues[i] = &zero + } + + if callNextP { + rs.Next() + if err := rs.Err(); err != nil { + return zero, err + } + } + if err := rs.Scan(rawValues...); err != nil { + return zero, err + } + + /* col idx → [field idx, array idx] */ + arrayTargets := make(map[int][2]int) + colToField := make(map[string]int) + + for i := 0; i < tType.NumField(); i++ { + field := tType.Field(i) + tag := field.Tag.Get("db") + if tag == "" { + continue + } + + if strings.Contains(tag, ";") { + dbcols := strings.Split(tag, ";") + fv := v.Field(i) + if fv.Kind() != reflect.Array { + return zero, fmt.Errorf("field ‘%s’ is not array", + field.Name) + } + if len(dbcols) != fv.Len() { + return zero, fmt.Errorf("field ‘%s’ array length mismatch", + field.Name) + } + for j, colName := range cols { + for k, dbColName := range dbcols { + if colName == dbColName { + arrayTargets[j] = [2]int{i, k} + } + } + } + } else { + colToField[tag] = i + } + } + + for i, col := range cols { + vp := rawValues[i].(*any) + if fieldIdx, ok := colToField[col]; ok { + assignValue(v.Field(fieldIdx), *vp) + } else if target, ok := arrayTargets[i]; ok { + assignValue(v.Field(target[0]).Index(target[1]), *vp) + } + } + + return t, nil +} + +func assignValue(fv reflect.Value, val any) { + if val == nil { + fv.Set(reflect.Zero(fv.Type())) + return + } + v := reflect.ValueOf(val) + if v.Type().ConvertibleTo(fv.Type()) { + fv.Set(v.Convert(fv.Type())) + } +} diff --git a/src/dbx/mintages.go b/src/dbx/mintages.go new file mode 100644 index 0000000..4a6d5d3 --- /dev/null +++ b/src/dbx/mintages.go @@ -0,0 +1,65 @@ +package dbx + +type MintageData struct { + Standard []MSRow + Commemorative []MCRow +} + +type MSRow struct { + Type int `db:"type"` + Year int `db:"year"` + Mintmark string `db:"mintmark"` + Mintages [ndenoms]int `db:"€0,01;€0,02;€0,05;€0,10;€0,20;€0,50;€1,00;€2,00"` + Reference string `db:"reference"` +} + +type MCRow struct { + Type int `db:"type"` + Year int `db:"year"` + Name string `db:"name"` + Number int `db:"number"` + Mintmark string `db:"mintmark"` + Mintage int `db:"mintage"` + Reference string `db:"reference"` +} + +/* DO NOT REORDER! */ +const ( + TypeCirc = iota + TypeNifc + TypeProof +) + +/* DO NOT REORDER! */ +const ( + MintageUnknown = -iota - 1 + MintageInvalid +) + +const ndenoms = 8 + +func GetMintages(country string) (MintageData, error) { + var zero MintageData + + srows, err := db.Query(`SELECT * FROM mintages_s WHERE country = ?`, country) + if err != nil { + return zero, err + } + defer srows.Close() + xs, err := scanToStructs[MSRow](srows) + if err != nil { + return zero, err + } + + crows, err := db.Query(`SELECT * FROM mintages_c WHERE country = ?`, country) + if err != nil { + return zero, err + } + defer crows.Close() + ys, err := scanToStructs[MCRow](crows) + if err != nil { + return zero, err + } + + return MintageData{xs, ys}, nil +} diff --git a/src/dbx/sql/000-genesis.sql b/src/dbx/sql/000-genesis.sql new file mode 100644 index 0000000..56ae7c3 --- /dev/null +++ b/src/dbx/sql/000-genesis.sql @@ -0,0 +1,46 @@ +PRAGMA encoding = "UTF-8"; + +CREATE TABLE migration ( + id INTEGER PRIMARY KEY CHECK (id = 1), + latest INTEGER +); +INSERT INTO migration (id, latest) VALUES (1, -1); + +CREATE TABLE mintages_s ( + country CHAR(2) NOT NULL COLLATE BINARY + CHECK(length(country) = 2), + type INTEGER NOT NULL -- Codes correspond to contants in mintages.go + CHECK(type BETWEEN 0 AND 2), + year INTEGER NOT NULL, + mintmark TEXT, + [€0,01] INTEGER, + [€0,02] INTEGER, + [€0,05] INTEGER, + [€0,10] INTEGER, + [€0,20] INTEGER, + [€0,50] INTEGER, + [€1,00] INTEGER, + [€2,00] INTEGER, + reference TEXT +); + +CREATE TABLE mintages_c ( + country CHAR(2) NOT NULL COLLATE BINARY + CHECK(length(country) = 2), + type INTEGER NOT NULL -- Codes correspond to contants in mintages.go + CHECK(type BETWEEN 0 AND 2), + name TEXT NOT NULL, + year INTEGER NOT NULL, + number INTEGER NOT NULL, + mintmark TEXT, + mintage INTEGER, + reference TEXT +); + +CREATE TABLE users ( + email TEXT COLLATE BINARY, + username TEXT COLLATE BINARY, + password TEXT COLLATE BINARY, + adminp INTEGER, + translates TEXT COLLATE BINARY +);
\ No newline at end of file diff --git a/src/dbx/users.go b/src/dbx/users.go new file mode 100644 index 0000000..e2270db --- /dev/null +++ b/src/dbx/users.go @@ -0,0 +1,68 @@ +package dbx + +import ( + "database/sql" + "errors" + + "golang.org/x/crypto/bcrypt" + "golang.org/x/text/unicode/norm" +) + +type User struct { + Email string `db:"email"` + Username string `db:"username"` + Password string `db:"password"` + AdminP bool `db:"adminp"` + Translates string `db:"translates"` +} + +var LoginFailed = errors.New("No user with the given username and password") + +func CreateUser(user User) error { + user.Username = norm.NFC.String(user.Username) + user.Password = norm.NFC.String(user.Password) + + hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 15) + if err != nil { + return err + } + + _, err = db.Exec(` + INSERT INTO users ( + email, + username, + password, + adminp, + translates + ) VALUES (?, ?, ?, ?, ?) + `, user.Email, user.Username, string(hash), user.AdminP, user.Translates) + return err +} + +func Login(username, password string) (User, error) { + username = norm.NFC.String(username) + password = norm.NFC.String(password) + + /* TODO: Pass a context here? */ + rs, err := db.Query(`SELECT * FROM users WHERE username = ?`, username) + if err != nil { + return User{}, err + } + u, err := scanToStruct[User](rs) + + switch { + case errors.Is(err, sql.ErrNoRows): + return User{}, LoginFailed + case err != nil: + return User{}, err + } + + err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) + switch { + case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword): + return User{}, LoginFailed + case err != nil: + return User{}, err + } + return u, nil +} diff --git a/src/http.go b/src/http.go index 3feda8c..6feb865 100644 --- a/src/http.go +++ b/src/http.go @@ -1,4 +1,4 @@ -package src +package app import ( "cmp" @@ -8,14 +8,14 @@ import ( "log" "math" "net/http" - "os" - "path/filepath" "slices" "strconv" "strings" + . "git.thomasvoss.com/euro-cash.eu/pkg/try" + + "git.thomasvoss.com/euro-cash.eu/src/dbx" "git.thomasvoss.com/euro-cash.eu/src/email" - "git.thomasvoss.com/euro-cash.eu/src/mintage" ) type middleware = func(http.Handler) http.Handler @@ -29,6 +29,7 @@ func Run(port int) { mwareC := chain(mwareB, countryHandler) // [C]ountry mwareM := chain(mwareC, mintageHandler) // [M]intage + mux.Handle("GET /codes/", fs) mux.Handle("GET /designs/", fs) mux.Handle("GET /favicon.ico", fs) mux.Handle("GET /fonts/", fs) @@ -42,7 +43,7 @@ func Run(port int) { portStr := ":" + strconv.Itoa(port) log.Println("Listening on", portStr) - log.Fatal(http.ListenAndServe(portStr, mux)) + Try(http.ListenAndServe(portStr, mux)) } func chain(xs ...middleware) middleware { @@ -137,20 +138,14 @@ func mintageHandler(next http.Handler) http.Handler { 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) + var err error + td.Mintages, err = dbx.GetMintages(td.Code) if err != nil { throwError(http.StatusInternalServerError, err, w, r) return } + processMintages(&td.Mintages, td.Type) next.ServeHTTP(w, r) }) } @@ -184,7 +179,7 @@ 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) + log.Println(err) } }() errorTmpl.Execute(w, struct { @@ -195,3 +190,35 @@ func throwError(status int, err error, w http.ResponseWriter, r *http.Request) { Msg: http.StatusText(status), }) } + +func processMintages(md *dbx.MintageData, typeStr string) { + var typ int + switch typeStr { + case "nifc": + typ = dbx.TypeNifc + case "proof": + typ = dbx.TypeProof + default: + typ = dbx.TypeCirc + } + + md.Standard = slices.DeleteFunc(md.Standard, + func(x dbx.MSRow) bool { return x.Type != typ }) + md.Commemorative = slices.DeleteFunc(md.Commemorative, + func(x dbx.MCRow) bool { return x.Type != typ }) + slices.SortFunc(md.Standard, func(x, y dbx.MSRow) int { + if x.Year != y.Year { + return x.Year - y.Year + } + return strings.Compare(x.Mintmark, y.Mintmark) + }) + slices.SortFunc(md.Commemorative, func(x, y dbx.MCRow) int { + if x.Year != y.Year { + return x.Year - y.Year + } + if x.Number != y.Number { + return x.Number - y.Number + } + return strings.Compare(x.Mintmark, y.Mintmark) + }) +} diff --git a/src/i18n.go b/src/i18n.go index e8ad2ea..cb54e28 100644 --- a/src/i18n.go +++ b/src/i18n.go @@ -1,7 +1,7 @@ -//go:generate gotext -srclang=en -dir=rosetta extract -lang=bg,el,en,nl . +//go:generate go tool gotext -srclang=en -dir=rosetta update -lang=bg,el,en,nl . //go:generate ../exttmpl -package src +package app import ( "fmt" @@ -23,10 +23,12 @@ type Printer struct { type locale struct { Bcp, Name string Eurozone, Enabled bool - dateFmt, moneyFmt string + dateFmt string } var ( + /* To determine the correct date format to use, use the ‘datefmt’ script in + the repository root */ Locales = [...]locale{ { Bcp: "ca", @@ -92,6 +94,13 @@ var ( Enabled: false, }, { + Bcp: "hr", + Name: "hrvatski", + dateFmt: "02. 01. 2006.", + Eurozone: true, + Enabled: false, + }, + { Bcp: "it", Name: "italiano", dateFmt: "02/01/2006", @@ -141,13 +150,6 @@ var ( Enabled: false, }, { - Bcp: "sh", - Name: "srpskohrvatski", - dateFmt: "02. 01. 2006.", - Eurozone: true, - Enabled: false, - }, - { Bcp: "sk", Name: "slovenčina", dateFmt: "2. 1. 2006", @@ -161,16 +163,30 @@ var ( Eurozone: true, Enabled: false, }, + { + Bcp: "sv", + Name: "svenska", + dateFmt: "2006-01-02", + Eurozone: true, + Enabled: false, + }, /* Non-Eurozone locales */ { Bcp: "bg", Name: "български", dateFmt: "2.01.2006 г.", - Eurozone: false, + Eurozone: false, /* TODO(2026): Set to true */ Enabled: true, }, { + Bcp: "da", + Name: "dansk", + dateFmt: "02.01.2006", + Eurozone: false, + Enabled: false, + }, + { Bcp: "en-US", Name: "English (US)", dateFmt: "1/2/2006", @@ -178,16 +194,23 @@ var ( Enabled: false, }, { - Bcp: "ro", - Name: "română", - dateFmt: "02.01.2006", + Bcp: "hu", + Name: "magyar", + dateFmt: "2006. 01. 02.", Eurozone: false, Enabled: false, }, { - Bcp: "sq", - Name: "Shqip", - dateFmt: "2.1.2006", + Bcp: "pl", + Name: "polski", + dateFmt: "2.01.2006", + Eurozone: false, + Enabled: false, + }, + { + Bcp: "ro", + Name: "română", + dateFmt: "02.01.2006", Eurozone: false, Enabled: false, }, @@ -219,15 +242,7 @@ func init() { 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 { +func (p Printer) D(d time.Time) string { return d.Format(p.Locale.dateFmt) } @@ -244,9 +259,11 @@ func (p Printer) M(val any) string { case float64: vstr = f("%.2f", val) default: - if err := email.ServerError(badMType{"TODO"}); err != nil { - log.Print(err) - } + go func() { + if err := email.ServerError(badMType{"TODO"}); err != nil { + log.Println(err) + } + }() /* Hopefully this never happens */ vstr = "ERROR" } @@ -264,6 +281,14 @@ func (p Printer) M(val any) string { } } +func (p Printer) N(n int) string { + return p.inner.Sprint(n) +} + +func (p Printer) T(fmt string, args ...any) string { + return p.inner.Sprintf(fmt, args...) +} + /* 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 deleted file mode 100644 index 364b6e8..0000000 --- a/src/mintage/parser.go +++ /dev/null @@ -1,297 +0,0 @@ -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 deleted file mode 100644 index 76e0f01..0000000 --- a/src/mintage/parser_test.go +++ /dev/null @@ -1,233 +0,0 @@ -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 index b813f46..608b3ae 100644 --- a/src/rosetta/bg/messages.gotext.json +++ b/src/rosetta/bg/messages.gotext.json @@ -257,6 +257,161 @@ "translation": "" }, { + "id": "%d Euro", + "message": "%d Euro", + "translation": "" + }, + { + "id": "Printer code on a %d euro bill", + "message": "Printer code on a %d euro bill", + "translation": "" + }, + { + "id": "Location Codes", + "message": "Location Codes", + "translation": "" + }, + { + "id": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "message": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "translation": "" + }, + { + "id": "Printer Code", + "message": "Printer Code", + "translation": "" + }, + { + "id": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "message": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "translation": "" + }, + { + "id": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "message": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "translation": "" + }, + { + "id": "2002 Series Printer Codes", + "message": "2002 Series Printer Codes", + "translation": "" + }, + { + "id": "All these images are taken from %seurobilltracker.com%s.", + "message": "All these images are taken from %seurobilltracker.com%s.", + "translation": "" + }, + { + "id": "Europa Series Printer Codes", + "message": "Europa Series Printer Codes", + "translation": "" + }, + { + "id": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "message": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "translation": "" + }, + { + "id": "2002 Series", + "message": "2002 Series", + "translation": "" + }, + { + "id": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "message": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "translation": "" + }, + { + "id": "Code", + "message": "Code", + "translation": "" + }, + { + "id": "Country", + "message": "Country", + "translation": "" + }, + { + "id": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "message": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "translation": "" + }, + { + "id": "Printer", + "message": "Printer", + "translation": "" + }, + { + "id": "United Kingdom", + "message": "United Kingdom", + "translation": "" + }, + { + "id": "Central Bank of Ireland", + "message": "Central Bank of Ireland", + "translation": "" + }, + { + "id": "Bank of Greece", + "message": "Bank of Greece", + "translation": "" + }, + { + "id": "National Bank of Belgium", + "message": "National Bank of Belgium", + "translation": "" + }, + { + "id": "Europa Series", + "message": "Europa Series", + "translation": "" + }, + { + "id": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "message": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "translation": "" + }, + { + "id": "Bulgaria", + "message": "Bulgaria", + "translation": "" + }, + { + "id": "Euro Banknotes", + "message": "Euro Banknotes", + "translation": "" + }, + { + "id": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "message": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "translation": "" + }, + { + "id": "Designs", + "message": "Designs", + "translation": "" + }, + { + "id": "View the different Euro-note designs!", + "message": "View the different Euro-note designs!", + "translation": "" + }, + { + "id": "Find out where your notes were printed!", + "message": "Find out where your notes were printed!", + "translation": "" + }, + { + "id": "Test Notes", + "message": "Test Notes", + "translation": "" + }, + { + "id": "Learn about the special test notes!", + "message": "Learn about the special test notes!", + "translation": "" + }, + { "id": "Andorran Euro Coin Designs", "message": "Andorran Euro Coin Designs", "translation": "" @@ -447,11 +602,6 @@ "translation": "" }, { - "id": "Country", - "message": "Country", - "translation": "" - }, - { "id": "Filter", "message": "Filter", "translation": "" @@ -472,6 +622,11 @@ "translation": "" }, { + "id": "Error", + "message": "Error", + "translation": "" + }, + { "id": "Commemorative Coins", "message": "Commemorative Coins", "translation": "" @@ -497,11 +652,6 @@ "translation": "" }, { - "id": "Designs", - "message": "Designs", - "translation": "" - }, - { "id": "View the 600+ different Euro-coin designs!", "message": "View the 600+ different Euro-coin designs!", "translation": "" diff --git a/src/rosetta/el/messages.gotext.json b/src/rosetta/el/messages.gotext.json index 7e035ec..41d2b25 100644 --- a/src/rosetta/el/messages.gotext.json +++ b/src/rosetta/el/messages.gotext.json @@ -257,6 +257,161 @@ "translation": "" }, { + "id": "%d Euro", + "message": "%d Euro", + "translation": "" + }, + { + "id": "Printer code on a %d euro bill", + "message": "Printer code on a %d euro bill", + "translation": "" + }, + { + "id": "Location Codes", + "message": "Location Codes", + "translation": "" + }, + { + "id": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "message": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "translation": "" + }, + { + "id": "Printer Code", + "message": "Printer Code", + "translation": "" + }, + { + "id": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "message": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "translation": "" + }, + { + "id": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "message": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "translation": "" + }, + { + "id": "2002 Series Printer Codes", + "message": "2002 Series Printer Codes", + "translation": "" + }, + { + "id": "All these images are taken from %seurobilltracker.com%s.", + "message": "All these images are taken from %seurobilltracker.com%s.", + "translation": "" + }, + { + "id": "Europa Series Printer Codes", + "message": "Europa Series Printer Codes", + "translation": "" + }, + { + "id": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "message": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "translation": "" + }, + { + "id": "2002 Series", + "message": "2002 Series", + "translation": "" + }, + { + "id": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "message": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "translation": "" + }, + { + "id": "Code", + "message": "Code", + "translation": "" + }, + { + "id": "Country", + "message": "Country", + "translation": "" + }, + { + "id": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "message": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "translation": "" + }, + { + "id": "Printer", + "message": "Printer", + "translation": "" + }, + { + "id": "United Kingdom", + "message": "United Kingdom", + "translation": "" + }, + { + "id": "Central Bank of Ireland", + "message": "Central Bank of Ireland", + "translation": "" + }, + { + "id": "Bank of Greece", + "message": "Bank of Greece", + "translation": "" + }, + { + "id": "National Bank of Belgium", + "message": "National Bank of Belgium", + "translation": "" + }, + { + "id": "Europa Series", + "message": "Europa Series", + "translation": "" + }, + { + "id": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "message": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "translation": "" + }, + { + "id": "Bulgaria", + "message": "Bulgaria", + "translation": "" + }, + { + "id": "Euro Banknotes", + "message": "Euro Banknotes", + "translation": "" + }, + { + "id": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "message": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "translation": "" + }, + { + "id": "Designs", + "message": "Designs", + "translation": "" + }, + { + "id": "View the different Euro-note designs!", + "message": "View the different Euro-note designs!", + "translation": "" + }, + { + "id": "Find out where your notes were printed!", + "message": "Find out where your notes were printed!", + "translation": "" + }, + { + "id": "Test Notes", + "message": "Test Notes", + "translation": "" + }, + { + "id": "Learn about the special test notes!", + "message": "Learn about the special test notes!", + "translation": "" + }, + { "id": "Andorran Euro Coin Designs", "message": "Andorran Euro Coin Designs", "translation": "" @@ -447,11 +602,6 @@ "translation": "" }, { - "id": "Country", - "message": "Country", - "translation": "" - }, - { "id": "Filter", "message": "Filter", "translation": "" @@ -472,6 +622,11 @@ "translation": "" }, { + "id": "Error", + "message": "Error", + "translation": "" + }, + { "id": "Commemorative Coins", "message": "Commemorative Coins", "translation": "" @@ -497,11 +652,6 @@ "translation": "" }, { - "id": "Designs", - "message": "Designs", - "translation": "" - }, - { "id": "View the 600+ different Euro-coin designs!", "message": "View the 600+ different Euro-coin designs!", "translation": "" diff --git a/src/rosetta/en/messages.gotext.json b/src/rosetta/en/messages.gotext.json index b9ee316..7570d37 100644 --- a/src/rosetta/en/messages.gotext.json +++ b/src/rosetta/en/messages.gotext.json @@ -359,6 +359,223 @@ "fuzzy": true }, { + "id": "%d Euro", + "message": "%d Euro", + "translation": "%d Euro", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Printer code on a %d euro bill", + "message": "Printer code on a %d euro bill", + "translation": "Printer code on a %d euro bill", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Location Codes", + "message": "Location Codes", + "translation": "Location Codes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "message": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "translation": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Printer Code", + "message": "Printer Code", + "translation": "Printer Code", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "message": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "translation": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "message": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "translation": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "2002 Series Printer Codes", + "message": "2002 Series Printer Codes", + "translation": "2002 Series Printer Codes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "All these images are taken from %seurobilltracker.com%s.", + "message": "All these images are taken from %seurobilltracker.com%s.", + "translation": "All these images are taken from %seurobilltracker.com%s.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Europa Series Printer Codes", + "message": "Europa Series Printer Codes", + "translation": "Europa Series Printer Codes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "message": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "translation": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "2002 Series", + "message": "2002 Series", + "translation": "2002 Series", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "message": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "translation": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Code", + "message": "Code", + "translation": "Code", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Country", + "message": "Country", + "translation": "Country", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "message": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "translation": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Printer", + "message": "Printer", + "translation": "Printer", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "United Kingdom", + "message": "United Kingdom", + "translation": "United Kingdom", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Central Bank of Ireland", + "message": "Central Bank of Ireland", + "translation": "Central Bank of Ireland", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Bank of Greece", + "message": "Bank of Greece", + "translation": "Bank of Greece", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "National Bank of Belgium", + "message": "National Bank of Belgium", + "translation": "National Bank of Belgium", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Europa Series", + "message": "Europa Series", + "translation": "Europa Series", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "message": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "translation": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Bulgaria", + "message": "Bulgaria", + "translation": "Bulgaria", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Euro Banknotes", + "message": "Euro Banknotes", + "translation": "Euro Banknotes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "message": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "translation": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Designs", + "message": "Designs", + "translation": "Designs", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "View the different Euro-note designs!", + "message": "View the different Euro-note designs!", + "translation": "View the different Euro-note designs!", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Find out where your notes were printed!", + "message": "Find out where your notes were printed!", + "translation": "Find out where your notes were printed!", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Test Notes", + "message": "Test Notes", + "translation": "Test Notes", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Learn about the special test notes!", + "message": "Learn about the special test notes!", + "translation": "Learn about the special test notes!", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { "id": "Andorran Euro Coin Designs", "message": "Andorran Euro Coin Designs", "translation": "Andorran Euro Coin Designs", @@ -625,13 +842,6 @@ "fuzzy": true }, { - "id": "Country", - "message": "Country", - "translation": "Country", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { "id": "Filter", "message": "Filter", "translation": "Filter", @@ -660,6 +870,13 @@ "fuzzy": true }, { + "id": "Error", + "message": "Error", + "translation": "Error", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { "id": "Commemorative Coins", "message": "Commemorative Coins", "translation": "Commemorative Coins", @@ -695,13 +912,6 @@ "fuzzy": true }, { - "id": "Designs", - "message": "Designs", - "translation": "Designs", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { "id": "View the 600+ different Euro-coin designs!", "message": "View the 600+ different Euro-coin designs!", "translation": "View the 600+ different Euro-coin designs!", diff --git a/src/rosetta/nl/messages.gotext.json b/src/rosetta/nl/messages.gotext.json index 4ce6565..8474cfc 100644 --- a/src/rosetta/nl/messages.gotext.json +++ b/src/rosetta/nl/messages.gotext.json @@ -257,6 +257,161 @@ "translation": "" }, { + "id": "%d Euro", + "message": "%d Euro", + "translation": "" + }, + { + "id": "Printer code on a %d euro bill", + "message": "Printer code on a %d euro bill", + "translation": "" + }, + { + "id": "Location Codes", + "message": "Location Codes", + "translation": "" + }, + { + "id": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "message": "Euro banknotes have two codes on them: a printer code and a serial number. The printer code tells you where a given note was printed, while the serial number tells you which country issues the banknote (for the 2002 series) or where the banknote was printed (for the Europa series).", + "translation": "" + }, + { + "id": "Printer Code", + "message": "Printer Code", + "translation": "" + }, + { + "id": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "message": "The printer code (not to be confused with the serial number) is a small code printed on banknotes with information about where the banknote was printed. All printer codes take the form of ‘X000X0’ — or in other words — a letter followed by 3 numbers, a letter and a final number.", + "translation": "" + }, + { + "id": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "message": "The printer code can be a bit tricky to find. The following dropdowns will show you where to find the printer code on each note.", + "translation": "" + }, + { + "id": "2002 Series Printer Codes", + "message": "2002 Series Printer Codes", + "translation": "" + }, + { + "id": "All these images are taken from %seurobilltracker.com%s.", + "message": "All these images are taken from %seurobilltracker.com%s.", + "translation": "" + }, + { + "id": "Europa Series Printer Codes", + "message": "Europa Series Printer Codes", + "translation": "" + }, + { + "id": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "message": "The first letter in the printer code identifies the specific printer at which the banknote was printed. The tables below will tell you which letters correspond to which printers. The final letter and number form a pair (such as ‘A2’ or ‘D6’). This pair acts as a set of coordinates telling you where on the sheet of paper the banknote was located. During printing, banknotes will be printed multiple times on a large sheet of paper which is later cut into smaller individual banknotes. A note with the pair ‘A1’ will have been at the upper-left corner of the printing sheet, with ‘A2’ to it’s right and ‘B1’ below it.", + "translation": "" + }, + { + "id": "2002 Series", + "message": "2002 Series", + "translation": "" + }, + { + "id": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "message": "In the 2002 series, the first letter of the serial number can be used to identify the country that issued the banknote. The following table shows which countries map to which codes.", + "translation": "" + }, + { + "id": "Code", + "message": "Code", + "translation": "" + }, + { + "id": "Country", + "message": "Country", + "translation": "" + }, + { + "id": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "message": "The first letter of the printer code can also be used to identify the specific printer at which the banknote was printed. The printer- and country codes do not need to line up; a banknote issued by a country will often be printed in another.", + "translation": "" + }, + { + "id": "Printer", + "message": "Printer", + "translation": "" + }, + { + "id": "United Kingdom", + "message": "United Kingdom", + "translation": "" + }, + { + "id": "Central Bank of Ireland", + "message": "Central Bank of Ireland", + "translation": "" + }, + { + "id": "Bank of Greece", + "message": "Bank of Greece", + "translation": "" + }, + { + "id": "National Bank of Belgium", + "message": "National Bank of Belgium", + "translation": "" + }, + { + "id": "Europa Series", + "message": "Europa Series", + "translation": "" + }, + { + "id": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "message": "In the Europa series the first letter of the serial number can be used to identify the printer that printed the banknote, just like the printer code. The following table shows which countries map to which codes.", + "translation": "" + }, + { + "id": "Bulgaria", + "message": "Bulgaria", + "translation": "" + }, + { + "id": "Euro Banknotes", + "message": "Euro Banknotes", + "translation": "" + }, + { + "id": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "message": "On this section of the site you can find everything there is to know about the banknotes of the Eurozone.", + "translation": "" + }, + { + "id": "Designs", + "message": "Designs", + "translation": "" + }, + { + "id": "View the different Euro-note designs!", + "message": "View the different Euro-note designs!", + "translation": "" + }, + { + "id": "Find out where your notes were printed!", + "message": "Find out where your notes were printed!", + "translation": "" + }, + { + "id": "Test Notes", + "message": "Test Notes", + "translation": "" + }, + { + "id": "Learn about the special test notes!", + "message": "Learn about the special test notes!", + "translation": "" + }, + { "id": "Andorran Euro Coin Designs", "message": "Andorran Euro Coin Designs", "translation": "" @@ -447,11 +602,6 @@ "translation": "" }, { - "id": "Country", - "message": "Country", - "translation": "" - }, - { "id": "Filter", "message": "Filter", "translation": "" @@ -472,6 +622,11 @@ "translation": "" }, { + "id": "Error", + "message": "Error", + "translation": "" + }, + { "id": "Commemorative Coins", "message": "Commemorative Coins", "translation": "" @@ -497,11 +652,6 @@ "translation": "" }, { - "id": "Designs", - "message": "Designs", - "translation": "" - }, - { "id": "View the 600+ different Euro-coin designs!", "message": "View the 600+ different Euro-coin designs!", "translation": "" diff --git a/src/templates.go b/src/templates.go index 4c37af1..4deeb67 100644 --- a/src/templates.go +++ b/src/templates.go @@ -1,66 +1,78 @@ -package src +package app import ( - "embed" + "fmt" "html/template" + "io/fs" "log" - "os" "strings" - "git.thomasvoss.com/euro-cash.eu/src/mintage" + . "git.thomasvoss.com/euro-cash.eu/pkg/try" + "git.thomasvoss.com/euro-cash.eu/pkg/watch" + + "git.thomasvoss.com/euro-cash.eu/src/dbx" ) type templateData struct { Printer Printer Code, Type string - Mintages mintage.Data + Mintages dbx.MintageData Countries []country } var ( - //go:embed templates/*.html.tmpl - templateFS embed.FS - notFoundTmpl = buildTemplate("-404") - errorTmpl = buildTemplate("-error") + notFoundTmpl *template.Template + errorTmpl *template.Template templates map[string]*template.Template funcmap = map[string]any{ - "denoms": denoms, - "locales": locales, - "safe": asHTML, - "strToCtype": strToCtype, - "toUpper": strings.ToUpper, - "tuple": templateMakeTuple, + "denoms": denoms, + "locales": locales, + "safe": asHTML, + "sprintf": fmt.Sprintf, + "toUpper": strings.ToUpper, + "tuple": templateMakeTuple, } ) -func init() { - ents, err := os.ReadDir("src/templates") - if err != nil { - log.Fatalln(err) - } +func BuildTemplates(dir fs.FS, debugp bool) { + ents := Try2(fs.ReadDir(dir, ".")) + notFoundTmpl = buildTemplate(dir, "-404") + errorTmpl = buildTemplate(dir, "-error") templates = make(map[string]*template.Template, len(ents)) + for _, e := range ents { - path := "/" - name := strings.TrimSuffix(e.Name(), ".html.tmpl") - switch { - case name[0] == '-': - continue - case name != "index": - path += strings.ReplaceAll(name, "-", "/") + name := e.Name() + buildAndSetTemplate(dir, name) + if debugp { + go watch.FileFS(dir, name, func() { + buildAndSetTemplate(dir, name) + log.Printf("Template ‘%s’ updated\n", name) + }) } - templates[path] = buildTemplate(name) } } -func buildTemplate(name string) *template.Template { +func buildAndSetTemplate(dir fs.FS, name string) { + path := "/" + name = strings.TrimSuffix(name, ".html.tmpl") + switch { + case name[0] == '-': + return + case name != "index": + path += strings.ReplaceAll(name, "-", "/") + } + templates[path] = buildTemplate(dir, name) +} + +func buildTemplate(dir fs.FS, name string) *template.Template { names := [...]string{"-base", "-navbar", name} for i, s := range names { - names[i] = "templates/" + s + ".html.tmpl" + names[i] = s + ".html.tmpl" } return template.Must(template. New("-base.html.tmpl"). Funcs(funcmap). - ParseFS(templateFS, names[:]...)) + ParseFS(dir, names[:]...)) } func asHTML(s string) template.HTML { @@ -82,17 +94,6 @@ func templateMakeTuple(args ...any) []any { return args } -func strToCtype(s string) int { - switch s { - case "nifc": - return mintage.TypeNIFC - case "proof": - return mintage.TypeProof - default: - return mintage.TypeCirc - } -} - func (td templateData) T(fmt string, args ...any) string { return td.Printer.T(fmt, args...) } diff --git a/src/templates/-navbar.html.tmpl b/src/templates/-navbar.html.tmpl index b0b1130..f1e95e9 100644 --- a/src/templates/-navbar.html.tmpl +++ b/src/templates/-navbar.html.tmpl @@ -5,7 +5,7 @@ <li><a href="#TODO">{{ .T "News" }}</a></li> <li><a href="/collecting">{{ .T "Coin Collecting" }}</a></li> <li><a href="/coins">{{ .T "Coins" }}</a></li> - <li><a href="#TODO">{{ .T "Banknotes" }}</a></li> + <li><a href="/banknotes">{{ .T "Banknotes" }}</a></li> <li><a href="/jargon">{{ .T "Jargon" }}</a></li> </menu> <menu> diff --git a/src/templates/banknotes-codes.html.tmpl b/src/templates/banknotes-codes.html.tmpl new file mode 100644 index 0000000..f7aea7f --- /dev/null +++ b/src/templates/banknotes-codes.html.tmpl @@ -0,0 +1,352 @@ +{{ define "content" }} +<header> + {{ template "navbar" . }} + <h1>{{ .T "Location Codes" }}</h1> +</header> +<main> + <p> + {{ .T ` + Euro banknotes have two codes on them: a printer code and a serial + number. The printer code tells you where a given note was printed, + while the serial number tells you which country issues the banknote + (for the 2002 series) or where the banknote was printed (for the + Europa series). + ` }} + </p> + + <h2>{{ .T "Printer Code" }}</h2> + <p> + {{ .T ` + The printer code (not to be confused with the serial number) is a + small code printed on banknotes with information about where the + banknote was printed. All printer codes take the form of ‘X000X0’ + — or in other words — a letter followed by 3 numbers, a letter + and a final number. + ` }} + </p> + <p> + {{ .T ` + The printer code can be a bit tricky to find. The following + dropdowns will show you where to find the printer code on each + note. + ` }} + </p> + <details> + <summary>{{ .T "2002 Series Printer Codes" }}</summary> + <p> + {{ .T ` + All these images are taken from %seurobilltracker.com%s.` + `<a href="https://eurobilltracker.com" target="_blank">` + `</a>` | safe + }} + </p> + {{ template "banknotes/codes/code-pos" (tuple .Printer 5 "2002") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 10 "2002") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 20 "2002") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 50 "2002") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 100 "2002") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 200 "2002") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 500 "2002") }} + </details> + <details> + <summary>{{ .T "Europa Series Printer Codes" }}</summary> + {{ template "banknotes/codes/code-pos" (tuple .Printer 5 "europa") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 10 "europa") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 20 "europa") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 50 "europa") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 100 "europa") }} + {{ template "banknotes/codes/code-pos" (tuple .Printer 200 "europa") }} + </details> + + <p> + {{ .T ` + The first letter in the printer code identifies the specific + printer at which the banknote was printed. The tables below will + tell you which letters correspond to which printers. The final + letter and number form a pair (such as ‘A2’ or ‘D6’). This pair + acts as a set of coordinates telling you where on the sheet of + paper the banknote was located. During printing, banknotes will be + printed multiple times on a large sheet of paper which is later cut + into smaller individual banknotes. A note with the pair ‘A1’ will + have been at the upper-left corner of the printing sheet, with ‘A2’ + to it’s right and ‘B1’ below it. + ` }} + </p> + + <h2>{{ .T "2002 Series" }}</h2> + <p> + {{ .T ` + In the 2002 series, the first letter of the serial number can be + used to identify the country that issued the banknote. The + following table shows which countries map to which codes. + ` }} + </p> + + <table role="grid"> + <thead> + <tr> + <th>{{ .T "Code" }}</th> + <th>{{ .T "Country" }}</th> + </tr> + </thead> + <tbody> + <tr> + <td>D</td> + <td>{{ .T "Estonia" }}</td> + </tr> + <tr> + <td>E</td> + <td>{{ .T "Slovakia" }}</td> + </tr> + <tr> + <td>F</td> + <td>{{ .T "Malta" }}</td> + </tr> + <tr> + <td>G</td> + <td>{{ .T "Cyprus" }}</td> + </tr> + <tr> + <td>H</td> + <td>{{ .T "Slovenia" }}</td> + </tr> + <tr> + <td>L</td> + <td>{{ .T "Finland" }}</td> + </tr> + <tr> + <td>M</td> + <td>{{ .T "Portugal" }}</td> + </tr> + <tr> + <td>N</td> + <td>{{ .T "Austria" }}</td> + </tr> + <tr> + <td>P</td> + <td>{{ .T "Netherlands" }}</td> + </tr> + <tr> + <td>S</td> + <td>{{ .T "Italy" }}</td> + </tr> + <tr> + <td>T</td> + <td>{{ .T "Ireland" }}</td> + </tr> + <tr> + <td>U</td> + <td>{{ .T "France" }}</td> + </tr> + <tr> + <td>V</td> + <td>{{ .T "Spain" }}</td> + </tr> + <tr> + <td>X</td> + <td>{{ .T "Germany" }}</td> + </tr> + <tr> + <td>Y</td> + <td>{{ .T "Greece" }}</td> + </tr> + <tr> + <td>Z</td> + <td>{{ .T "Belgium" }}</td> + </tr> + </tbody> + </table> + + <p> + {{ .T ` + The first letter of the printer code can also be used to identify + the specific printer at which the banknote was printed. The + printer- and country codes do not need to line up; a banknote + issued by a country will often be printed in another. + ` }} + </p> + + <table role="grid"> + <thead> + <tr> + <th>{{ .T "Code" }}</th> + <th>{{ .T "Country" }}</th> + <th>{{ .T "Printer" }}</th> + </tr> + </thead> + <tbody> + <tr> + <td>D</td> + <td>{{ .T "Finland" }}</td> + <td>SETEC</td> + </tr> + <tr> + <td>E</td> + <td>{{ .T "France" }}</td> + <td>Oberthur</td> + </tr> + <tr> + <td>F</td> + <td>{{ .T "Austria" }}</td> + <td>Österreichische Banknoten‐ und Sicherheitsdruck GmbH</td> + </tr> + <tr> + <td>G</td> + <td>{{ .T "Netherlands" }}</td> + <td>Koninklijke Joh. Enschede</td> + </tr> + <tr> + <td>H</td> + <td>{{ .T "United Kingdom" }}</td> + <td>Thomas de la Rue</td> + </tr> + <tr> + <td>J</td> + <td>{{ .T "Italy" }}</td> + <td>Banca d’ Italia</td> + </tr> + <tr> + <td>K</td> + <td>{{ .T "Ireland" }}</td> + <td>{{ .T "Central Bank of Ireland" }}</td> + </tr> + <tr> + <td>L</td> + <td>{{ .T "France" }}</td> + <td>Banque de France</td> + </tr> + <tr> + <td>M</td> + <td>{{ .T "Spain" }}</td> + <td>Fábrica Nacional de Moneda y Timbre</td> + </tr> + <tr> + <td>N</td> + <td>{{ .T "Greece" }}</td> + <td>{{ .T "Bank of Greece" }}</td> + </tr> + <tr> + <td>P</td> + <td>{{ .T "Germany" }}</td> + <td>Giesecke & Devrient</td> + </tr> + <tr> + <td>R</td> + <td>{{ .T "Germany" }}</td> + <td>Bundesdruckerei Berlin</td> + </tr> + <tr> + <td>T</td> + <td>{{ .T "Belgium" }}</td> + <td>{{ .T "National Bank of Belgium" }}</td> + </tr> + <tr> + <td>U</td> + <td>{{ .T "Portugal" }}</td> + <td>Valora S.A.</td> + </tr> + </tbody> + </table> + + <h2>{{ .T "Europa Series" }}</h2> + <p> + {{ .T ` + In the Europa series the first letter of the serial number can be + used to identify the printer that printed the banknote, just like + the printer code. The following table shows which countries map to + which codes. + ` }} + </p> + <table role="grid"> + <thead> + <tr> + <th>{{ .T "Code" }}</th> + <th>{{ .T "Country" }}</th> + <th>{{ .T "Printer" }}</th> + </tr> + </thead> + <tr> + <td>E</td> + <td>{{ .T "France" }}</td> + <td>Oberthur</td> + </tr> + <tr> + <td>F</td> + <td>{{ .T "Bulgaria" }}</td> + <td>Oberthur Fiduciaire AD</td> + </tr> + <tr> + <td>M</td> + <td>{{ .T "Portugal" }}</td> + <td>Valora S.A.</td> + </tr> + <tr> + <td>N</td> + <td>{{ .T "Austria" }}</td> + <td>Österreichische Banknoten‐ und Sicherheitsdruck GmbH</td> + </tr> + <tr> + <td>P</td> + <td>{{ .T "Netherlands" }}</td> + <td>Koninklijke Joh. Enschedé</td> + </tr> + <tr> + <td>R</td> + <td>{{ .T "Germany" }}</td> + <td>Bundesdruckerei Berlin</td> + </tr> + <tr> + <td>S</td> + <td>{{ .T "Italy" }}</td> + <td>Banca d’Italia</td> + </tr> + <tr> + <td>T</td> + <td>{{ .T "Ireland" }}</td> + <td>{{ .T "Central Bank of Ireland" }}</td> + </tr> + <tr> + <td>U</td> + <td>{{ .T "France" }}</td> + <td>Banque de France</td> + </tr> + <tr> + <td>V</td> + <td>{{ .T "Spain" }}</td> + <td>Fábrica Nacional de Moneda y Timbre</td> + </tr> + <tr> + <td>W</td> + <td>{{ .T "Germany" }}</td> + <td>Giesecke & Devrient Leipzig</td> + </tr> + <tr> + <td>X</td> + <td>{{ .T "Germany" }}</td> + <td>Giesecke & Devrient Munich</td> + </tr> + <tr> + <td>Y</td> + <td>{{ .T "Greece" }}</td> + <td>{{ .T "Bank of Greece" }}</td> + </tr> + <tr> + <td>Z</td> + <td>{{ .T "Belgium" }}</td> + <td>{{ .T "National Bank of Belgium" }}</td> + </tr> + </table> +</main> +{{ end }} + +{{ define "banknotes/codes/code-pos" }} +{{ $p := (index . 0) }} +<details> + <summary>{{ $p.T "%d Euro" (index . 1) }}</summary> + <img + class="big" + src={{ sprintf "/codes/%s-%03d.jpg" (index . 2) (index . 1) }} + alt={{ $p.T "Printer code on a %d euro bill" (index . 1) }} + > +</details> +{{ end }}
\ No newline at end of file diff --git a/src/templates/banknotes.html.tmpl b/src/templates/banknotes.html.tmpl new file mode 100644 index 0000000..1a171db --- /dev/null +++ b/src/templates/banknotes.html.tmpl @@ -0,0 +1,49 @@ +{{ define "content" }} +<header> + {{ template "navbar" . }} + <h1>{{ .T "Euro Banknotes" }}</h1> +</header> +<main> + <p> + {{ .T ` + On this section of the site you can find everything there is to + know about the banknotes of the Eurozone. + ` }} + </p> + <hr> + <section> + <div class="grid"> + <a class="no-deco" href="/banknotes/designs"> + <article> + <header> + <h3>{{ .T "Designs" }}</h3> + </header> + <main> + {{ .T "View the different Euro-note designs!" }} + </main> + </article> + </a> + <a class="no-deco" href="/banknotes/codes"> + <article> + <header> + <h3>{{ .T "Location Codes" }}</h3> + </header> + <main> + {{ .T "Find out where your notes were printed!" }} + </main> + </article> + </a> + <a class="no-deco" href="/banknotes/test"> + <article> + <header> + <h3>{{ .T "Test Notes" }}</h3> + </header> + <main> + {{ .T "Learn about the special test notes!" }} + </main> + </article> + </a> + </div> + </section> +</main> +{{ end }}
\ No newline at end of file diff --git a/src/templates/coins-designs-ad.html.tmpl b/src/templates/coins-designs-ad.html.tmpl index c42930a..20199bb 100644 --- a/src/templates/coins-designs-ad.html.tmpl +++ b/src/templates/coins-designs-ad.html.tmpl @@ -85,7 +85,7 @@ {{ .T ` The bottom of the coat of arms has the motto ‘%sVIRTVS VNITA FORTIOR%s’ (‘UNITED VIRTUE IS STRONGER’). - ` `<span lang="la">` `</span>` }} + ` `<span lang="la">` `</span>` | safe }} </p> </main> {{ end }}
\ No newline at end of file diff --git a/src/templates/coins-designs-be.html.tmpl b/src/templates/coins-designs-be.html.tmpl index 22f533f..4dcd325 100644 --- a/src/templates/coins-designs-be.html.tmpl +++ b/src/templates/coins-designs-be.html.tmpl @@ -21,8 +21,7 @@ target="_blank" href="https://www.wikipedia.org/wiki/Royal_cypher" >` - `</a>` - }} + `</a>` | safe }} </p> <p> {{ .T ` @@ -44,4 +43,4 @@ ` }} </p> </main> -{{ end }} +{{ end }}
\ No newline at end of file diff --git a/src/templates/coins-designs-hr.html.tmpl b/src/templates/coins-designs-hr.html.tmpl index b6333ba..8b6976f 100644 --- a/src/templates/coins-designs-hr.html.tmpl +++ b/src/templates/coins-designs-hr.html.tmpl @@ -26,10 +26,10 @@ <p> {{ .T ` The 1-, 2-, and 5 euro cent coins were designed by Maja - Škripelj and feature a motif of the letters ‘HR’ in the + Škripelj and feature a motif of the letters ‘ⰘⰓ’ from the %sGlagolitic script%s — an old Slavic script that saw use in - Croatia up until the 19th century — with ‘HR’ representing - Croatia’s country code.` + Croatia up until the 19th century — representing Croatia’s country + code (‘HR’ in the Latin alphabet).` `<a target="_blank" href="https://www.wikipedia.org/wiki/Glagolitic_script" @@ -80,4 +80,4 @@ `</a>` | safe }} </p> </main> -{{ end }} +{{ end }}
\ No newline at end of file diff --git a/src/templates/coins-mintages.html.tmpl b/src/templates/coins-mintages.html.tmpl index 8c60248..772db33 100644 --- a/src/templates/coins-mintages.html.tmpl +++ b/src/templates/coins-mintages.html.tmpl @@ -88,9 +88,11 @@ <sub><small>{{ .Mintmark }}</small></sub> {{- end -}} </th> - {{ range (index .Mintages (strToCtype $type)) }} + {{ range .Mintages }} {{ if eq . -1 }} <td>{{ $p.T "Unknown" }}</td> + {{ else if eq . -2 }} + <td class="error">{{ $p.T "Error" }}</td> {{ else if eq . 0 }} <td>—</td> {{ else }} @@ -125,9 +127,11 @@ </th> <!-- TODO: Translate commemorative names --> <td>{{ .Name }}</td> - {{ with (index .Mintage (strToCtype $type)) }} + {{ with .Mintage }} {{ if eq . -1 }} <td>{{ $p.T "Unknown" }}</td> + {{ else if eq . -2 }} + <td class="error">{{ $p.T "Error" }}</td> {{ else if eq . 0 }} <td>—</td> {{ else }} |