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 --- .exrc | 2 + .gitignore | 7 +- Makefile | 26 +- cmd/exttmpl/main.go | 200 + go.mod | 7 +- go.sum | 10 +- lib/countries.go | 46 - lib/email/email.go | 74 - lib/i18n.go | 252 - lib/locales/bg/messages.gotext.json | 570 - lib/locales/el/messages.gotext.json | 570 - lib/locales/en/messages.gotext.json | 748 - lib/locales/nl/messages.gotext.json | 570 - lib/mintage/parser.go | 297 - lib/mintage/parser_test.go | 233 - main.go | 188 +- rosetta/bg/messages.gotext.json | 285 + rosetta/el/messages.gotext.json | 285 + rosetta/en/messages.gotext.json | 397 + rosetta/nl/messages.gotext.json | 285 + 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 + template.old/404.templ | 16 + template.old/about.templ | 52 + template.old/base.templ | 51 + template.old/coins.templ | 53 + template.old/coins_designs.templ | 41 + template.old/coins_designs_nl.templ | 43 + template.old/coins_mintages.templ | 167 + template.old/error.templ | 24 + template.old/jargon.templ | 63 + template.old/language.templ | 54 + template.old/navbar.templ | 241 + template.old/root.templ | 23 + template/404.templ | 16 - template/about.templ | 52 - template/base.go | 3 - template/base.templ | 51 - template/coins.templ | 53 - template/coins_designs.templ | 41 - template/coins_designs_nl.templ | 43 - template/coins_mintages.templ | 167 - template/error.templ | 24 - template/jargon.templ | 63 - template/language.templ | 54 - template/navbar.templ | 241 - template/root.templ | 23 - vendor/github.com/a-h/templ/.dockerignore | 3 - vendor/github.com/a-h/templ/.gitignore | 28 - vendor/github.com/a-h/templ/.goreleaser.yaml | 72 - vendor/github.com/a-h/templ/.ignore | 7 - vendor/github.com/a-h/templ/.version | 1 - vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md | 128 - vendor/github.com/a-h/templ/CONTRIBUTING.md | 244 - vendor/github.com/a-h/templ/LICENSE | 21 - vendor/github.com/a-h/templ/README.md | 171 - vendor/github.com/a-h/templ/SECURITY.md | 9 - vendor/github.com/a-h/templ/cosign.pub | 4 - vendor/github.com/a-h/templ/flake.lock | 140 - vendor/github.com/a-h/templ/flake.nix | 93 - vendor/github.com/a-h/templ/flush.go | 36 - vendor/github.com/a-h/templ/gomod2nix.toml | 90 - vendor/github.com/a-h/templ/handler.go | 102 - vendor/github.com/a-h/templ/ide-demo.gif | Bin 544148 -> 0 bytes vendor/github.com/a-h/templ/jsonscript.go | 85 - vendor/github.com/a-h/templ/jsonstring.go | 14 - vendor/github.com/a-h/templ/once.go | 64 - vendor/github.com/a-h/templ/push-tag.sh | 14 - vendor/github.com/a-h/templ/runtime.go | 855 - vendor/github.com/a-h/templ/runtime/buffer.go | 62 - vendor/github.com/a-h/templ/runtime/bufferpool.go | 38 - vendor/github.com/a-h/templ/runtime/builder.go | 8 - vendor/github.com/a-h/templ/runtime/runtime.go | 21 - vendor/github.com/a-h/templ/safehtml/style.go | 168 - vendor/github.com/a-h/templ/templ.png | Bin 15528 -> 0 bytes vendor/github.com/a-h/templ/url.go | 20 - vendor/github.com/a-h/templ/version.go | 10 - vendor/golang.org/x/mod/LICENSE | 27 + vendor/golang.org/x/mod/PATENTS | 22 + vendor/golang.org/x/mod/semver/semver.go | 401 + vendor/golang.org/x/sync/LICENSE | 27 + vendor/golang.org/x/sync/PATENTS | 22 + vendor/golang.org/x/sync/errgroup/errgroup.go | 135 + vendor/golang.org/x/sync/errgroup/go120.go | 13 + vendor/golang.org/x/sync/errgroup/pre_go120.go | 14 + vendor/golang.org/x/text/internal/gen/code.go | 375 + vendor/golang.org/x/text/internal/gen/gen.go | 354 + .../golang.org/x/text/message/pipeline/extract.go | 821 + .../golang.org/x/text/message/pipeline/generate.go | 329 + .../golang.org/x/text/message/pipeline/message.go | 241 + .../golang.org/x/text/message/pipeline/pipeline.go | 422 + .../golang.org/x/text/message/pipeline/rewrite.go | 268 + vendor/golang.org/x/text/runes/cond.go | 187 + vendor/golang.org/x/text/runes/runes.go | 355 + vendor/golang.org/x/text/unicode/cldr/base.go | 105 + vendor/golang.org/x/text/unicode/cldr/cldr.go | 137 + vendor/golang.org/x/text/unicode/cldr/collate.go | 363 + vendor/golang.org/x/text/unicode/cldr/decode.go | 171 + vendor/golang.org/x/text/unicode/cldr/resolve.go | 602 + vendor/golang.org/x/text/unicode/cldr/slice.go | 144 + vendor/golang.org/x/text/unicode/cldr/xml.go | 1494 ++ vendor/golang.org/x/tools/LICENSE | 27 + vendor/golang.org/x/tools/PATENTS | 22 + .../golang.org/x/tools/go/ast/astutil/enclosing.go | 654 + .../golang.org/x/tools/go/ast/astutil/imports.go | 485 + .../golang.org/x/tools/go/ast/astutil/rewrite.go | 486 + vendor/golang.org/x/tools/go/ast/astutil/util.go | 19 + .../golang.org/x/tools/go/buildutil/allpackages.go | 195 + .../golang.org/x/tools/go/buildutil/fakecontext.go | 111 + vendor/golang.org/x/tools/go/buildutil/overlay.go | 101 + vendor/golang.org/x/tools/go/buildutil/tags.go | 100 + vendor/golang.org/x/tools/go/buildutil/util.go | 209 + .../golang.org/x/tools/go/callgraph/callgraph.go | 129 + vendor/golang.org/x/tools/go/callgraph/cha/cha.go | 164 + vendor/golang.org/x/tools/go/callgraph/util.go | 180 + .../x/tools/go/gcexportdata/gcexportdata.go | 186 + .../golang.org/x/tools/go/gcexportdata/importer.go | 75 + vendor/golang.org/x/tools/go/internal/cgo/cgo.go | 219 + .../x/tools/go/internal/cgo/cgo_pkgconfig.go | 42 + vendor/golang.org/x/tools/go/loader/doc.go | 202 + vendor/golang.org/x/tools/go/loader/loader.go | 1066 ++ vendor/golang.org/x/tools/go/loader/util.go | 123 + vendor/golang.org/x/tools/go/packages/doc.go | 242 + vendor/golang.org/x/tools/go/packages/external.go | 156 + vendor/golang.org/x/tools/go/packages/golist.go | 1066 ++ .../x/tools/go/packages/golist_overlay.go | 83 + .../x/tools/go/packages/loadmode_string.go | 57 + vendor/golang.org/x/tools/go/packages/packages.go | 1515 ++ vendor/golang.org/x/tools/go/packages/visit.go | 68 + vendor/golang.org/x/tools/go/ssa/TODO | 16 + vendor/golang.org/x/tools/go/ssa/block.go | 113 + vendor/golang.org/x/tools/go/ssa/blockopt.go | 183 + vendor/golang.org/x/tools/go/ssa/builder.go | 3276 ++++ vendor/golang.org/x/tools/go/ssa/const.go | 232 + vendor/golang.org/x/tools/go/ssa/coretype.go | 161 + vendor/golang.org/x/tools/go/ssa/create.go | 318 + vendor/golang.org/x/tools/go/ssa/doc.go | 122 + vendor/golang.org/x/tools/go/ssa/dom.go | 340 + vendor/golang.org/x/tools/go/ssa/emit.go | 614 + vendor/golang.org/x/tools/go/ssa/func.go | 816 + vendor/golang.org/x/tools/go/ssa/instantiate.go | 131 + vendor/golang.org/x/tools/go/ssa/lift.go | 688 + vendor/golang.org/x/tools/go/ssa/lvalue.go | 155 + vendor/golang.org/x/tools/go/ssa/methods.go | 281 + vendor/golang.org/x/tools/go/ssa/mode.go | 111 + vendor/golang.org/x/tools/go/ssa/print.go | 470 + vendor/golang.org/x/tools/go/ssa/sanity.go | 560 + vendor/golang.org/x/tools/go/ssa/source.go | 288 + vendor/golang.org/x/tools/go/ssa/ssa.go | 1871 ++ vendor/golang.org/x/tools/go/ssa/ssautil/load.go | 214 + vendor/golang.org/x/tools/go/ssa/ssautil/switch.go | 230 + vendor/golang.org/x/tools/go/ssa/ssautil/visit.go | 157 + vendor/golang.org/x/tools/go/ssa/subst.go | 642 + vendor/golang.org/x/tools/go/ssa/task.go | 103 + vendor/golang.org/x/tools/go/ssa/util.go | 430 + vendor/golang.org/x/tools/go/ssa/util_go120.go | 17 + vendor/golang.org/x/tools/go/ssa/wrappers.go | 348 + .../x/tools/go/types/objectpath/objectpath.go | 788 + .../golang.org/x/tools/go/types/typeutil/callee.go | 69 + .../x/tools/go/types/typeutil/imports.go | 30 + vendor/golang.org/x/tools/go/types/typeutil/map.go | 518 + .../x/tools/go/types/typeutil/methodsetcache.go | 73 + vendor/golang.org/x/tools/go/types/typeutil/ui.go | 55 + .../golang.org/x/tools/internal/aliases/aliases.go | 32 + .../x/tools/internal/aliases/aliases_go121.go | 35 + .../x/tools/internal/aliases/aliases_go122.go | 99 + .../x/tools/internal/event/core/event.go | 85 + .../x/tools/internal/event/core/export.go | 70 + .../golang.org/x/tools/internal/event/core/fast.go | 77 + vendor/golang.org/x/tools/internal/event/doc.go | 7 + vendor/golang.org/x/tools/internal/event/event.go | 127 + .../golang.org/x/tools/internal/event/keys/keys.go | 564 + .../x/tools/internal/event/keys/standard.go | 22 + .../golang.org/x/tools/internal/event/keys/util.go | 21 + .../x/tools/internal/event/label/label.go | 215 + .../x/tools/internal/gcimporter/bimport.go | 150 + .../x/tools/internal/gcimporter/exportdata.go | 99 + .../x/tools/internal/gcimporter/gcimporter.go | 266 + .../x/tools/internal/gcimporter/iexport.go | 1332 ++ .../x/tools/internal/gcimporter/iimport.go | 1100 ++ .../x/tools/internal/gcimporter/newInterface10.go | 22 + .../x/tools/internal/gcimporter/newInterface11.go | 14 + .../x/tools/internal/gcimporter/support_go118.go | 34 + .../x/tools/internal/gcimporter/unified_no.go | 10 + .../x/tools/internal/gcimporter/unified_yes.go | 10 + .../x/tools/internal/gcimporter/ureader_yes.go | 728 + .../x/tools/internal/gocommand/invoke.go | 555 + .../x/tools/internal/gocommand/vendor.go | 163 + .../x/tools/internal/gocommand/version.go | 71 + .../x/tools/internal/packagesinternal/packages.go | 22 + .../golang.org/x/tools/internal/pkgbits/codes.go | 77 + .../golang.org/x/tools/internal/pkgbits/decoder.go | 517 + vendor/golang.org/x/tools/internal/pkgbits/doc.go | 32 + .../golang.org/x/tools/internal/pkgbits/encoder.go | 383 + .../golang.org/x/tools/internal/pkgbits/flags.go | 9 + .../x/tools/internal/pkgbits/frames_go1.go | 21 + .../x/tools/internal/pkgbits/frames_go17.go | 28 + .../golang.org/x/tools/internal/pkgbits/reloc.go | 42 + .../golang.org/x/tools/internal/pkgbits/support.go | 17 + vendor/golang.org/x/tools/internal/pkgbits/sync.go | 113 + .../x/tools/internal/pkgbits/syncmarker_string.go | 89 + .../golang.org/x/tools/internal/stdlib/manifest.go | 17431 +++++++++++++++++++ .../golang.org/x/tools/internal/stdlib/stdlib.go | 97 + .../tools/internal/tokeninternal/tokeninternal.go | 137 + .../x/tools/internal/typeparams/common.go | 142 + .../x/tools/internal/typeparams/coretype.go | 150 + .../golang.org/x/tools/internal/typeparams/free.go | 120 + .../x/tools/internal/typeparams/normalize.go | 218 + .../x/tools/internal/typeparams/termlist.go | 163 + .../x/tools/internal/typeparams/typeterm.go | 169 + .../x/tools/internal/typesinternal/errorcode.go | 1560 ++ .../internal/typesinternal/errorcode_string.go | 179 + .../x/tools/internal/typesinternal/recv.go | 43 + .../x/tools/internal/typesinternal/toonew.go | 89 + .../x/tools/internal/typesinternal/types.go | 65 + .../x/tools/internal/versions/constraint.go | 13 + .../x/tools/internal/versions/constraint_go121.go | 14 + .../x/tools/internal/versions/features.go | 43 + .../golang.org/x/tools/internal/versions/gover.go | 172 + .../x/tools/internal/versions/toolchain.go | 14 + .../x/tools/internal/versions/toolchain_go119.go | 14 + .../x/tools/internal/versions/toolchain_go120.go | 14 + .../x/tools/internal/versions/toolchain_go121.go | 14 + .../golang.org/x/tools/internal/versions/types.go | 19 + .../x/tools/internal/versions/types_go121.go | 30 + .../x/tools/internal/versions/types_go122.go | 41 + .../x/tools/internal/versions/versions.go | 57 + vendor/modules.txt | 43 +- 244 files changed, 63008 insertions(+), 6892 deletions(-) create mode 100644 cmd/exttmpl/main.go delete mode 100644 lib/countries.go delete mode 100644 lib/email/email.go delete mode 100644 lib/i18n.go delete mode 100644 lib/locales/bg/messages.gotext.json delete mode 100644 lib/locales/el/messages.gotext.json delete mode 100644 lib/locales/en/messages.gotext.json delete mode 100644 lib/locales/nl/messages.gotext.json delete mode 100644 lib/mintage/parser.go delete mode 100644 lib/mintage/parser_test.go create mode 100644 rosetta/bg/messages.gotext.json create mode 100644 rosetta/el/messages.gotext.json create mode 100644 rosetta/en/messages.gotext.json create mode 100644 rosetta/nl/messages.gotext.json 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 create mode 100644 template.old/404.templ create mode 100644 template.old/about.templ create mode 100644 template.old/base.templ create mode 100644 template.old/coins.templ create mode 100644 template.old/coins_designs.templ create mode 100644 template.old/coins_designs_nl.templ create mode 100644 template.old/coins_mintages.templ create mode 100644 template.old/error.templ create mode 100644 template.old/jargon.templ create mode 100644 template.old/language.templ create mode 100644 template.old/navbar.templ create mode 100644 template.old/root.templ delete mode 100644 template/404.templ delete mode 100644 template/about.templ delete mode 100644 template/base.go delete mode 100644 template/base.templ delete mode 100644 template/coins.templ delete mode 100644 template/coins_designs.templ delete mode 100644 template/coins_designs_nl.templ delete mode 100644 template/coins_mintages.templ delete mode 100644 template/error.templ delete mode 100644 template/jargon.templ delete mode 100644 template/language.templ delete mode 100644 template/navbar.templ delete mode 100644 template/root.templ delete mode 100644 vendor/github.com/a-h/templ/.dockerignore delete mode 100644 vendor/github.com/a-h/templ/.gitignore delete mode 100644 vendor/github.com/a-h/templ/.goreleaser.yaml delete mode 100644 vendor/github.com/a-h/templ/.ignore delete mode 100644 vendor/github.com/a-h/templ/.version delete mode 100644 vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md delete mode 100644 vendor/github.com/a-h/templ/CONTRIBUTING.md delete mode 100644 vendor/github.com/a-h/templ/LICENSE delete mode 100644 vendor/github.com/a-h/templ/README.md delete mode 100644 vendor/github.com/a-h/templ/SECURITY.md delete mode 100644 vendor/github.com/a-h/templ/cosign.pub delete mode 100644 vendor/github.com/a-h/templ/flake.lock delete mode 100644 vendor/github.com/a-h/templ/flake.nix delete mode 100644 vendor/github.com/a-h/templ/flush.go delete mode 100644 vendor/github.com/a-h/templ/gomod2nix.toml delete mode 100644 vendor/github.com/a-h/templ/handler.go delete mode 100644 vendor/github.com/a-h/templ/ide-demo.gif delete mode 100644 vendor/github.com/a-h/templ/jsonscript.go delete mode 100644 vendor/github.com/a-h/templ/jsonstring.go delete mode 100644 vendor/github.com/a-h/templ/once.go delete mode 100644 vendor/github.com/a-h/templ/push-tag.sh delete mode 100644 vendor/github.com/a-h/templ/runtime.go delete mode 100644 vendor/github.com/a-h/templ/runtime/buffer.go delete mode 100644 vendor/github.com/a-h/templ/runtime/bufferpool.go delete mode 100644 vendor/github.com/a-h/templ/runtime/builder.go delete mode 100644 vendor/github.com/a-h/templ/runtime/runtime.go delete mode 100644 vendor/github.com/a-h/templ/safehtml/style.go delete mode 100644 vendor/github.com/a-h/templ/templ.png delete mode 100644 vendor/github.com/a-h/templ/url.go delete mode 100644 vendor/github.com/a-h/templ/version.go create mode 100644 vendor/golang.org/x/mod/LICENSE create mode 100644 vendor/golang.org/x/mod/PATENTS create mode 100644 vendor/golang.org/x/mod/semver/semver.go create mode 100644 vendor/golang.org/x/sync/LICENSE create mode 100644 vendor/golang.org/x/sync/PATENTS create mode 100644 vendor/golang.org/x/sync/errgroup/errgroup.go create mode 100644 vendor/golang.org/x/sync/errgroup/go120.go create mode 100644 vendor/golang.org/x/sync/errgroup/pre_go120.go create mode 100644 vendor/golang.org/x/text/internal/gen/code.go create mode 100644 vendor/golang.org/x/text/internal/gen/gen.go create mode 100644 vendor/golang.org/x/text/message/pipeline/extract.go create mode 100644 vendor/golang.org/x/text/message/pipeline/generate.go create mode 100644 vendor/golang.org/x/text/message/pipeline/message.go create mode 100644 vendor/golang.org/x/text/message/pipeline/pipeline.go create mode 100644 vendor/golang.org/x/text/message/pipeline/rewrite.go create mode 100644 vendor/golang.org/x/text/runes/cond.go create mode 100644 vendor/golang.org/x/text/runes/runes.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/base.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/cldr.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/collate.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/decode.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/resolve.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/slice.go create mode 100644 vendor/golang.org/x/text/unicode/cldr/xml.go create mode 100644 vendor/golang.org/x/tools/LICENSE create mode 100644 vendor/golang.org/x/tools/PATENTS create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/enclosing.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/imports.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/rewrite.go create mode 100644 vendor/golang.org/x/tools/go/ast/astutil/util.go create mode 100644 vendor/golang.org/x/tools/go/buildutil/allpackages.go create mode 100644 vendor/golang.org/x/tools/go/buildutil/fakecontext.go create mode 100644 vendor/golang.org/x/tools/go/buildutil/overlay.go create mode 100644 vendor/golang.org/x/tools/go/buildutil/tags.go create mode 100644 vendor/golang.org/x/tools/go/buildutil/util.go create mode 100644 vendor/golang.org/x/tools/go/callgraph/callgraph.go create mode 100644 vendor/golang.org/x/tools/go/callgraph/cha/cha.go create mode 100644 vendor/golang.org/x/tools/go/callgraph/util.go create mode 100644 vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go create mode 100644 vendor/golang.org/x/tools/go/gcexportdata/importer.go create mode 100644 vendor/golang.org/x/tools/go/internal/cgo/cgo.go create mode 100644 vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go create mode 100644 vendor/golang.org/x/tools/go/loader/doc.go create mode 100644 vendor/golang.org/x/tools/go/loader/loader.go create mode 100644 vendor/golang.org/x/tools/go/loader/util.go create mode 100644 vendor/golang.org/x/tools/go/packages/doc.go create mode 100644 vendor/golang.org/x/tools/go/packages/external.go create mode 100644 vendor/golang.org/x/tools/go/packages/golist.go create mode 100644 vendor/golang.org/x/tools/go/packages/golist_overlay.go create mode 100644 vendor/golang.org/x/tools/go/packages/loadmode_string.go create mode 100644 vendor/golang.org/x/tools/go/packages/packages.go create mode 100644 vendor/golang.org/x/tools/go/packages/visit.go create mode 100644 vendor/golang.org/x/tools/go/ssa/TODO create mode 100644 vendor/golang.org/x/tools/go/ssa/block.go create mode 100644 vendor/golang.org/x/tools/go/ssa/blockopt.go create mode 100644 vendor/golang.org/x/tools/go/ssa/builder.go create mode 100644 vendor/golang.org/x/tools/go/ssa/const.go create mode 100644 vendor/golang.org/x/tools/go/ssa/coretype.go create mode 100644 vendor/golang.org/x/tools/go/ssa/create.go create mode 100644 vendor/golang.org/x/tools/go/ssa/doc.go create mode 100644 vendor/golang.org/x/tools/go/ssa/dom.go create mode 100644 vendor/golang.org/x/tools/go/ssa/emit.go create mode 100644 vendor/golang.org/x/tools/go/ssa/func.go create mode 100644 vendor/golang.org/x/tools/go/ssa/instantiate.go create mode 100644 vendor/golang.org/x/tools/go/ssa/lift.go create mode 100644 vendor/golang.org/x/tools/go/ssa/lvalue.go create mode 100644 vendor/golang.org/x/tools/go/ssa/methods.go create mode 100644 vendor/golang.org/x/tools/go/ssa/mode.go create mode 100644 vendor/golang.org/x/tools/go/ssa/print.go create mode 100644 vendor/golang.org/x/tools/go/ssa/sanity.go create mode 100644 vendor/golang.org/x/tools/go/ssa/source.go create mode 100644 vendor/golang.org/x/tools/go/ssa/ssa.go create mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/load.go create mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/switch.go create mode 100644 vendor/golang.org/x/tools/go/ssa/ssautil/visit.go create mode 100644 vendor/golang.org/x/tools/go/ssa/subst.go create mode 100644 vendor/golang.org/x/tools/go/ssa/task.go create mode 100644 vendor/golang.org/x/tools/go/ssa/util.go create mode 100644 vendor/golang.org/x/tools/go/ssa/util_go120.go create mode 100644 vendor/golang.org/x/tools/go/ssa/wrappers.go create mode 100644 vendor/golang.org/x/tools/go/types/objectpath/objectpath.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/callee.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/imports.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/map.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/methodsetcache.go create mode 100644 vendor/golang.org/x/tools/go/types/typeutil/ui.go create mode 100644 vendor/golang.org/x/tools/internal/aliases/aliases.go create mode 100644 vendor/golang.org/x/tools/internal/aliases/aliases_go121.go create mode 100644 vendor/golang.org/x/tools/internal/aliases/aliases_go122.go create mode 100644 vendor/golang.org/x/tools/internal/event/core/event.go create mode 100644 vendor/golang.org/x/tools/internal/event/core/export.go create mode 100644 vendor/golang.org/x/tools/internal/event/core/fast.go create mode 100644 vendor/golang.org/x/tools/internal/event/doc.go create mode 100644 vendor/golang.org/x/tools/internal/event/event.go create mode 100644 vendor/golang.org/x/tools/internal/event/keys/keys.go create mode 100644 vendor/golang.org/x/tools/internal/event/keys/standard.go create mode 100644 vendor/golang.org/x/tools/internal/event/keys/util.go create mode 100644 vendor/golang.org/x/tools/internal/event/label/label.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/bimport.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/exportdata.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/iexport.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/iimport.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/newInterface10.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/newInterface11.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/support_go118.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/unified_no.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/unified_yes.go create mode 100644 vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go create mode 100644 vendor/golang.org/x/tools/internal/gocommand/invoke.go create mode 100644 vendor/golang.org/x/tools/internal/gocommand/vendor.go create mode 100644 vendor/golang.org/x/tools/internal/gocommand/version.go create mode 100644 vendor/golang.org/x/tools/internal/packagesinternal/packages.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/codes.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/decoder.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/doc.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/encoder.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/flags.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/frames_go1.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/frames_go17.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/reloc.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/support.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/sync.go create mode 100644 vendor/golang.org/x/tools/internal/pkgbits/syncmarker_string.go create mode 100644 vendor/golang.org/x/tools/internal/stdlib/manifest.go create mode 100644 vendor/golang.org/x/tools/internal/stdlib/stdlib.go create mode 100644 vendor/golang.org/x/tools/internal/tokeninternal/tokeninternal.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/common.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/coretype.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/free.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/normalize.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/termlist.go create mode 100644 vendor/golang.org/x/tools/internal/typeparams/typeterm.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/errorcode.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/errorcode_string.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/recv.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/toonew.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/types.go create mode 100644 vendor/golang.org/x/tools/internal/versions/constraint.go create mode 100644 vendor/golang.org/x/tools/internal/versions/constraint_go121.go create mode 100644 vendor/golang.org/x/tools/internal/versions/features.go create mode 100644 vendor/golang.org/x/tools/internal/versions/gover.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go119.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go120.go create mode 100644 vendor/golang.org/x/tools/internal/versions/toolchain_go121.go create mode 100644 vendor/golang.org/x/tools/internal/versions/types.go create mode 100644 vendor/golang.org/x/tools/internal/versions/types_go121.go create mode 100644 vendor/golang.org/x/tools/internal/versions/types_go122.go create mode 100644 vendor/golang.org/x/tools/internal/versions/versions.go diff --git a/.exrc b/.exrc index 011938b..252d632 100644 --- a/.exrc +++ b/.exrc @@ -13,6 +13,8 @@ endfunction autocmd BufNewFile,BufRead */data/mintages/* \ setfiletype mintage \ | setlocal nowrap +autocmd BufNewFile,BufRead */cmd/exttmpl/* + \ setlocal makeprg=go\ build\ ./cmd/exttmpl autocmd BufNewFile,BufRead */cmd/mfmt/* \ setlocal makeprg=go\ build\ ./cmd/mfmt diff --git a/.gitignore b/.gitignore index e58bfc9..36f48ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ +# Binaries euro-cash.eu +exttmpl mfmt -!cmd/mfmt + +!cmd/* + +# Autogenerated files out.gotext.json *.gen.* *_templ.go diff --git a/Makefile b/Makefile index b8a4aea..f867cfa 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,26 @@ -# Generating translations is rather slow; so don’t do that by default -all: - TEMPL_EXPERIMENT=rawgo go generate ./template +templates = $(shell find src/templates -name '*.tmpl') +gofiles = $(shell find main.go src -name '*.go') + +exttmpl = $(wildcard cmd/exttmpl/*.go) +mfmt = $(wildcard cmd/mfmt/*.go) + +all: euro-cash.eu exttmpl mfmt + +euro-cash.eu: $(templates) $(gofiles) go build -all-i18n: - TEMPL_EXPERIMENT=rawgo go generate ./template ./lib +# Generating translations is rather slow; so don’t do that by default +all-i18n: exttmpl + go generate ./src find . -name out.gotext.json | mcp -b sed s/out/messages/ go build +exttmpl: $(exttmpl) + go build ./cmd/exttmpl + +mfmt: $(mfmt) + go build ./cmd/mfmt + watch: ls euro-cash.eu | entr -r ./euro-cash.eu -no-email -port $${PORT:-8080} @@ -16,3 +29,6 @@ watch: release: all-i18n [ -n "$$GOOS" -a -n "$$GOARCH" ] tar -cf euro-cash.eu-$$GOOS-$$GOARCH.tar.gz euro-cash.eu data/ static/ + +clean: + rm -f euro-cash.eu diff --git a/cmd/exttmpl/main.go b/cmd/exttmpl/main.go new file mode 100644 index 0000000..ca7c15f --- /dev/null +++ b/cmd/exttmpl/main.go @@ -0,0 +1,200 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "slices" + "text/template/parse" + + "golang.org/x/text/language" + "golang.org/x/text/message/pipeline" + "golang.org/x/tools/go/packages" +) + +const ( + pkgbase = "git.thomasvoss.com/euro-cash.eu" + srclang = "en" + srcdir = "./src" + transdir = srcdir + "/rosetta" + outfile = "catalog.gen.go" + transfn = "T" +) + +func main() { + /* cd to the project root directory */ + try(os.Chdir(filepath.Dir(os.Args[0]))) + + pkgnames := packageList(".") + + var paths []string + pkgs := try2(packages.Load(&packages.Config{ + Mode: packages.NeedFiles | packages.NeedEmbedFiles, + }, pkgnames...)) + + for _, pkg := range pkgs { + if len(pkg.Errors) != 0 { + for _, err := range pkg.Errors { + warn(err.Msg) + } + os.Exit(1) + } + for _, f := range pkg.EmbedFiles { + if filepath.Ext(f) == ".tmpl" { + paths = append(paths, f) + } + } + } + + msgs := make([]pipeline.Message, 0, 1024) + for _, path := range paths { + f := try2(os.ReadFile(path)) + trees := make(map[string]*parse.Tree) + t := parse.New("name") + t.Mode |= parse.SkipFuncCheck + try2(t.Parse(string(f), "", "", trees)) + for _, t := range trees { + process(&msgs, t.Root) + } + } + + pconf := &pipeline.Config{ + Supported: languages(), + SourceLanguage: language.Make(srclang), + Packages: pkgnames, + Dir: transdir, + GenFile: outfile, + GenPackage: srcdir, + } + + state := try2(pipeline.Extract(pconf)) + state.Extracted.Messages = append(state.Extracted.Messages, msgs...) + + try(state.Import()) + try(state.Merge()) + try(state.Export()) + try(state.Generate()) +} + +func process(tmplMsgs *[]pipeline.Message, node parse.Node) { + switch node.Type() { + case parse.NodeList: + if ln, ok := node.(*parse.ListNode); ok { + for _, n := range ln.Nodes { + process(tmplMsgs, n) + } + } + case parse.NodeIf: + if in, ok := node.(*parse.IfNode); ok { + process(tmplMsgs, in.List) + if in.ElseList != nil { + process(tmplMsgs, in.ElseList) + } + } + case parse.NodeWith: + if wn, ok := node.(*parse.WithNode); ok { + process(tmplMsgs, wn.List) + if wn.ElseList != nil { + process(tmplMsgs, wn.ElseList) + } + } + case parse.NodeRange: + if rn, ok := node.(*parse.RangeNode); ok { + process(tmplMsgs, rn.List) + if rn.ElseList != nil { + process(tmplMsgs, rn.ElseList) + } + } + case parse.NodeAction: + an, ok := node.(*parse.ActionNode) + if !ok { + break + } + + for _, cmd := range an.Pipe.Cmds { + if !hasIndent(cmd, transfn) { + continue + } + for _, arg := range cmd.Args { + if arg.Type() != parse.NodeString { + continue + } + if sn, ok := arg.(*parse.StringNode); ok { + *tmplMsgs = append(*tmplMsgs, pipeline.Message{ + ID: pipeline.IDList{sn.Text}, + Key: sn.Text, + Message: pipeline.Text{ + Msg: sn.Text, + }, + }) + } + } + } + } +} + +func hasIndent(cmd *parse.CommandNode, s string) bool { + if len(cmd.Args) == 0 { + return false + } + arg := cmd.Args[0] + var idents []string + switch arg.Type() { + case parse.NodeField: + idents = arg.(*parse.FieldNode).Ident + case parse.NodeVariable: + idents = arg.(*parse.VariableNode).Ident + } + return slices.Contains(idents, s) +} + +func packageList(path string) []string { + ents := try2(os.ReadDir(path)) + xs := make([]string, 0, len(ents)) + foundOne := false + for _, ent := range ents { + switch { + case filepath.Ext(ent.Name()) == ".go": + if !foundOne { + xs = append(xs, pkgbase+"/"+path) + foundOne = true + } + case !ent.IsDir(), ent.Name() == "cmd", ent.Name() == "vendor": + continue + default: + xs = append(xs, packageList(path+"/"+ent.Name())...) + } + } + return xs +} + +func languages() []language.Tag { + ents := try2(os.ReadDir(transdir)) + tags := make([]language.Tag, len(ents)) + for i, e := range ents { + tags[i] = language.MustParse(e.Name()) + } + return tags +} + +func try(err error) { + if err != nil { + die(err) + } +} + +func try2[T any](val T, err error) T { + if err != nil { + die(err) + } + return val +} + +func warn(err any) { + fmt.Fprintf(os.Stderr, "%s: %s\n", filepath.Base(os.Args[0]), err) +} + +func die(err any) { + warn(err) + os.Exit(1) +} diff --git a/go.mod b/go.mod index 11b833c..53d5d8c 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,9 @@ go 1.23 require golang.org/x/text v0.17.0 -require github.com/a-h/templ v0.2.747 +require golang.org/x/tools v0.24.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect +) diff --git a/go.sum b/go.sum index 336985b..410c3e6 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ -github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg= -github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/lib/countries.go b/lib/countries.go deleted file mode 100644 index 565f61c..0000000 --- a/lib/countries.go +++ /dev/null @@ -1,46 +0,0 @@ -package lib - -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/lib/email/email.go b/lib/email/email.go deleted file mode 100644 index 16ba492..0000000 --- a/lib/email/email.go +++ /dev/null @@ -1,74 +0,0 @@ -package email - -import ( - "crypto/tls" - "fmt" - "math/rand/v2" - "net/smtp" - "strconv" - "time" -) - -var Config struct { - 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 { - 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/lib/i18n.go b/lib/i18n.go deleted file mode 100644 index 50652cb..0000000 --- a/lib/i18n.go +++ /dev/null @@ -1,252 +0,0 @@ -//go:generate gotext -srclang=en update -out=catalog.gen.go -lang=bg,el,en,nl git.thomasvoss.com/euro-cash.eu - -package lib - -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 - dateFmt, moneyFmt string - Eurozone, Enabled bool -} - -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 InitPrinters() { - 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/lib/locales/bg/messages.gotext.json b/lib/locales/bg/messages.gotext.json deleted file mode 100644 index 103b1a2..0000000 --- a/lib/locales/bg/messages.gotext.json +++ /dev/null @@ -1,570 +0,0 @@ -{ - "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 to email us at {ContactEmail}.", - "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 to email us at {ContactEmail}.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of 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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of the content on this site.", - "translation": "", - "placeholders": [ - { - "id": "RepoLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "repoLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "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 {ContactEmail} 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 {ContactEmail} or contact ‘@onetruemangoman’ on Discord.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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": "Dutch Euro Coin Designs", - "message": "Dutch Euro Coin Designs", - "translation": "" - }, - { - "id": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "message": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "translation": "" - }, - { - "id": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "message": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "translation": "" - }, - { - "id": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "message": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "translation": "" - }, - { - "id": "Euro Coin Designs", - "message": "Euro Coin Designs", - "translation": "" - }, - { - "id": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "message": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "translation": "", - "placeholders": [ - { - "id": "VarietiesLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "varietiesLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "Euro Coin Mintages", - "message": "Euro Coin Mintages", - "translation": "" - }, - { - "id": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "message": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "translation": "" - }, - { - "id": "Additional Notes", - "message": "Additional Notes", - "translation": "" - }, - { - "id": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "message": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "translation": "", - "placeholders": [ - { - "id": "MuntrolpakketLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "muntrolpakketLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "message": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "translation": "", - "placeholders": [ - { - "id": "217503", - "string": "%[1]d", - "type": "int", - "underlyingType": "int", - "argNum": 1, - "expr": "217503" - }, - { - "id": "177003", - "string": "%[2]d", - "type": "int", - "underlyingType": "int", - "argNum": 2, - "expr": "177003" - } - ] - }, - { - "id": "Country", - "message": "Country", - "translation": "" - }, - { - "id": "Circulation Coins", - "message": "Circulation Coins", - "translation": "" - }, - { - "id": "NIFC / BU Sets", - "message": "NIFC / BU Sets", - "translation": "" - }, - { - "id": "Proof Coins", - "message": "Proof Coins", - "translation": "" - }, - { - "id": "Filter", - "message": "Filter", - "translation": "" - }, - { - "id": "Standard Issue Coins", - "message": "Standard Issue Coins", - "translation": "" - }, - { - "id": "Year", - "message": "Year", - "translation": "" - }, - { - "id": "Unknown", - "message": "Unknown", - "translation": "" - }, - { - "id": "Commemorative Coins", - "message": "Commemorative Coins", - "translation": "" - }, - { - "id": "Commemorated Issue", - "message": "Commemorated Issue", - "translation": "" - }, - { - "id": "Mintage", - "message": "Mintage", - "translation": "" - }, - { - "id": "Euro Coins", - "message": "Euro Coins", - "translation": "" - }, - { - "id": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "message": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "translation": "", - "placeholders": [ - { - "id": "NewsLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "newsLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "Designs", - "message": "Designs", - "translation": "" - }, - { - "id": "View the 600+ different Euro-coin designs!", - "message": "View the 600+ different Euro-coin designs!", - "translation": "" - }, - { - "id": "Mintages", - "message": "Mintages", - "translation": "" - }, - { - "id": "View the mintage figures of all the Euro coins!", - "message": "View the mintage figures of all the Euro coins!", - "translation": "" - }, - { - "id": "Varieties", - "message": "Varieties", - "translation": "" - }, - { - "id": "View all the known Euro varieties!", - "message": "View all the known Euro varieties!", - "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 {ContactEmail}.", - "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at {ContactEmail}.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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": "" - }, - { - "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": "" - } - ] -} \ No newline at end of file diff --git a/lib/locales/el/messages.gotext.json b/lib/locales/el/messages.gotext.json deleted file mode 100644 index 1827a66..0000000 --- a/lib/locales/el/messages.gotext.json +++ /dev/null @@ -1,570 +0,0 @@ -{ - "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 to email us at {ContactEmail}.", - "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 to email us at {ContactEmail}.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of 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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of the content on this site.", - "translation": "", - "placeholders": [ - { - "id": "RepoLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "repoLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "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 {ContactEmail} 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 {ContactEmail} or contact ‘@onetruemangoman’ on Discord.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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": "Dutch Euro Coin Designs", - "message": "Dutch Euro Coin Designs", - "translation": "" - }, - { - "id": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "message": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "translation": "" - }, - { - "id": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "message": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "translation": "" - }, - { - "id": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "message": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "translation": "" - }, - { - "id": "Euro Coin Designs", - "message": "Euro Coin Designs", - "translation": "" - }, - { - "id": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "message": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "translation": "", - "placeholders": [ - { - "id": "VarietiesLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "varietiesLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "Euro Coin Mintages", - "message": "Euro Coin Mintages", - "translation": "" - }, - { - "id": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "message": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "translation": "" - }, - { - "id": "Additional Notes", - "message": "Additional Notes", - "translation": "" - }, - { - "id": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "message": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "translation": "", - "placeholders": [ - { - "id": "MuntrolpakketLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "muntrolpakketLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "message": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "translation": "", - "placeholders": [ - { - "id": "217503", - "string": "%[1]d", - "type": "int", - "underlyingType": "int", - "argNum": 1, - "expr": "217503" - }, - { - "id": "177003", - "string": "%[2]d", - "type": "int", - "underlyingType": "int", - "argNum": 2, - "expr": "177003" - } - ] - }, - { - "id": "Country", - "message": "Country", - "translation": "" - }, - { - "id": "Circulation Coins", - "message": "Circulation Coins", - "translation": "" - }, - { - "id": "NIFC / BU Sets", - "message": "NIFC / BU Sets", - "translation": "" - }, - { - "id": "Proof Coins", - "message": "Proof Coins", - "translation": "" - }, - { - "id": "Filter", - "message": "Filter", - "translation": "" - }, - { - "id": "Standard Issue Coins", - "message": "Standard Issue Coins", - "translation": "" - }, - { - "id": "Year", - "message": "Year", - "translation": "" - }, - { - "id": "Unknown", - "message": "Unknown", - "translation": "" - }, - { - "id": "Commemorative Coins", - "message": "Commemorative Coins", - "translation": "" - }, - { - "id": "Commemorated Issue", - "message": "Commemorated Issue", - "translation": "" - }, - { - "id": "Mintage", - "message": "Mintage", - "translation": "" - }, - { - "id": "Euro Coins", - "message": "Euro Coins", - "translation": "" - }, - { - "id": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "message": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "translation": "", - "placeholders": [ - { - "id": "NewsLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "newsLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "Designs", - "message": "Designs", - "translation": "" - }, - { - "id": "View the 600+ different Euro-coin designs!", - "message": "View the 600+ different Euro-coin designs!", - "translation": "" - }, - { - "id": "Mintages", - "message": "Mintages", - "translation": "" - }, - { - "id": "View the mintage figures of all the Euro coins!", - "message": "View the mintage figures of all the Euro coins!", - "translation": "" - }, - { - "id": "Varieties", - "message": "Varieties", - "translation": "" - }, - { - "id": "View all the known Euro varieties!", - "message": "View all the known Euro varieties!", - "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 {ContactEmail}.", - "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at {ContactEmail}.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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": "" - }, - { - "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": "" - } - ] -} \ No newline at end of file diff --git a/lib/locales/en/messages.gotext.json b/lib/locales/en/messages.gotext.json deleted file mode 100644 index 45d33b0..0000000 --- a/lib/locales/en/messages.gotext.json +++ /dev/null @@ -1,748 +0,0 @@ -{ - "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 to email us at {ContactEmail}.", - "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 to email us at {ContactEmail}.", - "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 to email us at {ContactEmail}.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ], - "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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of 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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of 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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of the content on this site.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "RepoLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "repoLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ], - "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 {ContactEmail} 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 {ContactEmail} 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 {ContactEmail} or contact ‘@onetruemangoman’ on Discord.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ], - "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": "Dutch Euro Coin Designs", - "message": "Dutch Euro Coin Designs", - "translation": "Dutch Euro Coin Designs", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "message": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "translation": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "message": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "translation": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "message": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "translation": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Euro Coin Designs", - "message": "Euro Coin Designs", - "translation": "Euro Coin Designs", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "message": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "translation": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "VarietiesLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "varietiesLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ], - "fuzzy": true - }, - { - "id": "Euro Coin Mintages", - "message": "Euro Coin Mintages", - "translation": "Euro Coin Mintages", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "message": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "translation": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Additional Notes", - "message": "Additional Notes", - "translation": "Additional Notes", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "message": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "translation": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "MuntrolpakketLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "muntrolpakketLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ], - "fuzzy": true - }, - { - "id": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "message": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "translation": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "217503", - "string": "%[1]d", - "type": "int", - "underlyingType": "int", - "argNum": 1, - "expr": "217503" - }, - { - "id": "177003", - "string": "%[2]d", - "type": "int", - "underlyingType": "int", - "argNum": 2, - "expr": "177003" - } - ], - "fuzzy": true - }, - { - "id": "Country", - "message": "Country", - "translation": "Country", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Circulation Coins", - "message": "Circulation Coins", - "translation": "Circulation Coins", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "NIFC / BU Sets", - "message": "NIFC / BU Sets", - "translation": "NIFC / BU Sets", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Proof Coins", - "message": "Proof Coins", - "translation": "Proof Coins", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Filter", - "message": "Filter", - "translation": "Filter", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Standard Issue Coins", - "message": "Standard Issue Coins", - "translation": "Standard Issue Coins", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Year", - "message": "Year", - "translation": "Year", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Unknown", - "message": "Unknown", - "translation": "Unknown", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Commemorative Coins", - "message": "Commemorative Coins", - "translation": "Commemorative Coins", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Commemorated Issue", - "message": "Commemorated Issue", - "translation": "Commemorated Issue", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Mintage", - "message": "Mintage", - "translation": "Mintage", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Euro Coins", - "message": "Euro Coins", - "translation": "Euro Coins", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "message": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "translation": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "NewsLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "newsLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ], - "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!", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Mintages", - "message": "Mintages", - "translation": "Mintages", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "View the mintage figures of all the Euro coins!", - "message": "View the mintage figures of all the Euro coins!", - "translation": "View the mintage figures of all the Euro coins!", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "Varieties", - "message": "Varieties", - "translation": "Varieties", - "translatorComment": "Copied from source.", - "fuzzy": true - }, - { - "id": "View all the known Euro varieties!", - "message": "View all the known Euro varieties!", - "translation": "View all the known Euro varieties!", - "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 {ContactEmail}.", - "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at {ContactEmail}.", - "translation": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at {ContactEmail}.", - "translatorComment": "Copied from source.", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ], - "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 - }, - { - "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 - } - ] -} \ No newline at end of file diff --git a/lib/locales/nl/messages.gotext.json b/lib/locales/nl/messages.gotext.json deleted file mode 100644 index c458b9d..0000000 --- a/lib/locales/nl/messages.gotext.json +++ /dev/null @@ -1,570 +0,0 @@ -{ - "language": "nl", - "messages": [ - { - "id": "Andorra", - "message": "Andorra", - "translation": "Andorra" - }, - { - "id": "Austria", - "message": "Austria", - "translation": "Oostenrijk" - }, - { - "id": "Belgium", - "message": "Belgium", - "translation": "België" - }, - { - "id": "Cyprus", - "message": "Cyprus", - "translation": "Cyprus" - }, - { - "id": "Germany", - "message": "Germany", - "translation": "Duitsland" - }, - { - "id": "Estonia", - "message": "Estonia", - "translation": "Estland" - }, - { - "id": "Spain", - "message": "Spain", - "translation": "Spanje" - }, - { - "id": "Finland", - "message": "Finland", - "translation": "Finland" - }, - { - "id": "France", - "message": "France", - "translation": "Frankrijk" - }, - { - "id": "Greece", - "message": "Greece", - "translation": "Griekenland" - }, - { - "id": "Croatia", - "message": "Croatia", - "translation": "Kroatië" - }, - { - "id": "Ireland", - "message": "Ireland", - "translation": "Ierland" - }, - { - "id": "Italy", - "message": "Italy", - "translation": "Italië" - }, - { - "id": "Lithuania", - "message": "Lithuania", - "translation": "Litouwen" - }, - { - "id": "Luxembourg", - "message": "Luxembourg", - "translation": "Luxemburg" - }, - { - "id": "Latvia", - "message": "Latvia", - "translation": "Letland" - }, - { - "id": "Monaco", - "message": "Monaco", - "translation": "Monaco" - }, - { - "id": "Malta", - "message": "Malta", - "translation": "Malta" - }, - { - "id": "Netherlands", - "message": "Netherlands", - "translation": "Nederland" - }, - { - "id": "Portugal", - "message": "Portugal", - "translation": "Portugal" - }, - { - "id": "Slovenia", - "message": "Slovenia", - "translation": "Slovenië" - }, - { - "id": "Slovakia", - "message": "Slovakia", - "translation": "Slowakije" - }, - { - "id": "San Marino", - "message": "San Marino", - "translation": "San Marino" - }, - { - "id": "Vatican City", - "message": "Vatican City", - "translation": "Vaticaanstad" - }, - { - "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 to email us at {ContactEmail}.", - "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 to email us at {ContactEmail}.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of 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 {RepoLinkStart}here{LinkEnd}. This site is licensed under the BSD 0-Clause license giving you the full freedom to do whatever you would like with any of the content on this site.", - "translation": "", - "placeholders": [ - { - "id": "RepoLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "repoLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "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 {ContactEmail} 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 {ContactEmail} or contact ‘@onetruemangoman’ on Discord.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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": "Dutch Euro Coin Designs", - "message": "Dutch Euro Coin Designs", - "translation": "" - }, - { - "id": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "message": "From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.", - "translation": "" - }, - { - "id": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "message": "Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.", - "translation": "" - }, - { - "id": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "message": "The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.", - "translation": "" - }, - { - "id": "Euro Coin Designs", - "message": "Euro Coin Designs", - "translation": "" - }, - { - "id": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "message": "Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the {VarietiesLinkStart}varieties{LinkEnd} page.", - "translation": "", - "placeholders": [ - { - "id": "VarietiesLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "varietiesLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "Euro Coin Mintages", - "message": "Euro Coin Mintages", - "translation": "" - }, - { - "id": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "message": "Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.", - "translation": "" - }, - { - "id": "Additional Notes", - "message": "Additional Notes", - "translation": "" - }, - { - "id": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "message": "Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, {MuntrolpakketLinkStart}click here{LinkEnd}.", - "translation": "", - "placeholders": [ - { - "id": "MuntrolpakketLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "muntrolpakketLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "message": "In 2003 Numista calculated a total of {217503} coins issued for coin sets per denomination. Our own calculations found only {177003}. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", - "translation": "", - "placeholders": [ - { - "id": "217503", - "string": "%[1]d", - "type": "int", - "underlyingType": "int", - "argNum": 1, - "expr": "217503" - }, - { - "id": "177003", - "string": "%[2]d", - "type": "int", - "underlyingType": "int", - "argNum": 2, - "expr": "177003" - } - ] - }, - { - "id": "Country", - "message": "Country", - "translation": "" - }, - { - "id": "Circulation Coins", - "message": "Circulation Coins", - "translation": "" - }, - { - "id": "NIFC / BU Sets", - "message": "NIFC / BU Sets", - "translation": "" - }, - { - "id": "Proof Coins", - "message": "Proof Coins", - "translation": "" - }, - { - "id": "Filter", - "message": "Filter", - "translation": "" - }, - { - "id": "Standard Issue Coins", - "message": "Standard Issue Coins", - "translation": "" - }, - { - "id": "Year", - "message": "Year", - "translation": "" - }, - { - "id": "Unknown", - "message": "Unknown", - "translation": "" - }, - { - "id": "Commemorative Coins", - "message": "Commemorative Coins", - "translation": "" - }, - { - "id": "Commemorated Issue", - "message": "Commemorated Issue", - "translation": "" - }, - { - "id": "Mintage", - "message": "Mintage", - "translation": "" - }, - { - "id": "Euro Coins", - "message": "Euro Coins", - "translation": "" - }, - { - "id": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "message": "On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the {NewsLinkStart}news{LinkEnd} tab!", - "translation": "", - "placeholders": [ - { - "id": "NewsLinkStart", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "newsLinkStart" - }, - { - "id": "LinkEnd", - "string": "%[2]s", - "type": "string", - "underlyingType": "string", - "argNum": 2, - "expr": "linkEnd" - } - ] - }, - { - "id": "Designs", - "message": "Designs", - "translation": "" - }, - { - "id": "View the 600+ different Euro-coin designs!", - "message": "View the 600+ different Euro-coin designs!", - "translation": "" - }, - { - "id": "Mintages", - "message": "Mintages", - "translation": "" - }, - { - "id": "View the mintage figures of all the Euro coins!", - "message": "View the mintage figures of all the Euro coins!", - "translation": "" - }, - { - "id": "Varieties", - "message": "Varieties", - "translation": "" - }, - { - "id": "View all the known Euro varieties!", - "message": "View all the known Euro varieties!", - "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 {ContactEmail}.", - "message": "If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at {ContactEmail}.", - "translation": "", - "placeholders": [ - { - "id": "ContactEmail", - "string": "%[1]s", - "type": "string", - "underlyingType": "string", - "argNum": 1, - "expr": "contactEmail" - } - ] - }, - { - "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": "" - }, - { - "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": "" - } - ] -} \ No newline at end of file diff --git a/lib/mintage/parser.go b/lib/mintage/parser.go deleted file mode 100644 index 364b6e8..0000000 --- a/lib/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/lib/mintage/parser_test.go b/lib/mintage/parser_test.go deleted file mode 100644 index 76e0f01..0000000 --- a/lib/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/main.go b/main.go index 4c9f4ba..7c541d0 100644 --- a/main.go +++ b/main.go @@ -1,48 +1,23 @@ package main import ( - "cmp" - "context" - "errors" "flag" - "fmt" - "log" - "math" - "net/http" - "os" - "path/filepath" - "slices" - "strconv" - "strings" - "git.thomasvoss.com/euro-cash.eu/lib" - "git.thomasvoss.com/euro-cash.eu/lib/email" - "git.thomasvoss.com/euro-cash.eu/lib/mintage" - "git.thomasvoss.com/euro-cash.eu/template" - "github.com/a-h/templ" + "git.thomasvoss.com/euro-cash.eu/src" + "git.thomasvoss.com/euro-cash.eu/src/email" ) -var emailDisabled bool - -var ( - notFound = template.NotFound() - components = map[string]templ.Component{ - "/": template.Root(), - "/about": template.About(), - "/coins": template.Coins(), - "/coins/designs": template.CoinsDesigns(), - "/coins/designs/nl": template.CoinsDesignsNl(), - "/coins/mintages": template.CoinsMintages(), - "/jargon": template.Jargon(), - "/language": template.Language(), - } -) +// components = map[string]templ.Component{ +// "/coins": template.Coins(), +// "/coins/designs": template.CoinsDesigns(), +// "/coins/designs/nl": template.CoinsDesignsNl(), +// "/coins/mintages": template.CoinsMintages(), +// "/jargon": template.Jargon(), +// } func main() { - lib.InitPrinters() - port := flag.Int("port", 8080, "port number") - flag.BoolVar(&emailDisabled, "no-email", false, + flag.BoolVar(&email.Config.Disabled, "no-email", false, "disables email support") flag.StringVar(&email.Config.Host, "smtp-host", "smtp.migadu.com", "SMTP server hostname") @@ -56,146 +31,5 @@ func main() { "password to authenticate the email client") flag.Parse() - fs := http.FileServer(http.Dir("static")) - 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", i18nHandler(mintageHandler(http.HandlerFunc(finalHandler)))) - mux.Handle("GET /", i18nHandler(http.HandlerFunc(finalHandler))) - mux.Handle("POST /language", http.HandlerFunc(setUserLanguage)) - - portStr := ":" + strconv.Itoa(*port) - log.Println("Listening on", portStr) - log.Fatal(http.ListenAndServe(portStr, mux)) -} - -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] - } - - c, ok := components[path] - if !ok { - w.WriteHeader(http.StatusNotFound) - c = notFound - } - - /* 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(), "/"), - }) - } - template.Base(c).Render(r.Context(), w) -} - -func i18nHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var p, pZero lib.Printer - - if c, err := r.Cookie("locale"); err == nil { - p = lib.Printers[strings.ToLower(c.Value)] - } - - ctx := context.WithValue( - r.Context(), "printer", cmp.Or(p, lib.DefaultPrinter)) - - if p == pZero { - http.SetCookie(w, &http.Cookie{ - Name: "redirect", - Value: r.URL.Path, - }) - template.Base(template.Language()).Render(ctx, w) - } else { - next.ServeHTTP(w, r.WithContext(ctx)) - } - }) -} - -func mintageHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - countries := lib.SortedCountries( - r.Context().Value("printer").(lib.Printer)) - - code := strings.ToLower(r.FormValue("code")) - if !slices.ContainsFunc(countries, func(c lib.Country) bool { - return c.Code == code - }) { - code = countries[0].Code - } - - ctype := strings.ToLower(r.FormValue("type")) - switch ctype { - case "circ", "nifc", "proof": - default: - ctype = "circ" - } - - path := filepath.Join("data", "mintages", code) - f, err := os.Open(path) - if err != nil { - throwError(http.StatusInternalServerError, err, w, r) - return - } - defer f.Close() - - data, err := mintage.Parse(f, path) - if err != nil { - throwError(http.StatusInternalServerError, err, w, r) - return - } - - ctx := context.WithValue(r.Context(), "code", code) - ctx = context.WithValue(ctx, "type", ctype) - ctx = context.WithValue(ctx, "mintages", data) - ctx = context.WithValue(ctx, "countries", countries) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func setUserLanguage(w http.ResponseWriter, r *http.Request) { - loc := r.FormValue("locale") - _, ok := lib.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) - if emailDisabled { - log.Print(err) - } else { - go func() { - if err := email.ServerError(err); err != nil { - log.Print(err) - } - }() - } - template.Base(template.Error(status)).Render(r.Context(), w) + src.Run(*port) } diff --git a/rosetta/bg/messages.gotext.json b/rosetta/bg/messages.gotext.json new file mode 100644 index 0000000..7445299 --- /dev/null +++ b/rosetta/bg/messages.gotext.json @@ -0,0 +1,285 @@ +{ + "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": "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": "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/rosetta/el/messages.gotext.json b/rosetta/el/messages.gotext.json new file mode 100644 index 0000000..7cadc9b --- /dev/null +++ b/rosetta/el/messages.gotext.json @@ -0,0 +1,285 @@ +{ + "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": "Η σελίδα που ψάχνατε δεν υπάρχει. Εάν πιστεύετε ότι πρόκειται για λάθος, μην διστάσετε να επικοινωνήσετε με το @onetruemangoman στο Discord ή να μας στείλετε email στο %s." + }, + { + "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": "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": "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/rosetta/en/messages.gotext.json b/rosetta/en/messages.gotext.json new file mode 100644 index 0000000..a9fe4d2 --- /dev/null +++ b/rosetta/en/messages.gotext.json @@ -0,0 +1,397 @@ +{ + "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": "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": "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/rosetta/nl/messages.gotext.json b/rosetta/nl/messages.gotext.json new file mode 100644 index 0000000..65016fb --- /dev/null +++ b/rosetta/nl/messages.gotext.json @@ -0,0 +1,285 @@ +{ + "language": "nl", + "messages": [ + { + "id": "Andorra", + "message": "Andorra", + "translation": "Andorra" + }, + { + "id": "Austria", + "message": "Austria", + "translation": "Oostenrijk" + }, + { + "id": "Belgium", + "message": "Belgium", + "translation": "België" + }, + { + "id": "Cyprus", + "message": "Cyprus", + "translation": "Cyprus" + }, + { + "id": "Germany", + "message": "Germany", + "translation": "Duitsland" + }, + { + "id": "Estonia", + "message": "Estonia", + "translation": "Estland" + }, + { + "id": "Spain", + "message": "Spain", + "translation": "Spanje" + }, + { + "id": "Finland", + "message": "Finland", + "translation": "Finland" + }, + { + "id": "France", + "message": "France", + "translation": "Frankrijk" + }, + { + "id": "Greece", + "message": "Greece", + "translation": "Griekenland" + }, + { + "id": "Croatia", + "message": "Croatia", + "translation": "Kroatië" + }, + { + "id": "Ireland", + "message": "Ireland", + "translation": "Ierland" + }, + { + "id": "Italy", + "message": "Italy", + "translation": "Italië" + }, + { + "id": "Lithuania", + "message": "Lithuania", + "translation": "Litouwen" + }, + { + "id": "Luxembourg", + "message": "Luxembourg", + "translation": "Luxemburg" + }, + { + "id": "Latvia", + "message": "Latvia", + "translation": "Letland" + }, + { + "id": "Monaco", + "message": "Monaco", + "translation": "Monaco" + }, + { + "id": "Malta", + "message": "Malta", + "translation": "Malta" + }, + { + "id": "Netherlands", + "message": "Netherlands", + "translation": "Nederland" + }, + { + "id": "Portugal", + "message": "Portugal", + "translation": "Portugal" + }, + { + "id": "Slovenia", + "message": "Slovenia", + "translation": "Slovenië" + }, + { + "id": "Slovakia", + "message": "Slovakia", + "translation": "Slowakije" + }, + { + "id": "San Marino", + "message": "San Marino", + "translation": "San Marino" + }, + { + "id": "Vatican City", + "message": "Vatican City", + "translation": "Vaticaanstad" + }, + { + "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": "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": "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/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 }} diff --git a/template.old/404.templ b/template.old/404.templ new file mode 100644 index 0000000..9a03b43 --- /dev/null +++ b/template.old/404.templ @@ -0,0 +1,16 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +templ NotFound() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("Page Not Found") }

+
+
+

+ @templ.Raw(p.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 to email us at %s.", contactEmail)) +

+
+} diff --git a/template.old/about.templ b/template.old/about.templ new file mode 100644 index 0000000..d7dfa06 --- /dev/null +++ b/template.old/about.templ @@ -0,0 +1,52 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +const ( + contactEmail = `mail@euro-cash.eu` + repoLinkStart = `` + linkEnd = `` +) + +templ About() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("About Us") }

+
+
+

{ p.T("Open Source") }

+

+ @templ.Raw(p.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 any of the content on this site.", repoLinkStart, linkEnd)) +

+

{ p.T("Contact Us") }

+

+ @templ.Raw(p.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.", contactEmail)) +

+

{ p.T("Special Thanks") }

+ + + + + + + + + + + + + +
{ p.T("Development") }{ p.T("Research") }{ p.T("Translations") }
+ Jessika Wexler, + Lyyli Savolainen, + Ralf Nadel + + Elín Hjartardóttir, + Storm Sørensen + + Thomas Voss, + Védís Indriðadóttir +
+
+} diff --git a/template.old/base.templ b/template.old/base.templ new file mode 100644 index 0000000..dcef2d8 --- /dev/null +++ b/template.old/base.templ @@ -0,0 +1,51 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +templ Base(body templ.Component) { + {{ p := ctx.Value("printer").(lib.Printer) }} + + + + + + + Euro Cash + + + + @body + + + +} diff --git a/template.old/coins.templ b/template.old/coins.templ new file mode 100644 index 0000000..ef1b0a1 --- /dev/null +++ b/template.old/coins.templ @@ -0,0 +1,53 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +const newsLinkStart = `` + +templ Coins() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("Euro Coins") }

+
+
+

+ @templ.Raw(p.T("On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the %snews%s tab!", newsLinkStart, linkEnd)) +

+
+
+ +
+
+} diff --git a/template.old/coins_designs.templ b/template.old/coins_designs.templ new file mode 100644 index 0000000..de47482 --- /dev/null +++ b/template.old/coins_designs.templ @@ -0,0 +1,41 @@ +package template + +import ( + "fmt" + "strings" + + "git.thomasvoss.com/euro-cash.eu/lib" +) + +var varietiesLinkStart = `` + +func makeURL(c lib.Country) templ.SafeURL { + url := fmt.Sprintf("/coins/designs/%s", c.Code) + return templ.SafeURL(url) +} + +templ CoinsDesigns() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("Euro Coin Designs") }

+
+
+

+ @templ.Raw(p.T("Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the %svarieties%s page.", varietiesLinkStart, linkEnd)) +

+
+
+
+} diff --git a/template.old/coins_designs_nl.templ b/template.old/coins_designs_nl.templ new file mode 100644 index 0000000..92c01be --- /dev/null +++ b/template.old/coins_designs_nl.templ @@ -0,0 +1,43 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/src" + +templ CoinsDesignsNl() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("Dutch Euro Coin Designs") }

+
+
+
+ Netherlands Queen Beatrix €0.50 Coin + Netherlands King Willem-Alexander €0.50 Coin +
+
+ Netherlands Queen Beatrix €1 Coin + Netherlands King Willem-Alexander €1 Coin +
+

+ { p.T("From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.") } +

+

+ { p.T("Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.") } +

+

+ + { p.T("The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.") } +

+
+} diff --git a/template.old/coins_mintages.templ b/template.old/coins_mintages.templ new file mode 100644 index 0000000..894cb75 --- /dev/null +++ b/template.old/coins_mintages.templ @@ -0,0 +1,167 @@ +package template + +import ( + "strconv" + + "git.thomasvoss.com/euro-cash.eu/lib" + "git.thomasvoss.com/euro-cash.eu/lib/mintage" +) + +const muntrolpakketLinkStart = `` + +var denoms = [...]float64{ + 0.01, 0.02, 0.05, 0.10, + 0.20, 0.50, 1.00, 2.00, +} + +templ CoinsMintages() { + {{ + code := ctx.Value("code").(string) + data := ctx.Value("mintages").(mintage.Data) + ctype := ctx.Value("type").(string) + p := ctx.Value("printer").(lib.Printer) + }} +
+ @navbar() +

{ p.T("Euro Coin Mintages") }

+
+
+

+ { p.T("Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.") } +

+
+ if code == "nl" { +

{ p.T("Additional Notes") }

+
    +
  • + @templ.Raw(p.T("Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, %sclick here%s.", muntrolpakketLinkStart, linkEnd)) +
  • +
  • + { p.T("In 2003 Numista calculated a total of %d coins issued for coin sets per denomination. Our own calculations found only %d. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", 217503, 177003) } +
  • +
+ } +
+
+
+ +
+ @coinTypeRadio(ctype, "circ", p.T("Circulation Coins")) + @coinTypeRadio(ctype, "nifc", p.T("NIFC / BU Sets")) + @coinTypeRadio(ctype, "proof", p.T("Proof Coins")) +
+
+ +
+
+
{ p.T("Standard Issue Coins") }
+ + + + for _, x := range denoms { + + } + + + for _, row := range data.Standard { + + + for _, col := range row.Mintages[strToCtype(ctype)] { + switch col { + case mintage.Unknown: + + case 0: + + default: + + } + } + + } + +
{ p.T("Year") }{ p.Money(x, false) }
+ if len(row.Mintmark) != 0 { + { strconv.Itoa(row.Year) } { row.Mintmark } + } else { + { strconv.Itoa(row.Year) } + } + { p.T("Unknown") }{ p.N(col) }
+
+ if len(data.Commemorative) != 0 { +
+
{ p.T("Commemorative Coins") }
+ + + + + + + + for _, row := range data.Commemorative { + + + + + switch row.Mintage[strToCtype(ctype)] { + case mintage.Unknown: + + case 0: + + default: + + } + + } + +
{ p.T("Year") }{ p.T("Commemorated Issue") }{ p.T("Mintage") }
+ if len(row.Mintmark) != 0 { + { strconv.Itoa(row.Year) } { row.Mintmark } + } else { + { strconv.Itoa(row.Year) } + } + { row.Name }{ p.T("Unknown") }{ p.N(row.Mintage[strToCtype(ctype)]) }
+
+ } +
+
+} + +templ coinTypeRadio(ctype, short, long string) { + +} + +func strToCtype(s string) int { + switch s { + case "circ": + return mintage.TypeCirc + case "nifc": + return mintage.TypeNIFC + case "proof": + return mintage.TypeProof + } + + /* Should never happen, but just incase */ + return mintage.TypeCirc +} diff --git a/template.old/error.templ b/template.old/error.templ new file mode 100644 index 0000000..d2fed83 --- /dev/null +++ b/template.old/error.templ @@ -0,0 +1,24 @@ +package template + +import ( + "net/http" + "strconv" + + "git.thomasvoss.com/euro-cash.eu/lib" +) + +templ Error(status int) { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ strconv.Itoa(status) } { http.StatusText(status) }

+
+
+

+ { p.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.") } +

+

+ @templ.Raw(p.T("If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", contactEmail)) +

+
+} diff --git a/template.old/jargon.templ b/template.old/jargon.templ new file mode 100644 index 0000000..255068e --- /dev/null +++ b/template.old/jargon.templ @@ -0,0 +1,63 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +templ Jargon() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("Euro Cash Jargon") }

+
+
+

+ { p.T("Both on this website and in other euro-cash-related forums there are many terms you will come across that you may not immediately understand. This page will hopefully get you up to speed with the most important and frequently-used terminology.") } +

+

+ { p.T("All terms defined below can be used as clickable links which highlight the selected term. It is recommended to use these links when sharing this page with others, so that the relevant terms are highlighted.") } +

+
+

{ p.T("General Terms") }

+
+ @dt("nifc", p.T("NIFC — Not Intended For Circulation")) +
+

+ { p.T("NIFC coins are coins minted without the intention of being put into general circulation. These coins are typically minted with the purpose of being put into coincards or coin-sets to be sold to collectors. Occasionally they are also handed out to collectors for face value at banks.") } +

+

+ { p.T("While uncommon, NIFC coins are occasionally found in circulation. This can happen for a variety of reasons such as someone depositing their coin collection (known as a ‘collection dump’), or a collector’s child spending their rare coins on an ice cream. Some coin mints have also been known to put NIFC coins that have gone unsold for multiple years into circulation.") } +

+
+ @dt("au", p.T("AU — Almost Uncirculated")) +
+ { p.T("AU coins are coins that are in extremely good condition as a result of limited use in circulation. Unlike the term ‘UNC’, this term is a description of the coins quality, not its usage. AU coins often appear to retain most of their original luster as well as possessing little-to-no scratches or other forms of post-mint damage (PMD).") } +
+ @dt("bu", p.T("BU — Brilliantly Uncirculated")) +
+ { p.T("BU is a general term to refer to coins from coincards and -sets. These are different from UNC coins in that they are typically handled with more care during the minting process and are struck with higher-quality dies than the coins minted for coin rolls resulting in a higher-quality end product. You may also see these coins referred to by the French term ‘fleur de coin’.") } +
+ @dt("pmd", p.T("PMD — Post-Mint Damage")) +
+ { p.T("Post-mint damage is any damage that a coin has sustained outside of the minting process, such as through being dropped on the ground, hit against a table, etc.") } +
+ @dt("unc", p.T("UNC — Uncirculated")) +
+ { p.T("Uncirculated coins are coins that have never been used in a monetary exchange. The term ‘UNC’ is often mistakenly used to refer to coins in very good condition, but this is incorrect. A coin in poor condition that has never been circulated is still considered an ‘UNC’ coin.") } +
+
+

{ p.T("Collector-Specific Terms") }

+
+ @dt("crh", p.T("CRH — Coin Roll Hunting")) +
+ { p.T("Coin roll hunting is a general term for the activity of searching through coin rolls and -bags to find coins for a collection. Coin rolls and bags are often obtained at banks or coin roll machines.") } +
+
+
+} + +templ dt(id, title string) { +
+ + { title } + +
+} diff --git a/template.old/language.templ b/template.old/language.templ new file mode 100644 index 0000000..1b48295 --- /dev/null +++ b/template.old/language.templ @@ -0,0 +1,54 @@ +package template + +import ( + "strings" + + "git.thomasvoss.com/euro-cash.eu/lib" +) + +templ Language() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +

{ p.T("Select Your Language") }

+
+
+

+ { p.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. +

+
+

{ p.T("Eurozone Languages") }

+ @languageGrid(true) +

{ p.T("Other Languages") }

+ @languageGrid(false) +
+} + +templ languageGrid(eurozone bool) { +
+
+ for _, loc := range lib.Locales { + if loc.Eurozone == eurozone { + + } + } +
+
+} diff --git a/template.old/navbar.templ b/template.old/navbar.templ new file mode 100644 index 0000000..85ad8a2 --- /dev/null +++ b/template.old/navbar.templ @@ -0,0 +1,241 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +templ navbar() { + {{ p := ctx.Value("printer").(lib.Printer) }} + +} + +css noMargin() { + margin: 0; +} diff --git a/template.old/root.templ b/template.old/root.templ new file mode 100644 index 0000000..657314c --- /dev/null +++ b/template.old/root.templ @@ -0,0 +1,23 @@ +package template + +import "git.thomasvoss.com/euro-cash.eu/lib" + +templ Root() { + {{ p := ctx.Value("printer").(lib.Printer) }} +
+ @navbar() +
+

{ p.T("The Euro Cash Compendium") }

+

+ { p.T("United in") } + { p.T("diversity") } + { p.T("cash") } +

+
+
+
+

+ { p.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.") } +

+
+} diff --git a/template/404.templ b/template/404.templ deleted file mode 100644 index 9a03b43..0000000 --- a/template/404.templ +++ /dev/null @@ -1,16 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -templ NotFound() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("Page Not Found") }

-
-
-

- @templ.Raw(p.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 to email us at %s.", contactEmail)) -

-
-} diff --git a/template/about.templ b/template/about.templ deleted file mode 100644 index d7dfa06..0000000 --- a/template/about.templ +++ /dev/null @@ -1,52 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -const ( - contactEmail = `mail@euro-cash.eu` - repoLinkStart = `` - linkEnd = `` -) - -templ About() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("About Us") }

-
-
-

{ p.T("Open Source") }

-

- @templ.Raw(p.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 any of the content on this site.", repoLinkStart, linkEnd)) -

-

{ p.T("Contact Us") }

-

- @templ.Raw(p.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.", contactEmail)) -

-

{ p.T("Special Thanks") }

- - - - - - - - - - - - - -
{ p.T("Development") }{ p.T("Research") }{ p.T("Translations") }
- Jessika Wexler, - Lyyli Savolainen, - Ralf Nadel - - Elín Hjartardóttir, - Storm Sørensen - - Thomas Voss, - Védís Indriðadóttir -
-
-} diff --git a/template/base.go b/template/base.go deleted file mode 100644 index 392b1c2..0000000 --- a/template/base.go +++ /dev/null @@ -1,3 +0,0 @@ -//go:generate templ generate -log-level warn - -package template diff --git a/template/base.templ b/template/base.templ deleted file mode 100644 index dcef2d8..0000000 --- a/template/base.templ +++ /dev/null @@ -1,51 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -templ Base(body templ.Component) { - {{ p := ctx.Value("printer").(lib.Printer) }} - - - - - - - Euro Cash - - - - @body - - - -} diff --git a/template/coins.templ b/template/coins.templ deleted file mode 100644 index ef1b0a1..0000000 --- a/template/coins.templ +++ /dev/null @@ -1,53 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -const newsLinkStart = `` - -templ Coins() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("Euro Coins") }

-
-
-

- @templ.Raw(p.T("On this section of the site you can find everything there is to know about the coins of the Eurozone. For the latest news on coin- and design releases, check out the %snews%s tab!", newsLinkStart, linkEnd)) -

-
-
- -
-
-} diff --git a/template/coins_designs.templ b/template/coins_designs.templ deleted file mode 100644 index de47482..0000000 --- a/template/coins_designs.templ +++ /dev/null @@ -1,41 +0,0 @@ -package template - -import ( - "fmt" - "strings" - - "git.thomasvoss.com/euro-cash.eu/lib" -) - -var varietiesLinkStart = `` - -func makeURL(c lib.Country) templ.SafeURL { - url := fmt.Sprintf("/coins/designs/%s", c.Code) - return templ.SafeURL(url) -} - -templ CoinsDesigns() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("Euro Coin Designs") }

-
-
-

- @templ.Raw(p.T("Here you’ll be able to view all the coin designs for each country in the Eurozone. This section of the site doesn’t include minor varieties such as different mintmarks or errors; those are on the %svarieties%s page.", varietiesLinkStart, linkEnd)) -

-
-
-
-} diff --git a/template/coins_designs_nl.templ b/template/coins_designs_nl.templ deleted file mode 100644 index 819b294..0000000 --- a/template/coins_designs_nl.templ +++ /dev/null @@ -1,43 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -templ CoinsDesignsNl() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("Dutch Euro Coin Designs") }

-
-
-
- Netherlands Queen Beatrix €0.50 Coin - Netherlands King Willem-Alexander €0.50 Coin -
-
- Netherlands Queen Beatrix €1 Coin - Netherlands King Willem-Alexander €1 Coin -
-

- { p.T("From the years 1999–2013 all Dutch euro coins featured the portrait of Queen Beatrix of the Netherlands. After her abdication from the throne in 2013 the designs of all denominations were changed to feature the portrait of the new King Willem-Alexander. After her abdication the direction in which the monarchs portrait faced was flipped; a tradition dating back to the earliest coins of the Kingdom of the Netherlands.") } -

-

- { p.T("Coins featuring both monarchs contain text reading ‘BEATRIX KONINGIN DER NEDERLANDEN’ (‘BEATRIX QUEEN OF THE NETHERLANDS’) and ‘Willem-Alexander Koning der Nederlanden’ (‘Willem-Alexander King of the Netherlands’) respectively.") } -

-

- - { p.T("The €1 and €2 coins featuring King Willem-Alexander were minted with a much lower relief than most euro coins of the same denomination. As a result it is not uncommon for these coins to appear worn after little use in circulation.") } -

-
-} diff --git a/template/coins_mintages.templ b/template/coins_mintages.templ deleted file mode 100644 index 894cb75..0000000 --- a/template/coins_mintages.templ +++ /dev/null @@ -1,167 +0,0 @@ -package template - -import ( - "strconv" - - "git.thomasvoss.com/euro-cash.eu/lib" - "git.thomasvoss.com/euro-cash.eu/lib/mintage" -) - -const muntrolpakketLinkStart = `` - -var denoms = [...]float64{ - 0.01, 0.02, 0.05, 0.10, - 0.20, 0.50, 1.00, 2.00, -} - -templ CoinsMintages() { - {{ - code := ctx.Value("code").(string) - data := ctx.Value("mintages").(mintage.Data) - ctype := ctx.Value("type").(string) - p := ctx.Value("printer").(lib.Printer) - }} -
- @navbar() -

{ p.T("Euro Coin Mintages") }

-
-
-

- { p.T("Here you’ll be able to view all the known mintages for all coins. You’ll also be able to filter on country, denomination, etc. If you have any mintage data that’s missing from our site, feel free to contact us.") } -

-
- if code == "nl" { -

{ p.T("Additional Notes") }

-
    -
  • - @templ.Raw(p.T("Most coins from the years 2003–2016 are listed as NIFC coins while other popular sources such as Numista claim they were minted for circulation. For more information on why others are wrong, %sclick here%s.", muntrolpakketLinkStart, linkEnd)) -
  • -
  • - { p.T("In 2003 Numista calculated a total of %d coins issued for coin sets per denomination. Our own calculations found only %d. Numista also forgot to include the many hundred thousand coins from the coin roll sets that were produced.", 217503, 177003) } -
  • -
- } -
-
-
- -
- @coinTypeRadio(ctype, "circ", p.T("Circulation Coins")) - @coinTypeRadio(ctype, "nifc", p.T("NIFC / BU Sets")) - @coinTypeRadio(ctype, "proof", p.T("Proof Coins")) -
-
- -
-
-
{ p.T("Standard Issue Coins") }
- - - - for _, x := range denoms { - - } - - - for _, row := range data.Standard { - - - for _, col := range row.Mintages[strToCtype(ctype)] { - switch col { - case mintage.Unknown: - - case 0: - - default: - - } - } - - } - -
{ p.T("Year") }{ p.Money(x, false) }
- if len(row.Mintmark) != 0 { - { strconv.Itoa(row.Year) } { row.Mintmark } - } else { - { strconv.Itoa(row.Year) } - } - { p.T("Unknown") }{ p.N(col) }
-
- if len(data.Commemorative) != 0 { -
-
{ p.T("Commemorative Coins") }
- - - - - - - - for _, row := range data.Commemorative { - - - - - switch row.Mintage[strToCtype(ctype)] { - case mintage.Unknown: - - case 0: - - default: - - } - - } - -
{ p.T("Year") }{ p.T("Commemorated Issue") }{ p.T("Mintage") }
- if len(row.Mintmark) != 0 { - { strconv.Itoa(row.Year) } { row.Mintmark } - } else { - { strconv.Itoa(row.Year) } - } - { row.Name }{ p.T("Unknown") }{ p.N(row.Mintage[strToCtype(ctype)]) }
-
- } -
-
-} - -templ coinTypeRadio(ctype, short, long string) { - -} - -func strToCtype(s string) int { - switch s { - case "circ": - return mintage.TypeCirc - case "nifc": - return mintage.TypeNIFC - case "proof": - return mintage.TypeProof - } - - /* Should never happen, but just incase */ - return mintage.TypeCirc -} diff --git a/template/error.templ b/template/error.templ deleted file mode 100644 index d2fed83..0000000 --- a/template/error.templ +++ /dev/null @@ -1,24 +0,0 @@ -package template - -import ( - "net/http" - "strconv" - - "git.thomasvoss.com/euro-cash.eu/lib" -) - -templ Error(status int) { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ strconv.Itoa(status) } { http.StatusText(status) }

-
-
-

- { p.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.") } -

-

- @templ.Raw(p.T("If this issue persists, don’t hesitate to contact @onetruemangoman on Discord or to email us at %s.", contactEmail)) -

-
-} diff --git a/template/jargon.templ b/template/jargon.templ deleted file mode 100644 index 255068e..0000000 --- a/template/jargon.templ +++ /dev/null @@ -1,63 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -templ Jargon() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("Euro Cash Jargon") }

-
-
-

- { p.T("Both on this website and in other euro-cash-related forums there are many terms you will come across that you may not immediately understand. This page will hopefully get you up to speed with the most important and frequently-used terminology.") } -

-

- { p.T("All terms defined below can be used as clickable links which highlight the selected term. It is recommended to use these links when sharing this page with others, so that the relevant terms are highlighted.") } -

-
-

{ p.T("General Terms") }

-
- @dt("nifc", p.T("NIFC — Not Intended For Circulation")) -
-

- { p.T("NIFC coins are coins minted without the intention of being put into general circulation. These coins are typically minted with the purpose of being put into coincards or coin-sets to be sold to collectors. Occasionally they are also handed out to collectors for face value at banks.") } -

-

- { p.T("While uncommon, NIFC coins are occasionally found in circulation. This can happen for a variety of reasons such as someone depositing their coin collection (known as a ‘collection dump’), or a collector’s child spending their rare coins on an ice cream. Some coin mints have also been known to put NIFC coins that have gone unsold for multiple years into circulation.") } -

-
- @dt("au", p.T("AU — Almost Uncirculated")) -
- { p.T("AU coins are coins that are in extremely good condition as a result of limited use in circulation. Unlike the term ‘UNC’, this term is a description of the coins quality, not its usage. AU coins often appear to retain most of their original luster as well as possessing little-to-no scratches or other forms of post-mint damage (PMD).") } -
- @dt("bu", p.T("BU — Brilliantly Uncirculated")) -
- { p.T("BU is a general term to refer to coins from coincards and -sets. These are different from UNC coins in that they are typically handled with more care during the minting process and are struck with higher-quality dies than the coins minted for coin rolls resulting in a higher-quality end product. You may also see these coins referred to by the French term ‘fleur de coin’.") } -
- @dt("pmd", p.T("PMD — Post-Mint Damage")) -
- { p.T("Post-mint damage is any damage that a coin has sustained outside of the minting process, such as through being dropped on the ground, hit against a table, etc.") } -
- @dt("unc", p.T("UNC — Uncirculated")) -
- { p.T("Uncirculated coins are coins that have never been used in a monetary exchange. The term ‘UNC’ is often mistakenly used to refer to coins in very good condition, but this is incorrect. A coin in poor condition that has never been circulated is still considered an ‘UNC’ coin.") } -
-
-

{ p.T("Collector-Specific Terms") }

-
- @dt("crh", p.T("CRH — Coin Roll Hunting")) -
- { p.T("Coin roll hunting is a general term for the activity of searching through coin rolls and -bags to find coins for a collection. Coin rolls and bags are often obtained at banks or coin roll machines.") } -
-
-
-} - -templ dt(id, title string) { -
- - { title } - -
-} diff --git a/template/language.templ b/template/language.templ deleted file mode 100644 index 1b48295..0000000 --- a/template/language.templ +++ /dev/null @@ -1,54 +0,0 @@ -package template - -import ( - "strings" - - "git.thomasvoss.com/euro-cash.eu/lib" -) - -templ Language() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -

{ p.T("Select Your Language") }

-
-
-

- { p.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. -

-
-

{ p.T("Eurozone Languages") }

- @languageGrid(true) -

{ p.T("Other Languages") }

- @languageGrid(false) -
-} - -templ languageGrid(eurozone bool) { -
-
- for _, loc := range lib.Locales { - if loc.Eurozone == eurozone { - - } - } -
-
-} diff --git a/template/navbar.templ b/template/navbar.templ deleted file mode 100644 index 85ad8a2..0000000 --- a/template/navbar.templ +++ /dev/null @@ -1,241 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -templ navbar() { - {{ p := ctx.Value("printer").(lib.Printer) }} - -} - -css noMargin() { - margin: 0; -} diff --git a/template/root.templ b/template/root.templ deleted file mode 100644 index 657314c..0000000 --- a/template/root.templ +++ /dev/null @@ -1,23 +0,0 @@ -package template - -import "git.thomasvoss.com/euro-cash.eu/lib" - -templ Root() { - {{ p := ctx.Value("printer").(lib.Printer) }} -
- @navbar() -
-

{ p.T("The Euro Cash Compendium") }

-

- { p.T("United in") } - { p.T("diversity") } - { p.T("cash") } -

-
-
-
-

- { p.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.") } -

-
-} diff --git a/vendor/github.com/a-h/templ/.dockerignore b/vendor/github.com/a-h/templ/.dockerignore deleted file mode 100644 index 17896fe..0000000 --- a/vendor/github.com/a-h/templ/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.git -Dockerfile -.dockerignore diff --git a/vendor/github.com/a-h/templ/.gitignore b/vendor/github.com/a-h/templ/.gitignore deleted file mode 100644 index 0338eda..0000000 --- a/vendor/github.com/a-h/templ/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Output. -cmd/templ/templ - -# Logs. -cmd/templ/lspcmd/*log.txt - -# Go code coverage. -coverage.out -coverage - -# Mac filesystem jank. -.DS_Store - -# Docusaurus. -docs/build/ -docs/resources/_gen/ -node_modules/ -dist/ - -# Nix artifacts. -result - -# Editors -## nvim -.null-ls* - -# Go workspace. -go.work diff --git a/vendor/github.com/a-h/templ/.goreleaser.yaml b/vendor/github.com/a-h/templ/.goreleaser.yaml deleted file mode 100644 index 456187c..0000000 --- a/vendor/github.com/a-h/templ/.goreleaser.yaml +++ /dev/null @@ -1,72 +0,0 @@ -builds: - - env: - - CGO_ENABLED=0 - dir: cmd/templ - mod_timestamp: '{{ .CommitTimestamp }}' - flags: - - -trimpath - ldflags: - - -s -w - goos: - - linux - - windows - - darwin - -checksum: - name_template: 'checksums.txt' - -signs: - - id: checksums - cmd: cosign - stdin: '{{ .Env.COSIGN_PASSWORD }}' - output: true - artifacts: checksum - args: - - sign-blob - - --yes - - --key - - env://COSIGN_PRIVATE_KEY - - '--output-certificate=${certificate}' - - '--output-signature=${signature}' - - '${artifact}' - -archives: - - format: tar.gz - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} - -kos: - - repository: ghcr.io/a-h/templ - platforms: - - linux/amd64 - - linux/arm64 - tags: - - latest - - '{{.Tag}}' - bare: true - -docker_signs: - - cmd: cosign - artifacts: all - output: true - args: - - sign - - --yes - - --key - - env://COSIGN_PRIVATE_KEY - - '${artifact}' - -snapshot: - name_template: "{{ incpatch .Version }}-next" - -changelog: - sort: asc - filters: - exclude: - - '^docs:' - - '^test:' diff --git a/vendor/github.com/a-h/templ/.ignore b/vendor/github.com/a-h/templ/.ignore deleted file mode 100644 index 21cb25e..0000000 --- a/vendor/github.com/a-h/templ/.ignore +++ /dev/null @@ -1,7 +0,0 @@ -*_templ.go -examples/integration-ct/static/index.js -examples/counter/assets/css/bulma.* -examples/counter/assets/js/htmx.min.js -examples/counter-basic/assets/css/bulma.* -examples/typescript/assets/index.js -package-lock.json diff --git a/vendor/github.com/a-h/templ/.version b/vendor/github.com/a-h/templ/.version deleted file mode 100644 index baee64f..0000000 --- a/vendor/github.com/a-h/templ/.version +++ /dev/null @@ -1 +0,0 @@ -0.2.747 \ No newline at end of file diff --git a/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md b/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md deleted file mode 100644 index 08340d3..0000000 --- a/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -adrianhesketh@hushail.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/vendor/github.com/a-h/templ/CONTRIBUTING.md b/vendor/github.com/a-h/templ/CONTRIBUTING.md deleted file mode 100644 index e98d31f..0000000 --- a/vendor/github.com/a-h/templ/CONTRIBUTING.md +++ /dev/null @@ -1,244 +0,0 @@ -# Contributing to templ - -## Vision - -Enable Go developers to build strongly typed, component-based HTML user interfaces with first-class developer tooling, and a short learning curve. - -## Come up with a design and share it - -Before starting work on any major pull requests or code changes, start a discussion at https://github.com/a-h/templ/discussions or raise an issue. - -We don't want you to spend time on a PR or feature that ultimately doesn't get merged because it doesn't fit with the project goals, or the design doesn't work for some reason. - -For issues, it really helps if you provide a reproduction repo, or can create a failing unit test to describe the behaviour. - -In designs, we need to consider: - -* Backwards compatibility - Not changing the public API between releases, introducing gradual deprecation - don't break people's code. -* Correctness over time - How can we reduce the risk of defects both now, and in future releases? -* Threat model - How could each change be used to inject vulnerabilities into web pages? -* Go version - We target the oldest supported version of Go as per https://go.dev/doc/devel/release -* Automatic migration - If we need to force through a change. -* Compile time vs runtime errors - Prefer compile time. -* Documentation - New features are only useful if people can understand the new feature, what would the documentation look like? -* Examples - How will we demonstrate the feature? - -## Project structure - -templ is structured into a few areas: - -### Parser `./parser` - -The parser directory currently contains both v1 and v2 parsers. - -The v1 parser is not maintained, it's only used to migrate v1 code over to the v2 syntax. - -The parser is responsible for parsing templ files into an object model. The types that make up the object model are in `types.go`. Automatic formatting of the types is tested in `types_test.go`. - -A templ file is parsed into the `TemplateFile` struct object model. - -```go -type TemplateFile struct { - // Header contains comments or whitespace at the top of the file. - Header []GoExpression - // Package expression. - Package Package - // Nodes in the file. - Nodes []TemplateFileNode -} -``` - -Parsers are individually tested using two types of unit test. - -One test covers the successful parsing of text into an object. For example, the `HTMLCommentParser` test checks for successful patterns. - -```go -func TestHTMLCommentParser(t *testing.T) { - var tests = []struct { - name string - input string - expected HTMLComment - }{ - { - name: "comment - single line", - input: ``, - expected: HTMLComment{ - Contents: " single line comment ", - }, - }, - { - name: "comment - no whitespace", - input: ``, - expected: HTMLComment{ - Contents: "no whitespace between sequence open and close", - }, - }, - { - name: "comment - multiline", - input: ``, - expected: HTMLComment{ - Contents: ` multiline - comment - `, - }, - }, - { - name: "comment - with tag", - input: ``, - expected: HTMLComment{ - Contents: `

tag

`, - }, - }, - { - name: "comments can contain tags", - input: ``, - expected: HTMLComment{ - Contents: `
hello world
`, - }, - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - input := parse.NewInput(tt.input) - result, ok, err := htmlComment.Parse(input) - if err != nil { - t.Fatalf("parser error: %v", err) - } - if !ok { - t.Fatalf("failed to parse at %d", input.Index()) - } - if diff := cmp.Diff(tt.expected, result); diff != "" { - t.Errorf(diff) - } - }) - } -} -``` - -Alongside each success test, is a similar test to check that invalid syntax is detected. - -```go -func TestHTMLCommentParserErrors(t *testing.T) { - var tests = []struct { - name string - input string - expected error - }{ - { - name: "unclosed HTML comment", - input: `' not found", - parse.Position{ - Index: 26, - Line: 0, - Col: 26, - }), - }, - { - name: "comment in comment", - input: ` -->`, - expected: parse.Error("comment contains invalid sequence '--'", parse.Position{ - Index: 8, - Line: 0, - Col: 8, - }), - }, - } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - input := parse.NewInput(tt.input) - _, _, err := htmlComment.Parse(input) - if diff := cmp.Diff(tt.expected, err); diff != "" { - t.Error(diff) - } - }) - } -} -``` - -### Generator - -The generator takes the object model and writes out Go code that produces the expected output. Any changes to Go code output by templ are made in this area. - -Testing of the generator is carried out by creating a templ file, and a matching expected output file. - -For example, `./generator/test-a-href` contains a templ file of: - -```templ -package testahref - -templ render() { - Ignored - Sanitized - Unsanitized -} -``` - -It also contains an expected output file. - -```html -Ignored -Sanitized -Unsanitized -``` - -These tests contribute towards the code coverage metrics by building an instrumented test CLI program. See the `test-cover` task in the `README.md` file. - -### CLI - -The command line interface for templ is used to generate Go code from templ files, format templ files, and run the LSP. - -The code for this is at `./cmd/templ`. - -Testing of the templ command line is done with unit tests to check the argument parsing. - -The `templ generate` command is tested by generating templ files in the project, and testing that the expected output HTML is present. - -### Runtime - -The runtime is used by generated code, and by template authors, to serve template content over HTTP, and to carry out various operations. - -It is in the root directory of the project at `./runtime.go`. The runtime is unit tested, as well as being tested as part of the `generate` tests. - -### LSP - -The LSP is structured within the command line interface, and proxies commands through to the `gopls` LSP. - -### Docs - -The docs are a Docusaurus project at `./docs`. - -## Coding - -### Build tasks - -templ uses the `xc` task runner - https://github.com/joerdav/xc - -If you run `xc` you can get see a list of the development tasks that can be run, or you can read the `README.md` file and see the `Tasks` section. - -The most useful tasks for local development are: - -* `install-snapshot` - this builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path. -* `test` - this regenerates all templates, and runs the unit tests. -* `fmt` - run the `gofmt` tool to format all Go code. -* `lint` - run the same linting as run in the CI process. -* `docs-run` - run the Docusaurus documentation site. - -### Commit messages - -The project using https://www.conventionalcommits.org/en/v1.0.0/ - -Examples: - -* `feat: support Go comments in templates, fixes #234"` - -### Coding style - -* Reduce nesting - i.e. prefer early returns over an `else` block, as per https://danp.net/posts/reducing-go-nesting/ or https://go.dev/doc/effective_go#if -* Use line breaks to separate "paragraphs" of code - don't use line breaks in between lines, or at the start/end of functions etc. -* Use the `fmt` and `lint` build tasks to format and lint your code before submitting a PR. - diff --git a/vendor/github.com/a-h/templ/LICENSE b/vendor/github.com/a-h/templ/LICENSE deleted file mode 100644 index 15e6fb8..0000000 --- a/vendor/github.com/a-h/templ/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Adrian Hesketh - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/a-h/templ/README.md b/vendor/github.com/a-h/templ/README.md deleted file mode 100644 index e3087f0..0000000 --- a/vendor/github.com/a-h/templ/README.md +++ /dev/null @@ -1,171 +0,0 @@ -![templ](https://github.com/a-h/templ/raw/main/templ.png) - -## An HTML templating language for Go that has great developer tooling. - -![templ](ide-demo.gif) - - -## Documentation - -See user documentation at https://templ.guide - -

-Go Reference -xc compatible -Go Coverage -Go Report Card - -## Tasks - -### build - -Build a local version. - -```sh -go run ./get-version > .version -cd cmd/templ -go build -``` - -### nix-update-gomod2nix - -```sh -gomod2nix -``` - -### install-snapshot - -Build and install current version. - -```sh -# Remove templ from the non-standard ~/bin/templ path -# that this command previously used. -rm -f ~/bin/templ -# Clear LSP logs. -rm -f cmd/templ/lspcmd/*.txt -# Update version. -go run ./get-version > .version -# Install to $GOPATH/bin or $HOME/go/bin -cd cmd/templ && go install -``` - -### build-snapshot - -Use goreleaser to build the command line binary using goreleaser. - -```sh -goreleaser build --snapshot --clean -``` - -### generate - -Run templ generate using local version. - -```sh -go run ./cmd/templ generate -include-version=false -``` - -### test - -Run Go tests. - -```sh -go run ./get-version > .version -go run ./cmd/templ generate -include-version=false -go test ./... -``` - -### test-short - -Run Go tests. - -```sh -go run ./get-version > .version -go run ./cmd/templ generate -include-version=false -go test ./... -short -``` - -### test-cover - -Run Go tests. - -```sh -# Create test profile directories. -mkdir -p coverage/fmt -mkdir -p coverage/generate -mkdir -p coverage/version -mkdir -p coverage/unit -# Build the test binary. -go build -cover -o ./coverage/templ-cover ./cmd/templ -# Run the covered generate command. -GOCOVERDIR=coverage/fmt ./coverage/templ-cover fmt . -GOCOVERDIR=coverage/generate ./coverage/templ-cover generate -include-version=false -GOCOVERDIR=coverage/version ./coverage/templ-cover version -# Run the unit tests. -go test -cover ./... -coverpkg ./... -args -test.gocoverdir="$PWD/coverage/unit" -# Display the combined percentage. -go tool covdata percent -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit -# Generate a text coverage profile for tooling to use. -go tool covdata textfmt -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit -o coverage.out -# Print total -go tool cover -func coverage.out | grep total -``` - -### test-cover-watch - -```sh -gotestsum --watch -- -coverprofile=coverage.out -``` - -### benchmark - -Run benchmarks. - -```sh -go run ./cmd/templ generate -include-version=false && go test ./... -bench=. -benchmem -``` - -### fmt - -Format all Go and templ code. - -```sh -gofmt -s -w . -go run ./cmd/templ fmt . -``` - -### lint - -```sh -golangci-lint run --verbose -``` - -### push-release-tag - -Push a semantic version number to Github to trigger the release process. - -```sh -./push-tag.sh -``` - -### docs-run - -Run the development server. - -Directory: docs - -```sh -npm run start -``` - -### docs-build - -Build production docs site. - -Directory: docs - -```sh -npm run build -``` - diff --git a/vendor/github.com/a-h/templ/SECURITY.md b/vendor/github.com/a-h/templ/SECURITY.md deleted file mode 100644 index 8241f55..0000000 --- a/vendor/github.com/a-h/templ/SECURITY.md +++ /dev/null @@ -1,9 +0,0 @@ -# Security Policy - -## Supported Versions - -The latest version of templ is supported. - -## Reporting a Vulnerability - -Use the "Security" tab in Github and fill out the "Report a vulnerability" form. diff --git a/vendor/github.com/a-h/templ/cosign.pub b/vendor/github.com/a-h/templ/cosign.pub deleted file mode 100644 index 9d7967b..0000000 --- a/vendor/github.com/a-h/templ/cosign.pub +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqHp75uAj8XqKrLO2YvY0M2EddckH -evQnNAj+0GmBptqdf3NJcUCjL6w4z2Ikh/Zb8lh6b13akAwO/dJQaMLoMA== ------END PUBLIC KEY----- diff --git a/vendor/github.com/a-h/templ/flake.lock b/vendor/github.com/a-h/templ/flake.lock deleted file mode 100644 index af4e370..0000000 --- a/vendor/github.com/a-h/templ/flake.lock +++ /dev/null @@ -1,140 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "gitignore": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", - "type": "github" - } - }, - "gomod2nix": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1717050755, - "narHash": "sha256-C9IEHABulv2zEDFA+Bf0E1nmfN4y6MIUe5eM2RCrDC0=", - "owner": "nix-community", - "repo": "gomod2nix", - "rev": "31b6d2e40b36456e792cd6cf50d5a8ddd2fa59a1", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "gomod2nix", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1720096762, - "narHash": "sha256-KvpJIWxTNuaSpN2L/9TmTlEhlwxEnzJ1vCpEcfK/4mQ=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "638369f687471823770f6d3093f1721dc7b8c897", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "release-24.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "gitignore": "gitignore", - "gomod2nix": "gomod2nix", - "nixpkgs": "nixpkgs", - "xc": "xc" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "xc": { - "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1717601811, - "narHash": "sha256-+XQvDRXpzjBdZI3JGKP6SAOYXM+JSEbWL5kqtCwRJXE=", - "owner": "joerdav", - "repo": "xc", - "rev": "f8e8e658978d6c9fe49c27b684ca7375a74deef1", - "type": "github" - }, - "original": { - "owner": "joerdav", - "repo": "xc", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/vendor/github.com/a-h/templ/flake.nix b/vendor/github.com/a-h/templ/flake.nix deleted file mode 100644 index fd8a238..0000000 --- a/vendor/github.com/a-h/templ/flake.nix +++ /dev/null @@ -1,93 +0,0 @@ -{ - description = "templ"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/release-24.05"; - gomod2nix = { - url = "github:nix-community/gomod2nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - gitignore = { - url = "github:hercules-ci/gitignore.nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - xc = { - url = "github:joerdav/xc"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - - outputs = { self, nixpkgs, gomod2nix, gitignore, xc }: - let - allSystems = [ - "x86_64-linux" # 64-bit Intel/AMD Linux - "aarch64-linux" # 64-bit ARM Linux - "x86_64-darwin" # 64-bit Intel macOS - "aarch64-darwin" # 64-bit ARM macOS - ]; - forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f { - inherit system; - pkgs = import nixpkgs { inherit system; }; - }); - in - { - packages = forAllSystems ({ system, pkgs, ... }: - let - buildGoApplication = gomod2nix.legacyPackages.${system}.buildGoApplication; - in - rec { - default = templ; - - templ = buildGoApplication { - name = "templ"; - src = gitignore.lib.gitignoreSource ./.; - # Update to latest Go version when https://nixpk.gs/pr-tracker.html?pr=324123 is backported to release-24.05. - go = pkgs.go; - # Must be added due to bug https://github.com/nix-community/gomod2nix/issues/120 - pwd = ./.; - subPackages = [ "cmd/templ" ]; - CGO_ENABLED = 0; - flags = [ - "-trimpath" - ]; - ldflags = [ - "-s" - "-w" - "-extldflags -static" - ]; - }; - }); - - # `nix develop` provides a shell containing development tools. - devShell = forAllSystems ({ system, pkgs }: - pkgs.mkShell { - buildInputs = with pkgs; [ - (golangci-lint.override { buildGoModule = buildGo121Module; }) - cosign # Used to sign container images. - esbuild # Used to package JS examples. - go_1_21 - gomod2nix.legacyPackages.${system}.gomod2nix - gopls - goreleaser - gotestsum - ko # Used to build Docker images. - nodejs # Used to build templ-docs. - xc.packages.${system}.xc - ]; - }); - - # This flake outputs an overlay that can be used to add templ and - # templ-docs to nixpkgs as per https://templ.guide/quick-start/installation/#nix - # - # Example usage: - # - # nixpkgs.overlays = [ - # inputs.templ.overlays.default - # ]; - overlays.default = final: prev: { - templ = self.packages.${final.stdenv.system}.templ; - templ-docs = self.packages.${final.stdenv.system}.templ-docs; - }; - }; -} - diff --git a/vendor/github.com/a-h/templ/flush.go b/vendor/github.com/a-h/templ/flush.go deleted file mode 100644 index 56d7d3a..0000000 --- a/vendor/github.com/a-h/templ/flush.go +++ /dev/null @@ -1,36 +0,0 @@ -package templ - -import ( - "context" - "io" -) - -// Flush flushes the output buffer after all its child components have been rendered. -func Flush() FlushComponent { - return FlushComponent{} -} - -type FlushComponent struct { -} - -type flusherError interface { - Flush() error -} - -type flusher interface { - Flush() -} - -func (f FlushComponent) Render(ctx context.Context, w io.Writer) (err error) { - if err = GetChildren(ctx).Render(ctx, w); err != nil { - return err - } - switch w := w.(type) { - case flusher: - w.Flush() - return nil - case flusherError: - return w.Flush() - } - return nil -} diff --git a/vendor/github.com/a-h/templ/gomod2nix.toml b/vendor/github.com/a-h/templ/gomod2nix.toml deleted file mode 100644 index d572a5f..0000000 --- a/vendor/github.com/a-h/templ/gomod2nix.toml +++ /dev/null @@ -1,90 +0,0 @@ -schema = 3 - -[mod] - [mod."github.com/PuerkitoBio/goquery"] - version = "v1.8.1" - hash = "sha256-z2RaB8PVPEzSJdMUfkfNjT616yXWTjW2gkhNOh989ZU=" - [mod."github.com/a-h/htmlformat"] - version = "v0.0.0-20231108124658-5bd994fe268e" - hash = "sha256-YSl9GsXhc0L2oKGZLwwjUtpe5W6ra6kk74zvQdsDCMU=" - [mod."github.com/a-h/parse"] - version = "v0.0.0-20240121214402-3caf7543159a" - hash = "sha256-ee/g6xwwhtF7vVt3griUSh96Kz4z0hM5/tpXxHW6PZk=" - [mod."github.com/a-h/pathvars"] - version = "v0.0.14" - hash = "sha256-2NytUpcO0zbzE5XunCLcK3jDqxYzmyb3WqtYDEudAYg=" - [mod."github.com/a-h/protocol"] - version = "v0.0.0-20240704131721-1e461c188041" - hash = "sha256-KSw8m+kVIubEi+nuS3dMdBw2ZZTlmcKD/hGbVRFaE5Q=" - [mod."github.com/andybalholm/brotli"] - version = "v1.1.0" - hash = "sha256-njLViV4v++ZdgOWGWzlvkefuFvA/nkugl3Ta/h1nu/0=" - [mod."github.com/andybalholm/cascadia"] - version = "v1.3.1" - hash = "sha256-M0u22DXSeXUaYtl1KoW1qWL46niFpycFkraCEQ/luYA=" - [mod."github.com/cenkalti/backoff/v4"] - version = "v4.3.0" - hash = "sha256-wfVjNZsGG1WoNC5aL+kdcy6QXPgZo4THAevZ1787md8=" - [mod."github.com/cli/browser"] - version = "v1.3.0" - hash = "sha256-06hcvQeOEm31clxkTuZ8ts8ZtdNKY575EsM1osRVpLg=" - [mod."github.com/fatih/color"] - version = "v1.16.0" - hash = "sha256-Aq/SM28aPJVzvapllQ64R/DM4aZ5CHPewcm/AUJPyJQ=" - [mod."github.com/fsnotify/fsnotify"] - version = "v1.7.0" - hash = "sha256-MdT2rQyQHspPJcx6n9ozkLbsktIOJutOqDuKpNAtoZY=" - [mod."github.com/google/go-cmp"] - version = "v0.6.0" - hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg=" - [mod."github.com/mattn/go-colorable"] - version = "v0.1.13" - hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8=" - [mod."github.com/mattn/go-isatty"] - version = "v0.0.20" - hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ=" - [mod."github.com/natefinch/atomic"] - version = "v1.0.1" - hash = "sha256-fbOVHCwRNI8PFjC4o0YXpKZO0JU2aWTfH5c7WXXKMHg=" - [mod."github.com/rs/cors"] - version = "v1.11.0" - hash = "sha256-hF25bVehtWCQsxiOfLuL4Hv8NKVunEqLPk/Vcuheha0=" - [mod."github.com/segmentio/asm"] - version = "v1.2.0" - hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs=" - [mod."github.com/segmentio/encoding"] - version = "v0.4.0" - hash = "sha256-4pWI9eTZRRDP9kO8rG6vbLCtBVVRLtbCJKd0Z2+8JoU=" - [mod."github.com/stretchr/testify"] - version = "v1.8.4" - hash = "sha256-MoOmRzbz9QgiJ+OOBo5h5/LbilhJfRUryvzHJmXAWjo=" - [mod."go.lsp.dev/jsonrpc2"] - version = "v0.10.0" - hash = "sha256-RbRsMYVBLR7ZDHHGMooycrkdbIauMXkQjVOGP7ggSgM=" - [mod."go.lsp.dev/pkg"] - version = "v0.0.0-20210717090340-384b27a52fb2" - hash = "sha256-TxS0Iqe1wbIaFe7MWZJRQdgqhKE8i8CggaGSV9zU1Vg=" - [mod."go.lsp.dev/uri"] - version = "v0.3.0" - hash = "sha256-jGP0N7Gf+bql5oJraUo33sXqWg7AKOTj0D8b4paV4dc=" - [mod."go.uber.org/multierr"] - version = "v1.11.0" - hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0=" - [mod."go.uber.org/zap"] - version = "v1.27.0" - hash = "sha256-8655KDrulc4Das3VRduO9MjCn8ZYD5WkULjCvruaYsU=" - [mod."golang.org/x/mod"] - version = "v0.17.0" - hash = "sha256-CLaPeF6uTFuRDv4oHwOQE6MCMvrzkUjWN3NuyywZjKU=" - [mod."golang.org/x/net"] - version = "v0.24.0" - hash = "sha256-w1c21ljta5wNIyel9CSIn/crPzwOCRofNKhqmfs4aEQ=" - [mod."golang.org/x/sync"] - version = "v0.3.0" - hash = "sha256-bCJKLvwExhYacH2ZrWlZ38lr1d6oNenNt2m1QqDCs0o=" - [mod."golang.org/x/sys"] - version = "v0.21.0" - hash = "sha256-gapzPWuEqY36V6W2YhIDYR49sEvjJRd7bSuf9K1f4JY=" - [mod."golang.org/x/tools"] - version = "v0.13.0" - hash = "sha256-OCgLOwia8fNHxfdogXVApf0/qK6jE2ukegOx7lkOzfo=" diff --git a/vendor/github.com/a-h/templ/handler.go b/vendor/github.com/a-h/templ/handler.go deleted file mode 100644 index a28d561..0000000 --- a/vendor/github.com/a-h/templ/handler.go +++ /dev/null @@ -1,102 +0,0 @@ -package templ - -import "net/http" - -// ComponentHandler is a http.Handler that renders components. -type ComponentHandler struct { - Component Component - Status int - ContentType string - ErrorHandler func(r *http.Request, err error) http.Handler - StreamResponse bool -} - -const componentHandlerErrorMessage = "templ: failed to render template" - -func (ch *ComponentHandler) ServeHTTPBuffered(w http.ResponseWriter, r *http.Request) { - // Since the component may error, write to a buffer first. - // This prevents partial responses from being written to the client. - buf := GetBuffer() - defer ReleaseBuffer(buf) - err := ch.Component.Render(r.Context(), buf) - if err != nil { - if ch.ErrorHandler != nil { - w.Header().Set("Content-Type", ch.ContentType) - ch.ErrorHandler(r, err).ServeHTTP(w, r) - return - } - http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", ch.ContentType) - if ch.Status != 0 { - w.WriteHeader(ch.Status) - } - // Ignore write error like http.Error() does, because there is - // no way to recover at this point. - _, _ = w.Write(buf.Bytes()) -} - -func (ch *ComponentHandler) ServeHTTPStreamed(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", ch.ContentType) - if ch.Status != 0 { - w.WriteHeader(ch.Status) - } - if err := ch.Component.Render(r.Context(), w); err != nil { - if ch.ErrorHandler != nil { - w.Header().Set("Content-Type", ch.ContentType) - ch.ErrorHandler(r, err).ServeHTTP(w, r) - return - } - http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError) - } -} - -// ServeHTTP implements the http.Handler interface. -func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if ch.StreamResponse { - ch.ServeHTTPStreamed(w, r) - return - } - ch.ServeHTTPBuffered(w, r) -} - -// Handler creates a http.Handler that renders the template. -func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler { - ch := &ComponentHandler{ - Component: c, - ContentType: "text/html; charset=utf-8", - } - for _, o := range options { - o(ch) - } - return ch -} - -// WithStatus sets the HTTP status code returned by the ComponentHandler. -func WithStatus(status int) func(*ComponentHandler) { - return func(ch *ComponentHandler) { - ch.Status = status - } -} - -// WithContentType sets the Content-Type header returned by the ComponentHandler. -func WithContentType(contentType string) func(*ComponentHandler) { - return func(ch *ComponentHandler) { - ch.ContentType = contentType - } -} - -// WithErrorHandler sets the error handler used if rendering fails. -func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) { - return func(ch *ComponentHandler) { - ch.ErrorHandler = eh - } -} - -// WithStreaming sets the ComponentHandler to stream the response instead of buffering it. -func WithStreaming() func(*ComponentHandler) { - return func(ch *ComponentHandler) { - ch.StreamResponse = true - } -} diff --git a/vendor/github.com/a-h/templ/ide-demo.gif b/vendor/github.com/a-h/templ/ide-demo.gif deleted file mode 100644 index e35fd68..0000000 Binary files a/vendor/github.com/a-h/templ/ide-demo.gif and /dev/null differ diff --git a/vendor/github.com/a-h/templ/jsonscript.go b/vendor/github.com/a-h/templ/jsonscript.go deleted file mode 100644 index 6e88174..0000000 --- a/vendor/github.com/a-h/templ/jsonscript.go +++ /dev/null @@ -1,85 +0,0 @@ -package templ - -import ( - "context" - "encoding/json" - "fmt" - "io" -) - -var _ Component = JSONScriptElement{} - -// JSONScript renders a JSON object inside a script element. -// e.g. -func JSONScript(id string, data any) JSONScriptElement { - return JSONScriptElement{ - ID: id, - Type: "application/json", - Data: data, - Nonce: GetNonce, - } -} - -// WithType sets the value of the type attribute of the script element. -func (j JSONScriptElement) WithType(t string) JSONScriptElement { - j.Type = t - return j -} - -// WithNonceFromString sets the value of the nonce attribute of the script element to the given string. -func (j JSONScriptElement) WithNonceFromString(nonce string) JSONScriptElement { - j.Nonce = func(context.Context) string { - return nonce - } - return j -} - -// WithNonceFrom sets the value of the nonce attribute of the script element to the value returned by the given function. -func (j JSONScriptElement) WithNonceFrom(f func(context.Context) string) JSONScriptElement { - j.Nonce = f - return j -} - -type JSONScriptElement struct { - // ID of the element in the DOM. - ID string - // Type of the script element, defaults to "application/json". - Type string - // Data that will be encoded as JSON. - Data any - // Nonce is a function that returns a CSP nonce. - // Defaults to CSPNonceFromContext. - // See https://content-security-policy.com/nonce for more information. - Nonce func(ctx context.Context) string -} - -func (j JSONScriptElement) Render(ctx context.Context, w io.Writer) (err error) { - if _, err = io.WriteString(w, ""); err != nil { - return err - } - if err = json.NewEncoder(w).Encode(j.Data); err != nil { - return err - } - if _, err = io.WriteString(w, ""); err != nil { - return err - } - return nil -} diff --git a/vendor/github.com/a-h/templ/jsonstring.go b/vendor/github.com/a-h/templ/jsonstring.go deleted file mode 100644 index 425e4e8..0000000 --- a/vendor/github.com/a-h/templ/jsonstring.go +++ /dev/null @@ -1,14 +0,0 @@ -package templ - -import ( - "encoding/json" -) - -// JSONString returns a JSON encoded string of v. -func JSONString(v any) (string, error) { - b, err := json.Marshal(v) - if err != nil { - return "", err - } - return string(b), nil -} diff --git a/vendor/github.com/a-h/templ/once.go b/vendor/github.com/a-h/templ/once.go deleted file mode 100644 index 7860ab8..0000000 --- a/vendor/github.com/a-h/templ/once.go +++ /dev/null @@ -1,64 +0,0 @@ -package templ - -import ( - "context" - "io" - "sync/atomic" -) - -// onceHandleIndex is used to identify unique once handles in a program run. -var onceHandleIndex int64 - -type OnceOpt func(*OnceHandle) - -// WithOnceComponent sets the component to be rendered once per context. -// This can be used instead of setting the children of the `Once` method, -// for example, if creating a code component outside of a templ HTML template. -func WithComponent(c Component) OnceOpt { - return func(o *OnceHandle) { - o.c = c - } -} - -// NewOnceHandle creates a OnceHandle used to ensure that the children of its -// `Once` method are only rendered once per context. -func NewOnceHandle(opts ...OnceOpt) *OnceHandle { - oh := &OnceHandle{ - id: atomic.AddInt64(&onceHandleIndex, 1), - } - for _, opt := range opts { - opt(oh) - } - return oh -} - -// OnceHandle is used to ensure that the children of its `Once` method are are only -// rendered once per context. -type OnceHandle struct { - // id is used to identify which instance of the OnceHandle is being used. - // The OnceHandle can't be an empty struct, because: - // - // | Two distinct zero-size variables may - // | have the same address in memory - // - // https://go.dev/ref/spec#Size_and_alignment_guarantees - id int64 - // c is the component to be rendered once per context. - // if c is nil, the children of the `Once` method are rendered. - c Component -} - -// Once returns a component that renders its children once per context. -func (o *OnceHandle) Once() Component { - return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { - _, v := getContext(ctx) - if v.getHasBeenRendered(o) { - return nil - } - v.setHasBeenRendered(o) - if o.c != nil { - return o.c.Render(ctx, w) - } - return GetChildren(ctx).Render(ctx, w) - }) -} diff --git a/vendor/github.com/a-h/templ/push-tag.sh b/vendor/github.com/a-h/templ/push-tag.sh deleted file mode 100644 index 9eedeed..0000000 --- a/vendor/github.com/a-h/templ/push-tag.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -if [ `git rev-parse --abbrev-ref HEAD` != "main" ]; then - echo "Error: Not on main branch. Please switch to main branch."; - exit 1; -fi -git pull -if ! git diff --quiet; then - echo "Error: Working directory is not clean. Please commit the changes first."; - exit 1; -fi -export VERSION=`cat .version` -echo Adding git tag with version v${VERSION}; -git tag v${VERSION}; -git push origin v${VERSION}; diff --git a/vendor/github.com/a-h/templ/runtime.go b/vendor/github.com/a-h/templ/runtime.go deleted file mode 100644 index d4d5aa0..0000000 --- a/vendor/github.com/a-h/templ/runtime.go +++ /dev/null @@ -1,855 +0,0 @@ -package templ - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "html" - "html/template" - "io" - "net/http" - "os" - "reflect" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/a-h/templ/safehtml" -) - -// Types exposed by all components. - -// Component is the interface that all templates implement. -type Component interface { - // Render the template. - Render(ctx context.Context, w io.Writer) error -} - -// ComponentFunc converts a function that matches the Component interface's -// Render method into a Component. -type ComponentFunc func(ctx context.Context, w io.Writer) error - -// Render the template. -func (cf ComponentFunc) Render(ctx context.Context, w io.Writer) error { - return cf(ctx, w) -} - -// WithNonce sets a CSP nonce on the context and returns it. -func WithNonce(ctx context.Context, nonce string) context.Context { - ctx, v := getContext(ctx) - v.nonce = nonce - return ctx -} - -// GetNonce returns the CSP nonce value set with WithNonce, or an -// empty string if none has been set. -func GetNonce(ctx context.Context) (nonce string) { - if ctx == nil { - return "" - } - _, v := getContext(ctx) - return v.nonce -} - -func WithChildren(ctx context.Context, children Component) context.Context { - ctx, v := getContext(ctx) - v.children = &children - return ctx -} - -func ClearChildren(ctx context.Context) context.Context { - _, v := getContext(ctx) - v.children = nil - return ctx -} - -// NopComponent is a component that doesn't render anything. -var NopComponent = ComponentFunc(func(ctx context.Context, w io.Writer) error { return nil }) - -// GetChildren from the context. -func GetChildren(ctx context.Context) Component { - _, v := getContext(ctx) - if v.children == nil { - return NopComponent - } - return *v.children -} - -// EscapeString escapes HTML text within templates. -func EscapeString(s string) string { - return html.EscapeString(s) -} - -// Bool attribute value. -func Bool(value bool) bool { - return value -} - -// Classes for CSS. -// Supported types are string, ConstantCSSClass, ComponentCSSClass, map[string]bool. -func Classes(classes ...any) CSSClasses { - return CSSClasses(classes) -} - -// CSSClasses is a slice of CSS classes. -type CSSClasses []any - -// String returns the names of all CSS classes. -func (classes CSSClasses) String() string { - if len(classes) == 0 { - return "" - } - cp := newCSSProcessor() - for _, v := range classes { - cp.Add(v) - } - return cp.String() -} - -func newCSSProcessor() *cssProcessor { - return &cssProcessor{ - classNameToEnabled: make(map[string]bool), - } -} - -type cssProcessor struct { - classNameToEnabled map[string]bool - orderedNames []string -} - -func (cp *cssProcessor) Add(item any) { - switch c := item.(type) { - case []string: - for _, className := range c { - cp.AddClassName(className, true) - } - case string: - cp.AddClassName(c, true) - case ConstantCSSClass: - cp.AddClassName(c.ClassName(), true) - case ComponentCSSClass: - cp.AddClassName(c.ClassName(), true) - case map[string]bool: - // In Go, map keys are iterated in a randomized order. - // So the keys in the map must be sorted to produce consistent output. - keys := make([]string, len(c)) - var i int - for key := range c { - keys[i] = key - i++ - } - sort.Strings(keys) - for _, className := range keys { - cp.AddClassName(className, c[className]) - } - case []KeyValue[string, bool]: - for _, kv := range c { - cp.AddClassName(kv.Key, kv.Value) - } - case KeyValue[string, bool]: - cp.AddClassName(c.Key, c.Value) - case []KeyValue[CSSClass, bool]: - for _, kv := range c { - cp.AddClassName(kv.Key.ClassName(), kv.Value) - } - case KeyValue[CSSClass, bool]: - cp.AddClassName(c.Key.ClassName(), c.Value) - case CSSClasses: - for _, item := range c { - cp.Add(item) - } - case []CSSClass: - for _, item := range c { - cp.Add(item) - } - case func() CSSClass: - cp.AddClassName(c().ClassName(), true) - default: - cp.AddClassName(unknownTypeClassName, true) - } -} - -func (cp *cssProcessor) AddClassName(className string, enabled bool) { - cp.classNameToEnabled[className] = enabled - cp.orderedNames = append(cp.orderedNames, className) -} - -func (cp *cssProcessor) String() string { - // Order the outputs according to how they were input, and remove disabled names. - rendered := make(map[string]any, len(cp.classNameToEnabled)) - var names []string - for _, name := range cp.orderedNames { - if enabled := cp.classNameToEnabled[name]; !enabled { - continue - } - if _, hasBeenRendered := rendered[name]; hasBeenRendered { - continue - } - names = append(names, name) - rendered[name] = struct{}{} - } - - return strings.Join(names, " ") -} - -// KeyValue is a key and value pair. -type KeyValue[TKey comparable, TValue any] struct { - Key TKey `json:"name"` - Value TValue `json:"value"` -} - -// KV creates a new key/value pair from the input key and value. -func KV[TKey comparable, TValue any](key TKey, value TValue) KeyValue[TKey, TValue] { - return KeyValue[TKey, TValue]{ - Key: key, - Value: value, - } -} - -const unknownTypeClassName = "--templ-css-class-unknown-type" - -// Class returns a CSS class name. -// Deprecated: use a string instead. -func Class(name string) CSSClass { - return SafeClass(name) -} - -// SafeClass bypasses CSS class name validation. -// Deprecated: use a string instead. -func SafeClass(name string) CSSClass { - return ConstantCSSClass(name) -} - -// CSSClass provides a class name. -type CSSClass interface { - ClassName() string -} - -// ConstantCSSClass is a string constant of a CSS class name. -// Deprecated: use a string instead. -type ConstantCSSClass string - -// ClassName of the CSS class. -func (css ConstantCSSClass) ClassName() string { - return string(css) -} - -// ComponentCSSClass is a templ.CSS -type ComponentCSSClass struct { - // ID of the class, will be autogenerated. - ID string - // Definition of the CSS. - Class SafeCSS -} - -// ClassName of the CSS class. -func (css ComponentCSSClass) ClassName() string { - return css.ID -} - -// CSSID calculates an ID. -func CSSID(name string, css string) string { - sum := sha256.Sum256([]byte(css)) - hp := hex.EncodeToString(sum[:])[0:4] - // Benchmarking showed this was fastest, and with fewest allocations (1). - // Using strings.Builder (2 allocs). - // Using fmt.Sprintf (3 allocs). - return name + "_" + hp -} - -// NewCSSMiddleware creates HTTP middleware that renders a global stylesheet of ComponentCSSClass -// CSS if the request path matches, or updates the HTTP context to ensure that any handlers that -// use templ.Components skip rendering `); err != nil { - return err - } - } - return nil -} - -func renderCSSItemsToBuilder(sb *strings.Builder, v *contextValue, classes ...any) { - for _, c := range classes { - switch ccc := c.(type) { - case ComponentCSSClass: - if !v.hasClassBeenRendered(ccc.ID) { - sb.WriteString(string(ccc.Class)) - v.addClass(ccc.ID) - } - case KeyValue[ComponentCSSClass, bool]: - if !ccc.Value { - continue - } - renderCSSItemsToBuilder(sb, v, ccc.Key) - case KeyValue[CSSClass, bool]: - if !ccc.Value { - continue - } - renderCSSItemsToBuilder(sb, v, ccc.Key) - case CSSClasses: - renderCSSItemsToBuilder(sb, v, ccc...) - case []CSSClass: - for _, item := range ccc { - renderCSSItemsToBuilder(sb, v, item) - } - case func() CSSClass: - renderCSSItemsToBuilder(sb, v, ccc()) - case []string: - // Skip. These are class names, not CSS classes. - case string: - // Skip. This is a class name, not a CSS class. - case ConstantCSSClass: - // Skip. This is a class name, not a CSS class. - case CSSClass: - // Skip. This is a class name, not a CSS class. - case map[string]bool: - // Skip. These are class names, not CSS classes. - case KeyValue[string, bool]: - // Skip. These are class names, not CSS classes. - case []KeyValue[string, bool]: - // Skip. These are class names, not CSS classes. - case KeyValue[ConstantCSSClass, bool]: - // Skip. These are class names, not CSS classes. - case []KeyValue[ConstantCSSClass, bool]: - // Skip. These are class names, not CSS classes. - } - } -} - -// SafeCSS is CSS that has been sanitized. -type SafeCSS string - -type SafeCSSProperty string - -var safeCSSPropertyType = reflect.TypeOf(SafeCSSProperty("")) - -// SanitizeCSS sanitizes CSS properties to ensure that they are safe. -func SanitizeCSS[T ~string](property string, value T) SafeCSS { - if reflect.TypeOf(value) == safeCSSPropertyType { - return SafeCSS(safehtml.SanitizeCSSProperty(property) + ":" + string(value) + ";") - } - p, v := safehtml.SanitizeCSS(property, string(value)) - return SafeCSS(p + ":" + v + ";") -} - -// Attributes is an alias to map[string]any made for spread attributes. -type Attributes map[string]any - -// sortedKeys returns the keys of a map in sorted order. -func sortedKeys(m map[string]any) (keys []string) { - keys = make([]string, len(m)) - var i int - for k := range m { - keys[i] = k - i++ - } - sort.Strings(keys) - return keys -} - -func writeStrings(w io.Writer, ss ...string) (err error) { - for _, s := range ss { - if _, err = io.WriteString(w, s); err != nil { - return err - } - } - return nil -} - -func RenderAttributes(ctx context.Context, w io.Writer, attributes Attributes) (err error) { - for _, key := range sortedKeys(attributes) { - value := attributes[key] - switch value := value.(type) { - case string: - if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value), `"`); err != nil { - return err - } - case *string: - if value != nil { - if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(*value), `"`); err != nil { - return err - } - } - case bool: - if value { - if err = writeStrings(w, ` `, EscapeString(key)); err != nil { - return err - } - } - case *bool: - if value != nil && *value { - if err = writeStrings(w, ` `, EscapeString(key)); err != nil { - return err - } - } - case KeyValue[string, bool]: - if value.Value { - if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value.Key), `"`); err != nil { - return err - } - } - case KeyValue[bool, bool]: - if value.Value && value.Key { - if err = writeStrings(w, ` `, EscapeString(key)); err != nil { - return err - } - } - case func() bool: - if value() { - if err = writeStrings(w, ` `, EscapeString(key)); err != nil { - return err - } - } - } - } - return nil -} - -// Script handling. - -func safeEncodeScriptParams(escapeHTML bool, params []any) []string { - encodedParams := make([]string, len(params)) - for i := 0; i < len(encodedParams); i++ { - enc, _ := json.Marshal(params[i]) - if !escapeHTML { - encodedParams[i] = string(enc) - continue - } - encodedParams[i] = EscapeString(string(enc)) - } - return encodedParams -} - -// SafeScript encodes unknown parameters for safety for inside HTML attributes. -func SafeScript(functionName string, params ...any) string { - encodedParams := safeEncodeScriptParams(true, params) - sb := new(strings.Builder) - sb.WriteString(functionName) - sb.WriteRune('(') - sb.WriteString(strings.Join(encodedParams, ",")) - sb.WriteRune(')') - return sb.String() -} - -// SafeScript encodes unknown parameters for safety for inline scripts. -func SafeScriptInline(functionName string, params ...any) string { - encodedParams := safeEncodeScriptParams(false, params) - sb := new(strings.Builder) - sb.WriteString(functionName) - sb.WriteRune('(') - sb.WriteString(strings.Join(encodedParams, ",")) - sb.WriteRune(')') - return sb.String() -} - -type contextKeyType int - -const contextKey = contextKeyType(0) - -type contextValue struct { - ss map[string]struct{} - onceHandles map[*OnceHandle]struct{} - children *Component - nonce string -} - -func (v *contextValue) setHasBeenRendered(h *OnceHandle) { - if v.onceHandles == nil { - v.onceHandles = map[*OnceHandle]struct{}{} - } - v.onceHandles[h] = struct{}{} -} - -func (v *contextValue) getHasBeenRendered(h *OnceHandle) (ok bool) { - if v.onceHandles == nil { - v.onceHandles = map[*OnceHandle]struct{}{} - } - _, ok = v.onceHandles[h] - return -} - -func (v *contextValue) addScript(s string) { - if v.ss == nil { - v.ss = map[string]struct{}{} - } - v.ss["script_"+s] = struct{}{} -} - -func (v *contextValue) hasScriptBeenRendered(s string) (ok bool) { - if v.ss == nil { - v.ss = map[string]struct{}{} - } - _, ok = v.ss["script_"+s] - return -} - -func (v *contextValue) addClass(s string) { - if v.ss == nil { - v.ss = map[string]struct{}{} - } - v.ss["class_"+s] = struct{}{} -} - -func (v *contextValue) hasClassBeenRendered(s string) (ok bool) { - if v.ss == nil { - v.ss = map[string]struct{}{} - } - _, ok = v.ss["class_"+s] - return -} - -// InitializeContext initializes context used to store internal state used during rendering. -func InitializeContext(ctx context.Context) context.Context { - if _, ok := ctx.Value(contextKey).(*contextValue); ok { - return ctx - } - v := &contextValue{} - ctx = context.WithValue(ctx, contextKey, v) - return ctx -} - -func getContext(ctx context.Context) (context.Context, *contextValue) { - v, ok := ctx.Value(contextKey).(*contextValue) - if !ok { - ctx = InitializeContext(ctx) - v = ctx.Value(contextKey).(*contextValue) - } - return ctx, v -} - -// ComponentScript is a templ Script template. -type ComponentScript struct { - // Name of the script, e.g. print. - Name string - // Function to render. - Function string - // Call of the function in JavaScript syntax, including parameters, and - // ensures parameters are HTML escaped; useful for injecting into HTML - // attributes like onclick, onhover, etc. - // - // Given: - // functionName("some string",12345) - // It would render: - // __templ_functionName_sha("some string",12345)) - // - // This is can be injected into HTML attributes: - // - Call string - // Call of the function in JavaScript syntax, including parameters. It - // does not HTML escape parameters; useful for directly calling in script - // elements. - // - // Given: - // functionName("some string",12345) - // It would render: - // __templ_functionName_sha("some string",12345)) - // - // This is can be used to call the function inside a script tag: - // - CallInline string -} - -var _ Component = ComponentScript{} - -func writeScriptHeader(ctx context.Context, w io.Writer) (err error) { - var nonceAttr string - if nonce := GetNonce(ctx); nonce != "" { - nonceAttr = " nonce=\"" + EscapeString(nonce) + "\"" - } - _, err = fmt.Fprintf(w, ``); err != nil { - return err - } - } - return nil -} - -// RenderScriptItems renders a `); err != nil { - return err - } - } - return nil -} - -var bufferPool = sync.Pool{ - New: func() any { - return new(bytes.Buffer) - }, -} - -func GetBuffer() *bytes.Buffer { - return bufferPool.Get().(*bytes.Buffer) -} - -func ReleaseBuffer(b *bytes.Buffer) { - b.Reset() - bufferPool.Put(b) -} - -// JoinStringErrs joins an optional list of errors. -func JoinStringErrs(s string, errs ...error) (string, error) { - return s, errors.Join(errs...) -} - -// Error returned during template rendering. -type Error struct { - Err error - // FileName of the template file. - FileName string - // Line index of the error. - Line int - // Col index of the error. - Col int -} - -func (e Error) Error() string { - if e.FileName == "" { - e.FileName = "templ" - } - return fmt.Sprintf("%s: error at line %d, col %d: %v", e.FileName, e.Line, e.Col, e.Err) -} - -func (e Error) Unwrap() error { - return e.Err -} - -// Raw renders the input HTML to the output without applying HTML escaping. -// -// Use of this component presents a security risk - the HTML should come from -// a trusted source, because it will be included as-is in the output. -func Raw[T ~string](html T, errs ...error) Component { - return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { - if err = errors.Join(errs...); err != nil { - return err - } - _, err = io.WriteString(w, string(html)) - return err - }) -} - -// FromGoHTML creates a templ Component from a Go html/template template. -func FromGoHTML(t *template.Template, data any) Component { - return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) { - return t.Execute(w, data) - }) -} - -// ToGoHTML renders the component to a Go html/template template.HTML string. -func ToGoHTML(ctx context.Context, c Component) (s template.HTML, err error) { - b := GetBuffer() - defer ReleaseBuffer(b) - if err = c.Render(ctx, b); err != nil { - return - } - s = template.HTML(b.String()) - return -} - -// WriteWatchModeString is used when rendering templates in development mode. -// the generator would have written non-go code to the _templ.txt file, which -// is then read by this function and written to the output. -func WriteWatchModeString(w io.Writer, lineNum int) error { - _, path, _, _ := runtime.Caller(1) - if !strings.HasSuffix(path, "_templ.go") { - return errors.New("templ: WriteWatchModeString can only be called from _templ.go") - } - txtFilePath := strings.Replace(path, "_templ.go", "_templ.txt", 1) - - literals, err := getWatchedStrings(txtFilePath) - if err != nil { - return fmt.Errorf("templ: failed to cache strings: %w", err) - } - - if lineNum > len(literals) { - return errors.New("templ: failed to find line " + strconv.Itoa(lineNum) + " in " + txtFilePath) - } - - unquoted, err := strconv.Unquote(`"` + literals[lineNum-1] + `"`) - if err != nil { - return err - } - _, err = io.WriteString(w, unquoted) - return err -} - -var ( - watchModeCache = map[string]watchState{} - watchStateMutex sync.Mutex -) - -type watchState struct { - modTime time.Time - strings []string -} - -func getWatchedStrings(txtFilePath string) ([]string, error) { - watchStateMutex.Lock() - defer watchStateMutex.Unlock() - - state, cached := watchModeCache[txtFilePath] - if !cached { - return cacheStrings(txtFilePath) - } - - if time.Since(state.modTime) < time.Millisecond*100 { - return state.strings, nil - } - - info, err := os.Stat(txtFilePath) - if err != nil { - return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err) - } - - if !info.ModTime().After(state.modTime) { - return state.strings, nil - } - - return cacheStrings(txtFilePath) -} - -func cacheStrings(txtFilePath string) ([]string, error) { - txtFile, err := os.Open(txtFilePath) - if err != nil { - return nil, fmt.Errorf("templ: failed to open %s: %w", txtFilePath, err) - } - defer txtFile.Close() - - info, err := txtFile.Stat() - if err != nil { - return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err) - } - - all, err := io.ReadAll(txtFile) - if err != nil { - return nil, fmt.Errorf("templ: failed to read %s: %w", txtFilePath, err) - } - - literals := strings.Split(string(all), "\n") - watchModeCache[txtFilePath] = watchState{ - modTime: info.ModTime(), - strings: literals, - } - - return literals, nil -} diff --git a/vendor/github.com/a-h/templ/runtime/buffer.go b/vendor/github.com/a-h/templ/runtime/buffer.go deleted file mode 100644 index 63e4acd..0000000 --- a/vendor/github.com/a-h/templ/runtime/buffer.go +++ /dev/null @@ -1,62 +0,0 @@ -package runtime - -import ( - "bufio" - "io" - "net/http" -) - -// DefaultBufferSize is the default size of buffers. It is set to 4KB by default, which is the -// same as the default buffer size of bufio.Writer. -var DefaultBufferSize = 4 * 1024 // 4KB - -// Buffer is a wrapper around bufio.Writer that enables flushing and closing of -// the underlying writer. -type Buffer struct { - Underlying io.Writer - b *bufio.Writer -} - -// Write the contents of p into the buffer. -func (b *Buffer) Write(p []byte) (n int, err error) { - return b.b.Write(p) -} - -// Flush writes any buffered data to the underlying io.Writer and -// calls the Flush method of the underlying http.Flusher if it implements it. -func (b *Buffer) Flush() error { - if err := b.b.Flush(); err != nil { - return err - } - if f, ok := b.Underlying.(http.Flusher); ok { - f.Flush() - } - return nil -} - -// Close closes the buffer and the underlying io.Writer if it implements io.Closer. -func (b *Buffer) Close() error { - if c, ok := b.Underlying.(io.Closer); ok { - return c.Close() - } - return nil -} - -// Reset sets the underlying io.Writer to w and resets the buffer. -func (b *Buffer) Reset(w io.Writer) { - if b.b == nil { - b.b = bufio.NewWriterSize(b, DefaultBufferSize) - } - b.Underlying = w - b.b.Reset(w) -} - -// Size returns the size of the underlying buffer in bytes. -func (b *Buffer) Size() int { - return b.b.Size() -} - -// WriteString writes the contents of s into the buffer. -func (b *Buffer) WriteString(s string) (n int, err error) { - return b.b.WriteString(s) -} diff --git a/vendor/github.com/a-h/templ/runtime/bufferpool.go b/vendor/github.com/a-h/templ/runtime/bufferpool.go deleted file mode 100644 index ca2a131..0000000 --- a/vendor/github.com/a-h/templ/runtime/bufferpool.go +++ /dev/null @@ -1,38 +0,0 @@ -package runtime - -import ( - "io" - "sync" -) - -var bufferPool = sync.Pool{ - New: func() any { - return new(Buffer) - }, -} - -// GetBuffer creates and returns a new buffer if the writer is not already a buffer, -// or returns the existing buffer if it is. -func GetBuffer(w io.Writer) (b *Buffer, existing bool) { - if w == nil { - return nil, false - } - b, ok := w.(*Buffer) - if ok { - return b, true - } - b = bufferPool.Get().(*Buffer) - b.Reset(w) - return b, false -} - -// ReleaseBuffer flushes the buffer and returns it to the pool. -func ReleaseBuffer(w io.Writer) (err error) { - b, ok := w.(*Buffer) - if !ok { - return nil - } - err = b.Flush() - bufferPool.Put(b) - return err -} diff --git a/vendor/github.com/a-h/templ/runtime/builder.go b/vendor/github.com/a-h/templ/runtime/builder.go deleted file mode 100644 index 0f4c9d4..0000000 --- a/vendor/github.com/a-h/templ/runtime/builder.go +++ /dev/null @@ -1,8 +0,0 @@ -package runtime - -import "strings" - -// GetBuilder returns a strings.Builder. -func GetBuilder() (sb strings.Builder) { - return sb -} diff --git a/vendor/github.com/a-h/templ/runtime/runtime.go b/vendor/github.com/a-h/templ/runtime/runtime.go deleted file mode 100644 index aaa4a2c..0000000 --- a/vendor/github.com/a-h/templ/runtime/runtime.go +++ /dev/null @@ -1,21 +0,0 @@ -package runtime - -import ( - "context" - "io" - - "github.com/a-h/templ" -) - -// GeneratedComponentInput is used to avoid generated code needing to import the `context` and `io` packages. -type GeneratedComponentInput struct { - Context context.Context - Writer io.Writer -} - -// GeneratedTemplate is used to avoid generated code needing to import the `context` and `io` packages. -func GeneratedTemplate(f func(GeneratedComponentInput) error) templ.Component { - return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error { - return f(GeneratedComponentInput{ctx, w}) - }) -} diff --git a/vendor/github.com/a-h/templ/safehtml/style.go b/vendor/github.com/a-h/templ/safehtml/style.go deleted file mode 100644 index 486df7c..0000000 --- a/vendor/github.com/a-h/templ/safehtml/style.go +++ /dev/null @@ -1,168 +0,0 @@ -// Adapted from https://raw.githubusercontent.com/google/safehtml/3c4cd5b5d8c9a6c5882fba099979e9f50b65c876/style.go - -// Copyright (c) 2017 The Go Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -package safehtml - -import ( - "net/url" - "regexp" - "strings" -) - -// SanitizeCSS attempts to sanitize CSS properties. -func SanitizeCSS(property, value string) (string, string) { - property = SanitizeCSSProperty(property) - if property == InnocuousPropertyName { - return InnocuousPropertyName, InnocuousPropertyValue - } - return property, SanitizeCSSValue(property, value) -} - -func SanitizeCSSValue(property, value string) string { - if sanitizer, ok := cssPropertyNameToValueSanitizer[property]; ok { - return sanitizer(value) - } - return sanitizeRegular(value) -} - -func SanitizeCSSProperty(property string) string { - if !identifierPattern.MatchString(property) { - return InnocuousPropertyName - } - return strings.ToLower(property) -} - -// identifierPattern matches a subset of valid values defined in -// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name -// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value. -var identifierPattern = regexp.MustCompile(`^[-a-zA-Z]+$`) - -var cssPropertyNameToValueSanitizer = map[string]func(string) string{ - "background-image": sanitizeBackgroundImage, - "font-family": sanitizeFontFamily, - "display": sanitizeEnum, - "background-color": sanitizeRegular, - "background-position": sanitizeRegular, - "background-repeat": sanitizeRegular, - "background-size": sanitizeRegular, - "color": sanitizeRegular, - "height": sanitizeRegular, - "width": sanitizeRegular, - "left": sanitizeRegular, - "right": sanitizeRegular, - "top": sanitizeRegular, - "bottom": sanitizeRegular, - "font-weight": sanitizeRegular, - "padding": sanitizeRegular, - "z-index": sanitizeRegular, -} - -var validURLPrefixes = []string{ - `url("`, - `url('`, - `url(`, -} - -var validURLSuffixes = []string{ - `")`, - `')`, - `)`, -} - -func sanitizeBackgroundImage(v string) string { - // Check for <> as per https://github.com/google/safehtml/blob/be23134998433fcf0135dda53593fc8f8bf4df7c/style.go#L87C2-L89C3 - if strings.ContainsAny(v, "<>") { - return InnocuousPropertyValue - } - for _, u := range strings.Split(v, ",") { - u = strings.TrimSpace(u) - var found bool - for i, prefix := range validURLPrefixes { - if strings.HasPrefix(u, prefix) && strings.HasSuffix(u, validURLSuffixes[i]) { - found = true - u = strings.TrimPrefix(u, validURLPrefixes[i]) - u = strings.TrimSuffix(u, validURLSuffixes[i]) - break - } - } - if !found || !urlIsSafe(u) { - return InnocuousPropertyValue - } - } - return v -} - -func urlIsSafe(s string) bool { - u, err := url.Parse(s) - if err != nil { - return false - } - if u.IsAbs() { - if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") || strings.EqualFold(u.Scheme, "mailto") { - return true - } - return false - } - return true -} - -var genericFontFamilyName = regexp.MustCompile(`^[a-zA-Z][- a-zA-Z]+$`) - -func sanitizeFontFamily(s string) string { - for _, f := range strings.Split(s, ",") { - f = strings.TrimSpace(f) - if strings.HasPrefix(f, `"`) { - if !strings.HasSuffix(f, `"`) { - return InnocuousPropertyValue - } - continue - } - if !genericFontFamilyName.MatchString(f) { - return InnocuousPropertyValue - } - } - return s -} - -func sanitizeEnum(s string) string { - if !safeEnumPropertyValuePattern.MatchString(s) { - return InnocuousPropertyValue - } - return s -} - -func sanitizeRegular(s string) string { - if !safeRegularPropertyValuePattern.MatchString(s) { - return InnocuousPropertyValue - } - return s -} - -// InnocuousPropertyName is an innocuous property generated by a sanitizer when its input is unsafe. -const InnocuousPropertyName = "zTemplUnsafeCSSPropertyName" - -// InnocuousPropertyValue is an innocuous property generated by a sanitizer when its input is unsafe. -const InnocuousPropertyValue = "zTemplUnsafeCSSPropertyValue" - -// safeRegularPropertyValuePattern matches strings that are safe to use as property values. -// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune -// (i.e. alphanumerics or runes in the set [+-.!#%_ \t]). This regex ensures that the following -// are disallowed: -// - "/*" and "*/", which are CSS comment markers. -// - "//", even though this is not a comment marker in the CSS specification. Disallowing -// this string minimizes the chance that browser peculiarities or parsing bugs will allow -// sanitization to be bypassed. -// - '(' and ')', which can be used to call functions. -// - ',', since it can be used to inject extra values into a property. -// - Runes which could be matched on CSS error recovery of a previously malformed token, such as '@' -// and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling. -var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`) - -// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values. -// Specifically, it matches strings that contain only alphabetic and '-' runes. -var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`) diff --git a/vendor/github.com/a-h/templ/templ.png b/vendor/github.com/a-h/templ/templ.png deleted file mode 100644 index 1c4bc2f..0000000 Binary files a/vendor/github.com/a-h/templ/templ.png and /dev/null differ diff --git a/vendor/github.com/a-h/templ/url.go b/vendor/github.com/a-h/templ/url.go deleted file mode 100644 index bf912e1..0000000 --- a/vendor/github.com/a-h/templ/url.go +++ /dev/null @@ -1,20 +0,0 @@ -package templ - -import "strings" - -// FailedSanitizationURL is returned if a URL fails sanitization checks. -const FailedSanitizationURL = SafeURL("about:invalid#TemplFailedSanitizationURL") - -// URL sanitizes the input string s and returns a SafeURL. -func URL(s string) SafeURL { - if i := strings.IndexRune(s, ':'); i >= 0 && !strings.ContainsRune(s[:i], '/') { - protocol := s[:i] - if !strings.EqualFold(protocol, "http") && !strings.EqualFold(protocol, "https") && !strings.EqualFold(protocol, "mailto") && !strings.EqualFold(protocol, "tel") && !strings.EqualFold(protocol, "ftp") && !strings.EqualFold(protocol, "ftps") { - return FailedSanitizationURL - } - } - return SafeURL(s) -} - -// SafeURL is a URL that has been sanitized. -type SafeURL string diff --git a/vendor/github.com/a-h/templ/version.go b/vendor/github.com/a-h/templ/version.go deleted file mode 100644 index b7fbb6f..0000000 --- a/vendor/github.com/a-h/templ/version.go +++ /dev/null @@ -1,10 +0,0 @@ -package templ - -import _ "embed" - -//go:embed .version -var version string - -func Version() string { - return "v" + version -} diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/vendor/golang.org/x/mod/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/mod/PATENTS b/vendor/golang.org/x/mod/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/mod/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/mod/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go new file mode 100644 index 0000000..9a2dfd3 --- /dev/null +++ b/vendor/golang.org/x/mod/semver/semver.go @@ -0,0 +1,401 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package semver implements comparison of semantic version strings. +// In this package, semantic version strings must begin with a leading "v", +// as in "v1.0.0". +// +// The general form of a semantic version string accepted by this package is +// +// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]] +// +// where square brackets indicate optional parts of the syntax; +// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros; +// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers +// using only alphanumeric characters and hyphens; and +// all-numeric PRERELEASE identifiers must not have leading zeros. +// +// This package follows Semantic Versioning 2.0.0 (see semver.org) +// with two exceptions. First, it requires the "v" prefix. Second, it recognizes +// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes) +// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0. +package semver + +import "sort" + +// parsed returns the parsed form of a semantic version string. +type parsed struct { + major string + minor string + patch string + short string + prerelease string + build string +} + +// IsValid reports whether v is a valid semantic version string. +func IsValid(v string) bool { + _, ok := parse(v) + return ok +} + +// Canonical returns the canonical formatting of the semantic version v. +// It fills in any missing .MINOR or .PATCH and discards build metadata. +// Two semantic versions compare equal only if their canonical formattings +// are identical strings. +// The canonical invalid semantic version is the empty string. +func Canonical(v string) string { + p, ok := parse(v) + if !ok { + return "" + } + if p.build != "" { + return v[:len(v)-len(p.build)] + } + if p.short != "" { + return v + p.short + } + return v +} + +// Major returns the major version prefix of the semantic version v. +// For example, Major("v2.1.0") == "v2". +// If v is an invalid semantic version string, Major returns the empty string. +func Major(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return v[:1+len(pv.major)] +} + +// MajorMinor returns the major.minor version prefix of the semantic version v. +// For example, MajorMinor("v2.1.0") == "v2.1". +// If v is an invalid semantic version string, MajorMinor returns the empty string. +func MajorMinor(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + i := 1 + len(pv.major) + if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor { + return v[:j] + } + return v[:i] + "." + pv.minor +} + +// Prerelease returns the prerelease suffix of the semantic version v. +// For example, Prerelease("v2.1.0-pre+meta") == "-pre". +// If v is an invalid semantic version string, Prerelease returns the empty string. +func Prerelease(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return pv.prerelease +} + +// Build returns the build suffix of the semantic version v. +// For example, Build("v2.1.0+meta") == "+meta". +// If v is an invalid semantic version string, Build returns the empty string. +func Build(v string) string { + pv, ok := parse(v) + if !ok { + return "" + } + return pv.build +} + +// Compare returns an integer comparing two versions according to +// semantic version precedence. +// The result will be 0 if v == w, -1 if v < w, or +1 if v > w. +// +// An invalid semantic version string is considered less than a valid one. +// All invalid semantic version strings compare equal to each other. +func Compare(v, w string) int { + pv, ok1 := parse(v) + pw, ok2 := parse(w) + if !ok1 && !ok2 { + return 0 + } + if !ok1 { + return -1 + } + if !ok2 { + return +1 + } + if c := compareInt(pv.major, pw.major); c != 0 { + return c + } + if c := compareInt(pv.minor, pw.minor); c != 0 { + return c + } + if c := compareInt(pv.patch, pw.patch); c != 0 { + return c + } + return comparePrerelease(pv.prerelease, pw.prerelease) +} + +// Max canonicalizes its arguments and then returns the version string +// that compares greater. +// +// Deprecated: use [Compare] instead. In most cases, returning a canonicalized +// version is not expected or desired. +func Max(v, w string) string { + v = Canonical(v) + w = Canonical(w) + if Compare(v, w) > 0 { + return v + } + return w +} + +// ByVersion implements [sort.Interface] for sorting semantic version strings. +type ByVersion []string + +func (vs ByVersion) Len() int { return len(vs) } +func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } +func (vs ByVersion) Less(i, j int) bool { + cmp := Compare(vs[i], vs[j]) + if cmp != 0 { + return cmp < 0 + } + return vs[i] < vs[j] +} + +// Sort sorts a list of semantic version strings using [ByVersion]. +func Sort(list []string) { + sort.Sort(ByVersion(list)) +} + +func parse(v string) (p parsed, ok bool) { + if v == "" || v[0] != 'v' { + return + } + p.major, v, ok = parseInt(v[1:]) + if !ok { + return + } + if v == "" { + p.minor = "0" + p.patch = "0" + p.short = ".0.0" + return + } + if v[0] != '.' { + ok = false + return + } + p.minor, v, ok = parseInt(v[1:]) + if !ok { + return + } + if v == "" { + p.patch = "0" + p.short = ".0" + return + } + if v[0] != '.' { + ok = false + return + } + p.patch, v, ok = parseInt(v[1:]) + if !ok { + return + } + if len(v) > 0 && v[0] == '-' { + p.prerelease, v, ok = parsePrerelease(v) + if !ok { + return + } + } + if len(v) > 0 && v[0] == '+' { + p.build, v, ok = parseBuild(v) + if !ok { + return + } + } + if v != "" { + ok = false + return + } + ok = true + return +} + +func parseInt(v string) (t, rest string, ok bool) { + if v == "" { + return + } + if v[0] < '0' || '9' < v[0] { + return + } + i := 1 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + if v[0] == '0' && i != 1 { + return + } + return v[:i], v[i:], true +} + +func parsePrerelease(v string) (t, rest string, ok bool) { + // "A pre-release version MAY be denoted by appending a hyphen and + // a series of dot separated identifiers immediately following the patch version. + // Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-]. + // Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes." + if v == "" || v[0] != '-' { + return + } + i := 1 + start := 1 + for i < len(v) && v[i] != '+' { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i || isBadNum(v[start:i]) { + return + } + start = i + 1 + } + i++ + } + if start == i || isBadNum(v[start:i]) { + return + } + return v[:i], v[i:], true +} + +func parseBuild(v string) (t, rest string, ok bool) { + if v == "" || v[0] != '+' { + return + } + i := 1 + start := 1 + for i < len(v) { + if !isIdentChar(v[i]) && v[i] != '.' { + return + } + if v[i] == '.' { + if start == i { + return + } + start = i + 1 + } + i++ + } + if start == i { + return + } + return v[:i], v[i:], true +} + +func isIdentChar(c byte) bool { + return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' +} + +func isBadNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) && i > 1 && v[0] == '0' +} + +func isNum(v string) bool { + i := 0 + for i < len(v) && '0' <= v[i] && v[i] <= '9' { + i++ + } + return i == len(v) +} + +func compareInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} + +func comparePrerelease(x, y string) int { + // "When major, minor, and patch are equal, a pre-release version has + // lower precedence than a normal version. + // Example: 1.0.0-alpha < 1.0.0. + // Precedence for two pre-release versions with the same major, minor, + // and patch version MUST be determined by comparing each dot separated + // identifier from left to right until a difference is found as follows: + // identifiers consisting of only digits are compared numerically and + // identifiers with letters or hyphens are compared lexically in ASCII + // sort order. Numeric identifiers always have lower precedence than + // non-numeric identifiers. A larger set of pre-release fields has a + // higher precedence than a smaller set, if all of the preceding + // identifiers are equal. + // Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < + // 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0." + if x == y { + return 0 + } + if x == "" { + return +1 + } + if y == "" { + return -1 + } + for x != "" && y != "" { + x = x[1:] // skip - or . + y = y[1:] // skip - or . + var dx, dy string + dx, x = nextIdent(x) + dy, y = nextIdent(y) + if dx != dy { + ix := isNum(dx) + iy := isNum(dy) + if ix != iy { + if ix { + return -1 + } else { + return +1 + } + } + if ix { + if len(dx) < len(dy) { + return -1 + } + if len(dx) > len(dy) { + return +1 + } + } + if dx < dy { + return -1 + } else { + return +1 + } + } + } + if x == "" { + return -1 + } else { + return +1 + } +} + +func nextIdent(x string) (dx, rest string) { + i := 0 + for i < len(x) && x[i] != '.' { + i++ + } + return x[:i], x[i:] +} diff --git a/vendor/golang.org/x/sync/LICENSE b/vendor/golang.org/x/sync/LICENSE new file mode 100644 index 0000000..2a7cf70 --- /dev/null +++ b/vendor/golang.org/x/sync/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/sync/PATENTS b/vendor/golang.org/x/sync/PATENTS new file mode 100644 index 0000000..7330990 --- /dev/null +++ b/vendor/golang.org/x/sync/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go new file mode 100644 index 0000000..948a3ee --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -0,0 +1,135 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errgroup provides synchronization, error propagation, and Context +// cancelation for groups of goroutines working on subtasks of a common task. +// +// [errgroup.Group] is related to [sync.WaitGroup] but adds handling of tasks +// returning errors. +package errgroup + +import ( + "context" + "fmt" + "sync" +) + +type token struct{} + +// A Group is a collection of goroutines working on subtasks that are part of +// the same overall task. +// +// A zero Group is valid, has no limit on the number of active goroutines, +// and does not cancel on error. +type Group struct { + cancel func(error) + + wg sync.WaitGroup + + sem chan token + + errOnce sync.Once + err error +} + +func (g *Group) done() { + if g.sem != nil { + <-g.sem + } + g.wg.Done() +} + +// WithContext returns a new Group and an associated Context derived from ctx. +// +// The derived Context is canceled the first time a function passed to Go +// returns a non-nil error or the first time Wait returns, whichever occurs +// first. +func WithContext(ctx context.Context) (*Group, context.Context) { + ctx, cancel := withCancelCause(ctx) + return &Group{cancel: cancel}, ctx +} + +// Wait blocks until all function calls from the Go method have returned, then +// returns the first non-nil error (if any) from them. +func (g *Group) Wait() error { + g.wg.Wait() + if g.cancel != nil { + g.cancel(g.err) + } + return g.err +} + +// Go calls the given function in a new goroutine. +// It blocks until the new goroutine can be added without the number of +// active goroutines in the group exceeding the configured limit. +// +// The first call to return a non-nil error cancels the group's context, if the +// group was created by calling WithContext. The error will be returned by Wait. +func (g *Group) Go(f func() error) { + if g.sem != nil { + g.sem <- token{} + } + + g.wg.Add(1) + go func() { + defer g.done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel(g.err) + } + }) + } + }() +} + +// TryGo calls the given function in a new goroutine only if the number of +// active goroutines in the group is currently below the configured limit. +// +// The return value reports whether the goroutine was started. +func (g *Group) TryGo(f func() error) bool { + if g.sem != nil { + select { + case g.sem <- token{}: + // Note: this allows barging iff channels in general allow barging. + default: + return false + } + } + + g.wg.Add(1) + go func() { + defer g.done() + + if err := f(); err != nil { + g.errOnce.Do(func() { + g.err = err + if g.cancel != nil { + g.cancel(g.err) + } + }) + } + }() + return true +} + +// SetLimit limits the number of active goroutines in this group to at most n. +// A negative value indicates no limit. +// +// Any subsequent call to the Go method will block until it can add an active +// goroutine without exceeding the configured limit. +// +// The limit must not be modified while any goroutines in the group are active. +func (g *Group) SetLimit(n int) { + if n < 0 { + g.sem = nil + return + } + if len(g.sem) != 0 { + panic(fmt.Errorf("errgroup: modify limit while %v goroutines in the group are still active", len(g.sem))) + } + g.sem = make(chan token, n) +} diff --git a/vendor/golang.org/x/sync/errgroup/go120.go b/vendor/golang.org/x/sync/errgroup/go120.go new file mode 100644 index 0000000..f93c740 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/go120.go @@ -0,0 +1,13 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + return context.WithCancelCause(parent) +} diff --git a/vendor/golang.org/x/sync/errgroup/pre_go120.go b/vendor/golang.org/x/sync/errgroup/pre_go120.go new file mode 100644 index 0000000..88ce334 --- /dev/null +++ b/vendor/golang.org/x/sync/errgroup/pre_go120.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.20 + +package errgroup + +import "context" + +func withCancelCause(parent context.Context) (context.Context, func(error)) { + ctx, cancel := context.WithCancel(parent) + return ctx, func(error) { cancel() } +} diff --git a/vendor/golang.org/x/text/internal/gen/code.go b/vendor/golang.org/x/text/internal/gen/code.go new file mode 100644 index 0000000..75435c9 --- /dev/null +++ b/vendor/golang.org/x/text/internal/gen/code.go @@ -0,0 +1,375 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gen + +import ( + "bytes" + "encoding/gob" + "fmt" + "hash" + "hash/fnv" + "io" + "log" + "os" + "reflect" + "strings" + "unicode" + "unicode/utf8" +) + +// This file contains utilities for generating code. + +// TODO: other write methods like: +// - slices, maps, types, etc. + +// CodeWriter is a utility for writing structured code. It computes the content +// hash and size of written content. It ensures there are newlines between +// written code blocks. +type CodeWriter struct { + buf bytes.Buffer + Size int + Hash hash.Hash32 // content hash + gob *gob.Encoder + // For comments we skip the usual one-line separator if they are followed by + // a code block. + skipSep bool +} + +func (w *CodeWriter) Write(p []byte) (n int, err error) { + return w.buf.Write(p) +} + +// NewCodeWriter returns a new CodeWriter. +func NewCodeWriter() *CodeWriter { + h := fnv.New32() + return &CodeWriter{Hash: h, gob: gob.NewEncoder(h)} +} + +// WriteGoFile appends the buffer with the total size of all created structures +// and writes it as a Go file to the given file with the given package name. +func (w *CodeWriter) WriteGoFile(filename, pkg string) { + f, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer f.Close() + if _, err = w.WriteGo(f, pkg, ""); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteVersionedGoFile appends the buffer with the total size of all created +// structures and writes it as a Go file to the given file with the given +// package name and build tags for the current Unicode version, +func (w *CodeWriter) WriteVersionedGoFile(filename, pkg string) { + tags := buildTags() + if tags != "" { + pattern := fileToPattern(filename) + updateBuildTags(pattern) + filename = fmt.Sprintf(pattern, UnicodeVersion()) + } + f, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer f.Close() + if _, err = w.WriteGo(f, pkg, tags); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteGo appends the buffer with the total size of all created structures and +// writes it as a Go file to the given writer with the given package name. +func (w *CodeWriter) WriteGo(out io.Writer, pkg, tags string) (n int, err error) { + sz := w.Size + if sz > 0 { + w.WriteComment("Total table size %d bytes (%dKiB); checksum: %X\n", sz, sz/1024, w.Hash.Sum32()) + } + defer w.buf.Reset() + return WriteGo(out, pkg, tags, w.buf.Bytes()) +} + +func (w *CodeWriter) printf(f string, x ...interface{}) { + fmt.Fprintf(w, f, x...) +} + +func (w *CodeWriter) insertSep() { + if w.skipSep { + w.skipSep = false + return + } + // Use at least two newlines to ensure a blank space between the previous + // block. WriteGoFile will remove extraneous newlines. + w.printf("\n\n") +} + +// WriteComment writes a comment block. All line starts are prefixed with "//". +// Initial empty lines are gobbled. The indentation for the first line is +// stripped from consecutive lines. +func (w *CodeWriter) WriteComment(comment string, args ...interface{}) { + s := fmt.Sprintf(comment, args...) + s = strings.Trim(s, "\n") + + // Use at least two newlines to ensure a blank space between the previous + // block. WriteGoFile will remove extraneous newlines. + w.printf("\n\n// ") + w.skipSep = true + + // strip first indent level. + sep := "\n" + for ; len(s) > 0 && (s[0] == '\t' || s[0] == ' '); s = s[1:] { + sep += s[:1] + } + + strings.NewReplacer(sep, "\n// ", "\n", "\n// ").WriteString(w, s) + + w.printf("\n") +} + +func (w *CodeWriter) writeSizeInfo(size int) { + w.printf("// Size: %d bytes\n", size) +} + +// WriteConst writes a constant of the given name and value. +func (w *CodeWriter) WriteConst(name string, x interface{}) { + w.insertSep() + v := reflect.ValueOf(x) + + switch v.Type().Kind() { + case reflect.String: + w.printf("const %s %s = ", name, typeName(x)) + w.WriteString(v.String()) + w.printf("\n") + default: + w.printf("const %s = %#v\n", name, x) + } +} + +// WriteVar writes a variable of the given name and value. +func (w *CodeWriter) WriteVar(name string, x interface{}) { + w.insertSep() + v := reflect.ValueOf(x) + oldSize := w.Size + sz := int(v.Type().Size()) + w.Size += sz + + switch v.Type().Kind() { + case reflect.String: + w.printf("var %s %s = ", name, typeName(x)) + w.WriteString(v.String()) + case reflect.Struct: + w.gob.Encode(x) + fallthrough + case reflect.Slice, reflect.Array: + w.printf("var %s = ", name) + w.writeValue(v) + w.writeSizeInfo(w.Size - oldSize) + default: + w.printf("var %s %s = ", name, typeName(x)) + w.gob.Encode(x) + w.writeValue(v) + w.writeSizeInfo(w.Size - oldSize) + } + w.printf("\n") +} + +func (w *CodeWriter) writeValue(v reflect.Value) { + x := v.Interface() + switch v.Kind() { + case reflect.String: + w.WriteString(v.String()) + case reflect.Array: + // Don't double count: callers of WriteArray count on the size being + // added, so we need to discount it here. + w.Size -= int(v.Type().Size()) + w.writeSlice(x, true) + case reflect.Slice: + w.writeSlice(x, false) + case reflect.Struct: + w.printf("%s{\n", typeName(v.Interface())) + t := v.Type() + for i := 0; i < v.NumField(); i++ { + w.printf("%s: ", t.Field(i).Name) + w.writeValue(v.Field(i)) + w.printf(",\n") + } + w.printf("}") + default: + w.printf("%#v", x) + } +} + +// WriteString writes a string literal. +func (w *CodeWriter) WriteString(s string) { + io.WriteString(w.Hash, s) // content hash + w.Size += len(s) + + const maxInline = 40 + if len(s) <= maxInline { + w.printf("%q", s) + return + } + + // We will render the string as a multi-line string. + const maxWidth = 80 - 4 - len(`"`) - len(`" +`) + + // When starting on its own line, go fmt indents line 2+ an extra level. + n, max := maxWidth, maxWidth-4 + + // As per https://golang.org/issue/18078, the compiler has trouble + // compiling the concatenation of many strings, s0 + s1 + s2 + ... + sN, + // for large N. We insert redundant, explicit parentheses to work around + // that, lowering the N at any given step: (s0 + s1 + ... + s63) + (s64 + + // ... + s127) + etc + (etc + ... + sN). + explicitParens, extraComment := len(s) > 128*1024, "" + if explicitParens { + w.printf(`(`) + extraComment = "; the redundant, explicit parens are for https://golang.org/issue/18078" + } + + // Print "" +\n, if a string does not start on its own line. + b := w.buf.Bytes() + if p := len(bytes.TrimRight(b, " \t")); p > 0 && b[p-1] != '\n' { + w.printf("\"\" + // Size: %d bytes%s\n", len(s), extraComment) + n, max = maxWidth, maxWidth + } + + w.printf(`"`) + + for sz, p, nLines := 0, 0, 0; p < len(s); { + var r rune + r, sz = utf8.DecodeRuneInString(s[p:]) + out := s[p : p+sz] + chars := 1 + if !unicode.IsPrint(r) || r == utf8.RuneError || r == '"' { + switch sz { + case 1: + out = fmt.Sprintf("\\x%02x", s[p]) + case 2, 3: + out = fmt.Sprintf("\\u%04x", r) + case 4: + out = fmt.Sprintf("\\U%08x", r) + } + chars = len(out) + } else if r == '\\' { + out = "\\" + string(r) + chars = 2 + } + if n -= chars; n < 0 { + nLines++ + if explicitParens && nLines&63 == 63 { + w.printf("\") + (\"") + } + w.printf("\" +\n\"") + n = max - len(out) + } + w.printf("%s", out) + p += sz + } + w.printf(`"`) + if explicitParens { + w.printf(`)`) + } +} + +// WriteSlice writes a slice value. +func (w *CodeWriter) WriteSlice(x interface{}) { + w.writeSlice(x, false) +} + +// WriteArray writes an array value. +func (w *CodeWriter) WriteArray(x interface{}) { + w.writeSlice(x, true) +} + +func (w *CodeWriter) writeSlice(x interface{}, isArray bool) { + v := reflect.ValueOf(x) + w.gob.Encode(v.Len()) + w.Size += v.Len() * int(v.Type().Elem().Size()) + name := typeName(x) + if isArray { + name = fmt.Sprintf("[%d]%s", v.Len(), name[strings.Index(name, "]")+1:]) + } + if isArray { + w.printf("%s{\n", name) + } else { + w.printf("%s{ // %d elements\n", name, v.Len()) + } + + switch kind := v.Type().Elem().Kind(); kind { + case reflect.String: + for _, s := range x.([]string) { + w.WriteString(s) + w.printf(",\n") + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // nLine and nBlock are the number of elements per line and block. + nLine, nBlock, format := 8, 64, "%d," + switch kind { + case reflect.Uint8: + format = "%#02x," + case reflect.Uint16: + format = "%#04x," + case reflect.Uint32: + nLine, nBlock, format = 4, 32, "%#08x," + case reflect.Uint, reflect.Uint64: + nLine, nBlock, format = 4, 32, "%#016x," + case reflect.Int8: + nLine = 16 + } + n := nLine + for i := 0; i < v.Len(); i++ { + if i%nBlock == 0 && v.Len() > nBlock { + w.printf("// Entry %X - %X\n", i, i+nBlock-1) + } + x := v.Index(i).Interface() + w.gob.Encode(x) + w.printf(format, x) + if n--; n == 0 { + n = nLine + w.printf("\n") + } + } + w.printf("\n") + case reflect.Struct: + zero := reflect.Zero(v.Type().Elem()).Interface() + for i := 0; i < v.Len(); i++ { + x := v.Index(i).Interface() + w.gob.EncodeValue(v) + if !reflect.DeepEqual(zero, x) { + line := fmt.Sprintf("%#v,\n", x) + line = line[strings.IndexByte(line, '{'):] + w.printf("%d: ", i) + w.printf(line) + } + } + case reflect.Array: + for i := 0; i < v.Len(); i++ { + w.printf("%d: %#v,\n", i, v.Index(i).Interface()) + } + default: + panic("gen: slice elem type not supported") + } + w.printf("}") +} + +// WriteType writes a definition of the type of the given value and returns the +// type name. +func (w *CodeWriter) WriteType(x interface{}) string { + t := reflect.TypeOf(x) + w.printf("type %s struct {\n", t.Name()) + for i := 0; i < t.NumField(); i++ { + w.printf("\t%s %s\n", t.Field(i).Name, t.Field(i).Type) + } + w.printf("}\n") + return t.Name() +} + +// typeName returns the name of the go type of x. +func typeName(x interface{}) string { + t := reflect.ValueOf(x).Type() + return strings.Replace(fmt.Sprint(t), "main.", "", 1) +} diff --git a/vendor/golang.org/x/text/internal/gen/gen.go b/vendor/golang.org/x/text/internal/gen/gen.go new file mode 100644 index 0000000..78bfef6 --- /dev/null +++ b/vendor/golang.org/x/text/internal/gen/gen.go @@ -0,0 +1,354 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gen contains common code for the various code generation tools in the +// text repository. Its usage ensures consistency between tools. +// +// This package defines command line flags that are common to most generation +// tools. The flags allow for specifying specific Unicode and CLDR versions +// in the public Unicode data repository (https://www.unicode.org/Public). +// +// A local Unicode data mirror can be set through the flag -local or the +// environment variable UNICODE_DIR. The former takes precedence. The local +// directory should follow the same structure as the public repository. +// +// IANA data can also optionally be mirrored by putting it in the iana directory +// rooted at the top of the local mirror. Beware, though, that IANA data is not +// versioned. So it is up to the developer to use the right version. +package gen // import "golang.org/x/text/internal/gen" + +import ( + "bytes" + "flag" + "fmt" + "go/build" + "go/format" + "io" + "log" + "net/http" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "sync" + "unicode" + + "golang.org/x/text/unicode/cldr" +) + +var ( + url = flag.String("url", + "https://www.unicode.org/Public", + "URL of Unicode database directory") + iana = flag.String("iana", + "http://www.iana.org", + "URL of the IANA repository") + unicodeVersion = flag.String("unicode", + getEnv("UNICODE_VERSION", unicode.Version), + "unicode version to use") + cldrVersion = flag.String("cldr", + getEnv("CLDR_VERSION", cldr.Version), + "cldr version to use") +) + +func getEnv(name, def string) string { + if v := os.Getenv(name); v != "" { + return v + } + return def +} + +// Init performs common initialization for a gen command. It parses the flags +// and sets up the standard logging parameters. +func Init() { + log.SetPrefix("") + log.SetFlags(log.Lshortfile) + flag.Parse() +} + +const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +` + +// UnicodeVersion reports the requested Unicode version. +func UnicodeVersion() string { + return *unicodeVersion +} + +// CLDRVersion reports the requested CLDR version. +func CLDRVersion() string { + return *cldrVersion +} + +var tags = []struct{ version, buildTags string }{ + {"9.0.0", "!go1.10"}, + {"10.0.0", "go1.10,!go1.13"}, + {"11.0.0", "go1.13,!go1.14"}, + {"12.0.0", "go1.14,!go1.16"}, + {"13.0.0", "go1.16,!go1.21"}, + {"15.0.0", "go1.21"}, +} + +// buildTags reports the build tags used for the current Unicode version. +func buildTags() string { + v := UnicodeVersion() + for _, e := range tags { + if e.version == v { + return e.buildTags + } + } + log.Fatalf("Unknown build tags for Unicode version %q.", v) + return "" +} + +// IsLocal reports whether data files are available locally. +func IsLocal() bool { + dir, err := localReadmeFile() + if err != nil { + return false + } + if _, err = os.Stat(dir); err != nil { + return false + } + return true +} + +// OpenUCDFile opens the requested UCD file. The file is specified relative to +// the public Unicode root directory. It will call log.Fatal if there are any +// errors. +func OpenUCDFile(file string) io.ReadCloser { + return openUnicode(path.Join(*unicodeVersion, "ucd", file)) +} + +// OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there +// are any errors. +func OpenCLDRCoreZip() io.ReadCloser { + return OpenUnicodeFile("cldr", *cldrVersion, "core.zip") +} + +// OpenUnicodeFile opens the requested file of the requested category from the +// root of the Unicode data archive. The file is specified relative to the +// public Unicode root directory. If version is "", it will use the default +// Unicode version. It will call log.Fatal if there are any errors. +func OpenUnicodeFile(category, version, file string) io.ReadCloser { + if version == "" { + version = UnicodeVersion() + } + return openUnicode(path.Join(category, version, file)) +} + +// OpenIANAFile opens the requested IANA file. The file is specified relative +// to the IANA root, which is typically either http://www.iana.org or the +// iana directory in the local mirror. It will call log.Fatal if there are any +// errors. +func OpenIANAFile(path string) io.ReadCloser { + return Open(*iana, "iana", path) +} + +var ( + dirMutex sync.Mutex + localDir string +) + +const permissions = 0755 + +func localReadmeFile() (string, error) { + p, err := build.Import("golang.org/x/text", "", build.FindOnly) + if err != nil { + return "", fmt.Errorf("Could not locate package: %v", err) + } + return filepath.Join(p.Dir, "DATA", "README"), nil +} + +func getLocalDir() string { + dirMutex.Lock() + defer dirMutex.Unlock() + + readme, err := localReadmeFile() + if err != nil { + log.Fatal(err) + } + dir := filepath.Dir(readme) + if _, err := os.Stat(readme); err != nil { + if err := os.MkdirAll(dir, permissions); err != nil { + log.Fatalf("Could not create directory: %v", err) + } + os.WriteFile(readme, []byte(readmeTxt), permissions) + } + return dir +} + +const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT. + +This directory contains downloaded files used to generate the various tables +in the golang.org/x/text subrepo. + +Note that the language subtag repo (iana/assignments/language-subtag-registry) +and all other times in the iana subdirectory are not versioned and will need +to be periodically manually updated. The easiest way to do this is to remove +the entire iana directory. This is mostly of concern when updating the language +package. +` + +// Open opens subdir/path if a local directory is specified and the file exists, +// where subdir is a directory relative to the local root, or fetches it from +// urlRoot/path otherwise. It will call log.Fatal if there are any errors. +func Open(urlRoot, subdir, path string) io.ReadCloser { + file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path)) + return open(file, urlRoot, path) +} + +func openUnicode(path string) io.ReadCloser { + file := filepath.Join(getLocalDir(), filepath.FromSlash(path)) + return open(file, *url, path) +} + +// TODO: automatically periodically update non-versioned files. + +func open(file, urlRoot, path string) io.ReadCloser { + if f, err := os.Open(file); err == nil { + return f + } + r := get(urlRoot, path) + defer r.Close() + b, err := io.ReadAll(r) + if err != nil { + log.Fatalf("Could not download file: %v", err) + } + os.MkdirAll(filepath.Dir(file), permissions) + if err := os.WriteFile(file, b, permissions); err != nil { + log.Fatalf("Could not create file: %v", err) + } + return io.NopCloser(bytes.NewReader(b)) +} + +func get(root, path string) io.ReadCloser { + url := root + "/" + path + fmt.Printf("Fetching %s...", url) + defer fmt.Println(" done.") + resp, err := http.Get(url) + if err != nil { + log.Fatalf("HTTP GET: %v", err) + } + if resp.StatusCode != 200 { + log.Fatalf("Bad GET status for %q: %q", url, resp.Status) + } + return resp.Body +} + +// TODO: use Write*Version in all applicable packages. + +// WriteUnicodeVersion writes a constant for the Unicode version from which the +// tables are generated. +func WriteUnicodeVersion(w io.Writer) { + fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n") + fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion()) +} + +// WriteCLDRVersion writes a constant for the CLDR version from which the +// tables are generated. +func WriteCLDRVersion(w io.Writer) { + fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n") + fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion()) +} + +// WriteGoFile prepends a standard file comment and package statement to the +// given bytes, applies gofmt, and writes them to a file with the given name. +// It will call log.Fatal if there are any errors. +func WriteGoFile(filename, pkg string, b []byte) { + w, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer w.Close() + if _, err = WriteGo(w, pkg, "", b); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +func fileToPattern(filename string) string { + suffix := ".go" + if strings.HasSuffix(filename, "_test.go") { + suffix = "_test.go" + } + prefix := filename[:len(filename)-len(suffix)] + return fmt.Sprint(prefix, "%s", suffix) +} + +// tagLines returns the //go:build lines to add to the file. +func tagLines(tags string) string { + return "//go:build " + strings.ReplaceAll(tags, ",", " && ") + "\n" +} + +func updateBuildTags(pattern string) { + for _, t := range tags { + oldFile := fmt.Sprintf(pattern, t.version) + b, err := os.ReadFile(oldFile) + if err != nil { + continue + } + b = regexp.MustCompile(`//go:build.*\n`).ReplaceAll(b, []byte(tagLines(t.buildTags))) + err = os.WriteFile(oldFile, b, 0644) + if err != nil { + log.Fatal(err) + } + } +} + +// WriteVersionedGoFile prepends a standard file comment, adds build tags to +// version the file for the current Unicode version, and package statement to +// the given bytes, applies gofmt, and writes them to a file with the given +// name. It will call log.Fatal if there are any errors. +func WriteVersionedGoFile(filename, pkg string, b []byte) { + pattern := fileToPattern(filename) + updateBuildTags(pattern) + filename = fmt.Sprintf(pattern, UnicodeVersion()) + + w, err := os.Create(filename) + if err != nil { + log.Fatalf("Could not create file %s: %v", filename, err) + } + defer w.Close() + if _, err = WriteGo(w, pkg, buildTags(), b); err != nil { + log.Fatalf("Error writing file %s: %v", filename, err) + } +} + +// WriteGo prepends a standard file comment and package statement to the given +// bytes, applies gofmt, and writes them to w. +func WriteGo(w io.Writer, pkg, tags string, b []byte) (n int, err error) { + src := []byte(header) + if tags != "" { + src = append(src, tagLines(tags)...) + src = append(src, '\n') + } + src = append(src, fmt.Sprintf("package %s\n\n", pkg)...) + src = append(src, b...) + formatted, err := format.Source(src) + if err != nil { + // Print the generated code even in case of an error so that the + // returned error can be meaningfully interpreted. + n, _ = w.Write(src) + return n, err + } + return w.Write(formatted) +} + +// Repackage rewrites a Go file from belonging to package main to belonging to +// the given package. +func Repackage(inFile, outFile, pkg string) { + src, err := os.ReadFile(inFile) + if err != nil { + log.Fatalf("reading %s: %v", inFile, err) + } + const toDelete = "package main\n\n" + i := bytes.Index(src, []byte(toDelete)) + if i < 0 { + log.Fatalf("Could not find %q in %s.", toDelete, inFile) + } + w := &bytes.Buffer{} + w.Write(src[i+len(toDelete):]) + WriteGoFile(outFile, pkg, w.Bytes()) +} diff --git a/vendor/golang.org/x/text/message/pipeline/extract.go b/vendor/golang.org/x/text/message/pipeline/extract.go new file mode 100644 index 0000000..a15a7f9 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/extract.go @@ -0,0 +1,821 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pipeline + +import ( + "bytes" + "errors" + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/token" + "go/types" + "path/filepath" + "sort" + "strings" + "unicode" + "unicode/utf8" + + fmtparser "golang.org/x/text/internal/format" + "golang.org/x/tools/go/callgraph" + "golang.org/x/tools/go/callgraph/cha" + "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" +) + +const debug = false + +// TODO: +// - merge information into existing files +// - handle different file formats (PO, XLIFF) +// - handle features (gender, plural) +// - message rewriting + +// - `msg:"etc"` tags + +// Extract extracts all strings form the package defined in Config. +func Extract(c *Config) (*State, error) { + x, err := newExtracter(c) + if err != nil { + return nil, wrap(err, "") + } + + if err := x.seedEndpoints(); err != nil { + return nil, err + } + x.extractMessages() + + return &State{ + Config: *c, + program: x.iprog, + Extracted: Messages{ + Language: c.SourceLanguage, + Messages: x.messages, + }, + }, nil +} + +type extracter struct { + conf loader.Config + iprog *loader.Program + prog *ssa.Program + callGraph *callgraph.Graph + + // Calls and other expressions to collect. + globals map[token.Pos]*constData + funcs map[token.Pos]*callData + messages []Message +} + +func newExtracter(c *Config) (x *extracter, err error) { + x = &extracter{ + conf: loader.Config{}, + globals: map[token.Pos]*constData{}, + funcs: map[token.Pos]*callData{}, + } + + x.iprog, err = loadPackages(&x.conf, c.Packages) + if err != nil { + return nil, wrap(err, "") + } + + x.prog = ssautil.CreateProgram(x.iprog, ssa.GlobalDebug|ssa.BareInits) + x.prog.Build() + + x.callGraph = cha.CallGraph(x.prog) + + return x, nil +} + +func (x *extracter) globalData(pos token.Pos) *constData { + cd := x.globals[pos] + if cd == nil { + cd = &constData{} + x.globals[pos] = cd + } + return cd +} + +func (x *extracter) seedEndpoints() error { + pkgInfo := x.iprog.Package("golang.org/x/text/message") + if pkgInfo == nil { + return errors.New("pipeline: golang.org/x/text/message is not imported") + } + pkg := x.prog.Package(pkgInfo.Pkg) + typ := types.NewPointer(pkg.Type("Printer").Type()) + + x.processGlobalVars() + + x.handleFunc(x.prog.LookupMethod(typ, pkg.Pkg, "Printf"), &callData{ + formatPos: 1, + argPos: 2, + isMethod: true, + }) + x.handleFunc(x.prog.LookupMethod(typ, pkg.Pkg, "Sprintf"), &callData{ + formatPos: 1, + argPos: 2, + isMethod: true, + }) + x.handleFunc(x.prog.LookupMethod(typ, pkg.Pkg, "Fprintf"), &callData{ + formatPos: 2, + argPos: 3, + isMethod: true, + }) + return nil +} + +// processGlobalVars finds string constants that are assigned to global +// variables. +func (x *extracter) processGlobalVars() { + for _, p := range x.prog.AllPackages() { + m, ok := p.Members["init"] + if !ok { + continue + } + for _, b := range m.(*ssa.Function).Blocks { + for _, i := range b.Instrs { + s, ok := i.(*ssa.Store) + if !ok { + continue + } + a, ok := s.Addr.(*ssa.Global) + if !ok { + continue + } + t := a.Type() + for { + p, ok := t.(*types.Pointer) + if !ok { + break + } + t = p.Elem() + } + if b, ok := t.(*types.Basic); !ok || b.Kind() != types.String { + continue + } + x.visitInit(a, s.Val) + } + } + } +} + +type constData struct { + call *callData // to provide a signature for the constants + values []constVal + others []token.Pos // Assigned to other global data. +} + +func (d *constData) visit(x *extracter, f func(c constant.Value)) { + for _, v := range d.values { + f(v.value) + } + for _, p := range d.others { + if od, ok := x.globals[p]; ok { + od.visit(x, f) + } + } +} + +type constVal struct { + value constant.Value + pos token.Pos +} + +type callData struct { + call ssa.CallInstruction + expr *ast.CallExpr + formats []constant.Value + + callee *callData + isMethod bool + formatPos int + argPos int // varargs at this position in the call + argTypes []int // arguments extractable from this position +} + +func (c *callData) callFormatPos() int { + c = c.callee + if c.isMethod { + return c.formatPos - 1 + } + return c.formatPos +} + +func (c *callData) callArgsStart() int { + c = c.callee + if c.isMethod { + return c.argPos - 1 + } + return c.argPos +} + +func (c *callData) Pos() token.Pos { return c.call.Pos() } +func (c *callData) Pkg() *types.Package { return c.call.Parent().Pkg.Pkg } + +func (x *extracter) handleFunc(f *ssa.Function, fd *callData) { + for _, e := range x.callGraph.Nodes[f].In { + if e.Pos() == 0 { + continue + } + + call := e.Site + caller := x.funcs[call.Pos()] + if caller != nil { + // TODO: theoretically a format string could be passed to multiple + // arguments of a function. Support this eventually. + continue + } + x.debug(call, "CALL", f.String()) + + caller = &callData{ + call: call, + callee: fd, + formatPos: -1, + argPos: -1, + } + // Offset by one if we are invoking an interface method. + offset := 0 + if call.Common().IsInvoke() { + offset = -1 + } + x.funcs[call.Pos()] = caller + if fd.argPos >= 0 { + x.visitArgs(caller, call.Common().Args[fd.argPos+offset]) + } + x.visitFormats(caller, call.Common().Args[fd.formatPos+offset]) + } +} + +type posser interface { + Pos() token.Pos + Parent() *ssa.Function +} + +func (x *extracter) debug(v posser, header string, args ...interface{}) { + if debug { + pos := "" + if p := v.Parent(); p != nil { + pos = posString(&x.conf, p.Package().Pkg, v.Pos()) + } + if header != "CALL" && header != "INSERT" { + header = " " + header + } + fmt.Printf("%-32s%-10s%-15T ", pos+fmt.Sprintf("@%d", v.Pos()), header, v) + for _, a := range args { + fmt.Printf(" %v", a) + } + fmt.Println() + } +} + +// visitInit evaluates and collects values assigned to global variables in an +// init function. +func (x *extracter) visitInit(global *ssa.Global, v ssa.Value) { + if v == nil { + return + } + x.debug(v, "GLOBAL", v) + + switch v := v.(type) { + case *ssa.Phi: + for _, e := range v.Edges { + x.visitInit(global, e) + } + + case *ssa.Const: + // Only record strings with letters. + if str := constant.StringVal(v.Value); isMsg(str) { + cd := x.globalData(global.Pos()) + cd.values = append(cd.values, constVal{v.Value, v.Pos()}) + } + // TODO: handle %m-directive. + + case *ssa.Global: + cd := x.globalData(global.Pos()) + cd.others = append(cd.others, v.Pos()) + + case *ssa.FieldAddr, *ssa.Field: + // TODO: mark field index v.Field of v.X.Type() for extraction. extract + // an example args as to give parameters for the translator. + + case *ssa.Slice: + if v.Low == nil && v.High == nil && v.Max == nil { + x.visitInit(global, v.X) + } + + case *ssa.Alloc: + if ref := v.Referrers(); ref == nil { + for _, r := range *ref { + values := []ssa.Value{} + for _, o := range r.Operands(nil) { + if o == nil || *o == v { + continue + } + values = append(values, *o) + } + // TODO: return something different if we care about multiple + // values as well. + if len(values) == 1 { + x.visitInit(global, values[0]) + } + } + } + + case ssa.Instruction: + rands := v.Operands(nil) + if len(rands) == 1 && rands[0] != nil { + x.visitInit(global, *rands[0]) + } + } + return +} + +// visitFormats finds the original source of the value. The returned index is +// position of the argument if originated from a function argument or -1 +// otherwise. +func (x *extracter) visitFormats(call *callData, v ssa.Value) { + if v == nil { + return + } + x.debug(v, "VALUE", v) + + switch v := v.(type) { + case *ssa.Phi: + for _, e := range v.Edges { + x.visitFormats(call, e) + } + + case *ssa.Const: + // Only record strings with letters. + if isMsg(constant.StringVal(v.Value)) { + x.debug(call.call, "FORMAT", v.Value.ExactString()) + call.formats = append(call.formats, v.Value) + } + // TODO: handle %m-directive. + + case *ssa.Global: + x.globalData(v.Pos()).call = call + + case *ssa.FieldAddr, *ssa.Field: + // TODO: mark field index v.Field of v.X.Type() for extraction. extract + // an example args as to give parameters for the translator. + + case *ssa.Slice: + if v.Low == nil && v.High == nil && v.Max == nil { + x.visitFormats(call, v.X) + } + + case *ssa.Parameter: + // TODO: handle the function for the index parameter. + f := v.Parent() + for i, p := range f.Params { + if p == v { + if call.formatPos < 0 { + call.formatPos = i + // TODO: is there a better way to detect this is calling + // a method rather than a function? + call.isMethod = len(f.Params) > f.Signature.Params().Len() + x.handleFunc(v.Parent(), call) + } else if debug && i != call.formatPos { + // TODO: support this. + fmt.Printf("WARNING:%s: format string passed to arg %d and %d\n", + posString(&x.conf, call.Pkg(), call.Pos()), + call.formatPos, i) + } + } + } + + case *ssa.Alloc: + if ref := v.Referrers(); ref == nil { + for _, r := range *ref { + values := []ssa.Value{} + for _, o := range r.Operands(nil) { + if o == nil || *o == v { + continue + } + values = append(values, *o) + } + // TODO: return something different if we care about multiple + // values as well. + if len(values) == 1 { + x.visitFormats(call, values[0]) + } + } + } + + // TODO: + // case *ssa.Index: + // // Get all values in the array if applicable + // case *ssa.IndexAddr: + // // Get all values in the slice or *array if applicable. + // case *ssa.Lookup: + // // Get all values in the map if applicable. + + case *ssa.FreeVar: + // TODO: find the link between free variables and parameters: + // + // func freeVar(p *message.Printer, str string) { + // fn := func(p *message.Printer) { + // p.Printf(str) + // } + // fn(p) + // } + + case *ssa.Call: + + case ssa.Instruction: + rands := v.Operands(nil) + if len(rands) == 1 && rands[0] != nil { + x.visitFormats(call, *rands[0]) + } + } +} + +// Note: a function may have an argument marked as both format and passthrough. + +// visitArgs collects information on arguments. For wrapped functions it will +// just determine the position of the variable args slice. +func (x *extracter) visitArgs(fd *callData, v ssa.Value) { + if v == nil { + return + } + x.debug(v, "ARGV", v) + switch v := v.(type) { + + case *ssa.Slice: + if v.Low == nil && v.High == nil && v.Max == nil { + x.visitArgs(fd, v.X) + } + + case *ssa.Parameter: + // TODO: handle the function for the index parameter. + f := v.Parent() + for i, p := range f.Params { + if p == v { + fd.argPos = i + } + } + + case *ssa.Alloc: + if ref := v.Referrers(); ref == nil { + for _, r := range *ref { + values := []ssa.Value{} + for _, o := range r.Operands(nil) { + if o == nil || *o == v { + continue + } + values = append(values, *o) + } + // TODO: return something different if we care about + // multiple values as well. + if len(values) == 1 { + x.visitArgs(fd, values[0]) + } + } + } + + case ssa.Instruction: + rands := v.Operands(nil) + if len(rands) == 1 && rands[0] != nil { + x.visitArgs(fd, *rands[0]) + } + } +} + +// print returns Go syntax for the specified node. +func (x *extracter) print(n ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, x.conf.Fset, n) + return buf.String() +} + +type packageExtracter struct { + f *ast.File + x *extracter + info *loader.PackageInfo + cmap ast.CommentMap +} + +func (px packageExtracter) getComment(n ast.Node) string { + cs := px.cmap.Filter(n).Comments() + if len(cs) > 0 { + return strings.TrimSpace(cs[0].Text()) + } + return "" +} + +func (x *extracter) extractMessages() { + prog := x.iprog + keys := make([]*types.Package, 0, len(x.iprog.AllPackages)) + for k := range x.iprog.AllPackages { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { return keys[i].Path() < keys[j].Path() }) + files := []packageExtracter{} + for _, k := range keys { + info := x.iprog.AllPackages[k] + for _, f := range info.Files { + // Associate comments with nodes. + px := packageExtracter{ + f, x, info, + ast.NewCommentMap(prog.Fset, f, f.Comments), + } + files = append(files, px) + } + } + for _, px := range files { + ast.Inspect(px.f, func(n ast.Node) bool { + switch v := n.(type) { + case *ast.CallExpr: + if d := x.funcs[v.Lparen]; d != nil { + d.expr = v + } + } + return true + }) + } + for _, px := range files { + ast.Inspect(px.f, func(n ast.Node) bool { + switch v := n.(type) { + case *ast.CallExpr: + return px.handleCall(v) + case *ast.ValueSpec: + return px.handleGlobal(v) + } + return true + }) + } +} + +func (px packageExtracter) handleGlobal(spec *ast.ValueSpec) bool { + comment := px.getComment(spec) + + for _, ident := range spec.Names { + data, ok := px.x.globals[ident.Pos()] + if !ok { + continue + } + name := ident.Name + var arguments []argument + if data.call != nil { + arguments = px.getArguments(data.call) + } else if !strings.HasPrefix(name, "msg") && !strings.HasPrefix(name, "Msg") { + continue + } + data.visit(px.x, func(c constant.Value) { + px.addMessage(spec.Pos(), []string{name}, c, comment, arguments) + }) + } + + return true +} + +func (px packageExtracter) handleCall(call *ast.CallExpr) bool { + x := px.x + data := x.funcs[call.Lparen] + if data == nil || len(data.formats) == 0 { + return true + } + if data.expr != call { + panic("invariant `data.call != call` failed") + } + x.debug(data.call, "INSERT", data.formats) + + argn := data.callFormatPos() + if argn >= len(call.Args) { + return true + } + format := call.Args[argn] + + arguments := px.getArguments(data) + + comment := "" + key := []string{} + if ident, ok := format.(*ast.Ident); ok { + key = append(key, ident.Name) + if v, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && v.Comment != nil { + // TODO: get comment above ValueSpec as well + comment = v.Comment.Text() + } + } + if c := px.getComment(call.Args[0]); c != "" { + comment = c + } + + formats := data.formats + for _, c := range formats { + px.addMessage(call.Lparen, key, c, comment, arguments) + } + return true +} + +func (px packageExtracter) getArguments(data *callData) []argument { + arguments := []argument{} + x := px.x + info := px.info + if data.callArgsStart() >= 0 { + args := data.expr.Args[data.callArgsStart():] + for i, arg := range args { + expr := x.print(arg) + val := "" + if v := info.Types[arg].Value; v != nil { + val = v.ExactString() + switch arg.(type) { + case *ast.BinaryExpr, *ast.UnaryExpr: + expr = val + } + } + arguments = append(arguments, argument{ + ArgNum: i + 1, + Type: info.Types[arg].Type.String(), + UnderlyingType: info.Types[arg].Type.Underlying().String(), + Expr: expr, + Value: val, + Comment: px.getComment(arg), + Position: posString(&x.conf, info.Pkg, arg.Pos()), + // TODO report whether it implements + // interfaces plural.Interface, + // gender.Interface. + }) + } + } + return arguments +} + +func (px packageExtracter) addMessage( + pos token.Pos, + key []string, + c constant.Value, + comment string, + arguments []argument) { + x := px.x + fmtMsg := constant.StringVal(c) + + ph := placeholders{index: map[string]string{}} + + trimmed, _, _ := trimWS(fmtMsg) + + p := fmtparser.Parser{} + simArgs := make([]interface{}, len(arguments)) + for i, v := range arguments { + simArgs[i] = v + } + msg := "" + p.Reset(simArgs) + for p.SetFormat(trimmed); p.Scan(); { + name := "" + var arg *argument + switch p.Status { + case fmtparser.StatusText: + msg += p.Text() + continue + case fmtparser.StatusSubstitution, + fmtparser.StatusBadWidthSubstitution, + fmtparser.StatusBadPrecSubstitution: + arguments[p.ArgNum-1].used = true + arg = &arguments[p.ArgNum-1] + name = getID(arg) + case fmtparser.StatusBadArgNum, fmtparser.StatusMissingArg: + arg = &argument{ + ArgNum: p.ArgNum, + Position: posString(&x.conf, px.info.Pkg, pos), + } + name, arg.UnderlyingType = verbToPlaceholder(p.Text(), p.ArgNum) + } + sub := p.Text() + if !p.HasIndex { + r, sz := utf8.DecodeLastRuneInString(sub) + sub = fmt.Sprintf("%s[%d]%c", sub[:len(sub)-sz], p.ArgNum, r) + } + msg += fmt.Sprintf("{%s}", ph.addArg(arg, name, sub)) + } + key = append(key, msg) + + // Add additional Placeholders that can be used in translations + // that are not present in the string. + for _, arg := range arguments { + if arg.used { + continue + } + ph.addArg(&arg, getID(&arg), fmt.Sprintf("%%[%d]v", arg.ArgNum)) + } + + x.messages = append(x.messages, Message{ + ID: key, + Key: fmtMsg, + Message: Text{Msg: msg}, + // TODO(fix): this doesn't get the before comment. + Comment: comment, + Placeholders: ph.slice, + Position: posString(&x.conf, px.info.Pkg, pos), + }) +} + +func posString(conf *loader.Config, pkg *types.Package, pos token.Pos) string { + p := conf.Fset.Position(pos) + file := fmt.Sprintf("%s:%d:%d", filepath.Base(p.Filename), p.Line, p.Column) + return filepath.Join(pkg.Path(), file) +} + +func getID(arg *argument) string { + s := getLastComponent(arg.Expr) + s = strip(s) + s = strings.Replace(s, " ", "", -1) + // For small variable names, use user-defined types for more info. + if len(s) <= 2 && arg.UnderlyingType != arg.Type { + s = getLastComponent(arg.Type) + } + return strings.Title(s) +} + +// strip is a dirty hack to convert function calls to placeholder IDs. +func strip(s string) string { + s = strings.Map(func(r rune) rune { + if unicode.IsSpace(r) || r == '-' { + return '_' + } + if !unicode.In(r, unicode.Letter, unicode.Mark, unicode.Number) { + return -1 + } + return r + }, s) + // Strip "Get" from getter functions. + if strings.HasPrefix(s, "Get") || strings.HasPrefix(s, "get") { + if len(s) > len("get") { + r, _ := utf8.DecodeRuneInString(s) + if !unicode.In(r, unicode.Ll, unicode.M) { // not lower or mark + s = s[len("get"):] + } + } + } + return s +} + +// verbToPlaceholder gives a name for a placeholder based on the substitution +// verb. This is only to be used if there is otherwise no other type information +// available. +func verbToPlaceholder(sub string, pos int) (name, underlying string) { + r, _ := utf8.DecodeLastRuneInString(sub) + name = fmt.Sprintf("Arg_%d", pos) + switch r { + case 's', 'q': + underlying = "string" + case 'd': + name = "Integer" + underlying = "int" + case 'e', 'f', 'g': + name = "Number" + underlying = "float64" + case 'm': + name = "Message" + underlying = "string" + default: + underlying = "interface{}" + } + return name, underlying +} + +type placeholders struct { + index map[string]string + slice []Placeholder +} + +func (p *placeholders) addArg(arg *argument, name, sub string) (id string) { + id = name + alt, ok := p.index[id] + for i := 1; ok && alt != sub; i++ { + id = fmt.Sprintf("%s_%d", name, i) + alt, ok = p.index[id] + } + p.index[id] = sub + p.slice = append(p.slice, Placeholder{ + ID: id, + String: sub, + Type: arg.Type, + UnderlyingType: arg.UnderlyingType, + ArgNum: arg.ArgNum, + Expr: arg.Expr, + Comment: arg.Comment, + }) + return id +} + +func getLastComponent(s string) string { + return s[1+strings.LastIndexByte(s, '.'):] +} + +// isMsg returns whether s should be translated. +func isMsg(s string) bool { + // TODO: parse as format string and omit strings that contain letters + // coming from format verbs. + for _, r := range s { + if unicode.In(r, unicode.L) { + return true + } + } + return false +} diff --git a/vendor/golang.org/x/text/message/pipeline/generate.go b/vendor/golang.org/x/text/message/pipeline/generate.go new file mode 100644 index 0000000..f747c37 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/generate.go @@ -0,0 +1,329 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pipeline + +import ( + "fmt" + "go/build" + "io" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "text/template" + + "golang.org/x/text/collate" + "golang.org/x/text/feature/plural" + "golang.org/x/text/internal" + "golang.org/x/text/internal/catmsg" + "golang.org/x/text/internal/gen" + "golang.org/x/text/language" + "golang.org/x/tools/go/loader" +) + +var transRe = regexp.MustCompile(`messages\.(.*)\.json`) + +// Generate writes a Go file that defines a Catalog with translated messages. +// Translations are retrieved from s.Messages, not s.Translations, so it +// is assumed Merge has been called. +func (s *State) Generate() error { + path := s.Config.GenPackage + if path == "" { + path = "." + } + isDir := path[0] == '.' + prog, err := loadPackages(&loader.Config{}, []string{path}) + if err != nil { + return wrap(err, "could not load package") + } + pkgs := prog.InitialPackages() + if len(pkgs) != 1 { + return errorf("more than one package selected: %v", pkgs) + } + pkg := pkgs[0].Pkg.Name() + + cw, err := s.generate() + if err != nil { + return err + } + if !isDir { + gopath := filepath.SplitList(build.Default.GOPATH)[0] + path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].Pkg.Path())) + } + if len(s.Config.GenFile) == 0 { + cw.WriteGo(os.Stdout, pkg, "") + return nil + } + if filepath.IsAbs(s.Config.GenFile) { + path = s.Config.GenFile + } else { + path = filepath.Join(path, s.Config.GenFile) + } + cw.WriteGoFile(path, pkg) // TODO: WriteGoFile should return error. + return err +} + +// WriteGen writes a Go file with the given package name to w that defines a +// Catalog with translated messages. Translations are retrieved from s.Messages, +// not s.Translations, so it is assumed Merge has been called. +func (s *State) WriteGen(w io.Writer, pkg string) error { + cw, err := s.generate() + if err != nil { + return err + } + _, err = cw.WriteGo(w, pkg, "") + return err +} + +// Generate is deprecated; use (*State).Generate(). +func Generate(w io.Writer, pkg string, extracted *Messages, trans ...Messages) (n int, err error) { + s := State{ + Extracted: *extracted, + Translations: trans, + } + cw, err := s.generate() + if err != nil { + return 0, err + } + return cw.WriteGo(w, pkg, "") +} + +func (s *State) generate() (*gen.CodeWriter, error) { + // Build up index of translations and original messages. + translations := map[language.Tag]map[string]Message{} + languages := []language.Tag{} + usedKeys := map[string]int{} + + for _, loc := range s.Messages { + tag := loc.Language + if _, ok := translations[tag]; !ok { + translations[tag] = map[string]Message{} + languages = append(languages, tag) + } + for _, m := range loc.Messages { + if !m.Translation.IsEmpty() { + for _, id := range m.ID { + if _, ok := translations[tag][id]; ok { + warnf("Duplicate translation in locale %q for message %q", tag, id) + } + translations[tag][id] = m + } + } + } + } + + // Verify completeness and register keys. + internal.SortTags(languages) + + langVars := []string{} + for _, tag := range languages { + langVars = append(langVars, strings.Replace(tag.String(), "-", "_", -1)) + dict := translations[tag] + for _, msg := range s.Extracted.Messages { + for _, id := range msg.ID { + if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() { + if _, ok := usedKeys[msg.Key]; !ok { + usedKeys[msg.Key] = len(usedKeys) + } + break + } + // TODO: log missing entry. + warnf("%s: Missing entry for %q.", tag, id) + } + } + } + + cw := gen.NewCodeWriter() + + x := &struct { + Fallback language.Tag + Languages []string + }{ + Fallback: s.Extracted.Language, + Languages: langVars, + } + + if err := lookup.Execute(cw, x); err != nil { + return nil, wrap(err, "error") + } + + keyToIndex := []string{} + for k := range usedKeys { + keyToIndex = append(keyToIndex, k) + } + sort.Strings(keyToIndex) + fmt.Fprint(cw, "var messageKeyToIndex = map[string]int{\n") + for _, k := range keyToIndex { + fmt.Fprintf(cw, "%q: %d,\n", k, usedKeys[k]) + } + fmt.Fprint(cw, "}\n\n") + + for i, tag := range languages { + dict := translations[tag] + a := make([]string, len(usedKeys)) + for _, msg := range s.Extracted.Messages { + for _, id := range msg.ID { + if trans, ok := dict[id]; ok && !trans.Translation.IsEmpty() { + m, err := assemble(&msg, &trans.Translation) + if err != nil { + return nil, wrap(err, "error") + } + _, leadWS, trailWS := trimWS(msg.Key) + if leadWS != "" || trailWS != "" { + m = catmsg.Affix{ + Message: m, + Prefix: leadWS, + Suffix: trailWS, + } + } + // TODO: support macros. + data, err := catmsg.Compile(tag, nil, m) + if err != nil { + return nil, wrap(err, "error") + } + key := usedKeys[msg.Key] + if d := a[key]; d != "" && d != data { + warnf("Duplicate non-consistent translation for key %q, picking the one for message %q", msg.Key, id) + } + a[key] = string(data) + break + } + } + } + index := []uint32{0} + p := 0 + for _, s := range a { + p += len(s) + index = append(index, uint32(p)) + } + + cw.WriteVar(langVars[i]+"Index", index) + cw.WriteConst(langVars[i]+"Data", strings.Join(a, "")) + } + return cw, nil +} + +func assemble(m *Message, t *Text) (msg catmsg.Message, err error) { + keys := []string{} + for k := range t.Var { + keys = append(keys, k) + } + sort.Strings(keys) + var a []catmsg.Message + for _, k := range keys { + t := t.Var[k] + m, err := assemble(m, &t) + if err != nil { + return nil, err + } + a = append(a, &catmsg.Var{Name: k, Message: m}) + } + if t.Select != nil { + s, err := assembleSelect(m, t.Select) + if err != nil { + return nil, err + } + a = append(a, s) + } + if t.Msg != "" { + sub, err := m.Substitute(t.Msg) + if err != nil { + return nil, err + } + a = append(a, catmsg.String(sub)) + } + switch len(a) { + case 0: + return nil, errorf("generate: empty message") + case 1: + return a[0], nil + default: + return catmsg.FirstOf(a), nil + + } +} + +func assembleSelect(m *Message, s *Select) (msg catmsg.Message, err error) { + cases := []string{} + for c := range s.Cases { + cases = append(cases, c) + } + sortCases(cases) + + caseMsg := []interface{}{} + for _, c := range cases { + cm := s.Cases[c] + m, err := assemble(m, &cm) + if err != nil { + return nil, err + } + caseMsg = append(caseMsg, c, m) + } + + ph := m.Placeholder(s.Arg) + + switch s.Feature { + case "plural": + // TODO: only printf-style selects are supported as of yet. + return plural.Selectf(ph.ArgNum, ph.String, caseMsg...), nil + } + return nil, errorf("unknown feature type %q", s.Feature) +} + +func sortCases(cases []string) { + // TODO: implement full interface. + sort.Slice(cases, func(i, j int) bool { + switch { + case cases[i] != "other" && cases[j] == "other": + return true + case cases[i] == "other" && cases[j] != "other": + return false + } + // the following code relies on '<' < '=' < any letter. + return cmpNumeric(cases[i], cases[j]) == -1 + }) +} + +var cmpNumeric = collate.New(language.Und, collate.Numeric).CompareString + +var lookup = template.Must(template.New("gen").Parse(` +import ( + "golang.org/x/text/language" + "golang.org/x/text/message" + "golang.org/x/text/message/catalog" +) + +type dictionary struct { + index []uint32 + data string +} + +func (d *dictionary) Lookup(key string) (data string, ok bool) { + p, ok := messageKeyToIndex[key] + if !ok { + return "", false + } + start, end := d.index[p], d.index[p+1] + if start == end { + return "", false + } + return d.data[start:end], true +} + +func init() { + dict := map[string]catalog.Dictionary{ + {{range .Languages}}"{{.}}": &dictionary{index: {{.}}Index, data: {{.}}Data }, + {{end}} + } + fallback := language.MustParse("{{.Fallback}}") + cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback)) + if err != nil { + panic(err) + } + message.DefaultCatalog = cat +} + +`)) diff --git a/vendor/golang.org/x/text/message/pipeline/message.go b/vendor/golang.org/x/text/message/pipeline/message.go new file mode 100644 index 0000000..c83a8fd --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/message.go @@ -0,0 +1,241 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pipeline + +import ( + "encoding/json" + "errors" + "strings" + + "golang.org/x/text/language" +) + +// TODO: these definitions should be moved to a package so that the can be used +// by other tools. + +// The file contains the structures used to define translations of a certain +// messages. +// +// A translation may have multiple translations strings, or messages, depending +// on the feature values of the various arguments. For instance, consider +// a hypothetical translation from English to English, where the source defines +// the format string "%d file(s) remaining". +// See the examples directory for examples of extracted messages. + +// Messages is used to store translations for a single language. +type Messages struct { + Language language.Tag `json:"language"` + Messages []Message `json:"messages"` + Macros map[string]Text `json:"macros,omitempty"` +} + +// A Message describes a message to be translated. +type Message struct { + // ID contains a list of identifiers for the message. + ID IDList `json:"id"` + // Key is the string that is used to look up the message at runtime. + Key string `json:"key,omitempty"` + Meaning string `json:"meaning,omitempty"` + Message Text `json:"message"` + Translation Text `json:"translation"` + + Comment string `json:"comment,omitempty"` + TranslatorComment string `json:"translatorComment,omitempty"` + + Placeholders []Placeholder `json:"placeholders,omitempty"` + + // Fuzzy indicates that the provide translation needs review by a + // translator, for instance because it was derived from automated + // translation. + Fuzzy bool `json:"fuzzy,omitempty"` + + // TODO: default placeholder syntax is {foo}. Allow alternative escaping + // like `foo`. + + // Extraction information. + Position string `json:"position,omitempty"` // filePosition:line +} + +// Placeholder reports the placeholder for the given ID if it is defined or nil +// otherwise. +func (m *Message) Placeholder(id string) *Placeholder { + for _, p := range m.Placeholders { + if p.ID == id { + return &p + } + } + return nil +} + +// Substitute replaces placeholders in msg with their original value. +func (m *Message) Substitute(msg string) (sub string, err error) { + last := 0 + for i := 0; i < len(msg); { + pLeft := strings.IndexByte(msg[i:], '{') + if pLeft == -1 { + break + } + pLeft += i + pRight := strings.IndexByte(msg[pLeft:], '}') + if pRight == -1 { + return "", errorf("unmatched '}'") + } + pRight += pLeft + id := strings.TrimSpace(msg[pLeft+1 : pRight]) + i = pRight + 1 + if id != "" && id[0] == '$' { + continue + } + sub += msg[last:pLeft] + last = i + ph := m.Placeholder(id) + if ph == nil { + return "", errorf("unknown placeholder %q in message %q", id, msg) + } + sub += ph.String + } + sub += msg[last:] + return sub, err +} + +var errIncompatibleMessage = errors.New("messages incompatible") + +func checkEquivalence(a, b *Message) error { + for _, v := range a.ID { + for _, w := range b.ID { + if v == w { + return nil + } + } + } + // TODO: canonicalize placeholders and check for type equivalence. + return errIncompatibleMessage +} + +// A Placeholder is a part of the message that should not be changed by a +// translator. It can be used to hide or prettify format strings (e.g. %d or +// {{.Count}}), hide HTML, or mark common names that should not be translated. +type Placeholder struct { + // ID is the placeholder identifier without the curly braces. + ID string `json:"id"` + + // String is the string with which to replace the placeholder. This may be a + // formatting string (for instance "%d" or "{{.Count}}") or a literal string + // (

). + String string `json:"string"` + + Type string `json:"type"` + UnderlyingType string `json:"underlyingType"` + // ArgNum and Expr are set if the placeholder is a substitution of an + // argument. + ArgNum int `json:"argNum,omitempty"` + Expr string `json:"expr,omitempty"` + + Comment string `json:"comment,omitempty"` + Example string `json:"example,omitempty"` + + // Features contains the features that are available for the implementation + // of this argument. + Features []Feature `json:"features,omitempty"` +} + +// An argument contains information about the arguments passed to a message. +type argument struct { + // ArgNum corresponds to the number that should be used for explicit argument indexes (e.g. + // "%[1]d"). + ArgNum int `json:"argNum,omitempty"` + + used bool // Used by Placeholder + Type string `json:"type"` + UnderlyingType string `json:"underlyingType"` + Expr string `json:"expr"` + Value string `json:"value,omitempty"` + Comment string `json:"comment,omitempty"` + Position string `json:"position,omitempty"` +} + +// Feature holds information about a feature that can be implemented by +// an Argument. +type Feature struct { + Type string `json:"type"` // Right now this is only gender and plural. + + // TODO: possible values and examples for the language under consideration. + +} + +// Text defines a message to be displayed. +type Text struct { + // Msg and Select contains the message to be displayed. Msg may be used as + // a fallback value if none of the select cases match. + Msg string `json:"msg,omitempty"` + Select *Select `json:"select,omitempty"` + + // Var defines a map of variables that may be substituted in the selected + // message. + Var map[string]Text `json:"var,omitempty"` + + // Example contains an example message formatted with default values. + Example string `json:"example,omitempty"` +} + +// IsEmpty reports whether this Text can generate anything. +func (t *Text) IsEmpty() bool { + return t.Msg == "" && t.Select == nil && t.Var == nil +} + +// rawText erases the UnmarshalJSON method. +type rawText Text + +// UnmarshalJSON implements json.Unmarshaler. +func (t *Text) UnmarshalJSON(b []byte) error { + if b[0] == '"' { + return json.Unmarshal(b, &t.Msg) + } + return json.Unmarshal(b, (*rawText)(t)) +} + +// MarshalJSON implements json.Marshaler. +func (t *Text) MarshalJSON() ([]byte, error) { + if t.Select == nil && t.Var == nil && t.Example == "" { + return json.Marshal(t.Msg) + } + return json.Marshal((*rawText)(t)) +} + +// IDList is a set identifiers that each may refer to possibly different +// versions of the same message. When looking up a messages, the first +// identifier in the list takes precedence. +type IDList []string + +// UnmarshalJSON implements json.Unmarshaler. +func (id *IDList) UnmarshalJSON(b []byte) error { + if b[0] == '"' { + *id = []string{""} + return json.Unmarshal(b, &((*id)[0])) + } + return json.Unmarshal(b, (*[]string)(id)) +} + +// MarshalJSON implements json.Marshaler. +func (id *IDList) MarshalJSON() ([]byte, error) { + if len(*id) == 1 { + return json.Marshal((*id)[0]) + } + return json.Marshal((*[]string)(id)) +} + +// Select selects a Text based on the feature value associated with a feature of +// a certain argument. +type Select struct { + Feature string `json:"feature"` // Name of Feature type (e.g plural) + Arg string `json:"arg"` // The placeholder ID + Cases map[string]Text `json:"cases"` +} + +// TODO: order matters, but can we derive the ordering from the case keys? +// type Case struct { +// Key string `json:"key"` +// Value Text `json:"value"` +// } diff --git a/vendor/golang.org/x/text/message/pipeline/pipeline.go b/vendor/golang.org/x/text/message/pipeline/pipeline.go new file mode 100644 index 0000000..34f15f8 --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/pipeline.go @@ -0,0 +1,422 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package pipeline provides tools for creating translation pipelines. +// +// NOTE: UNDER DEVELOPMENT. API MAY CHANGE. +package pipeline + +import ( + "bytes" + "encoding/json" + "fmt" + "go/build" + "go/parser" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + "unicode" + + "golang.org/x/text/internal" + "golang.org/x/text/language" + "golang.org/x/text/runes" + "golang.org/x/tools/go/loader" +) + +const ( + extractFile = "extracted.gotext.json" + outFile = "out.gotext.json" + gotextSuffix = "gotext.json" +) + +// Config contains configuration for the translation pipeline. +type Config struct { + // Supported indicates the languages for which data should be generated. + // The default is to support all locales for which there are matching + // translation files. + Supported []language.Tag + + // --- Extraction + + SourceLanguage language.Tag + + Packages []string + + // --- File structure + + // Dir is the root dir for all operations. + Dir string + + // TranslationsPattern is a regular expression to match incoming translation + // files. These files may appear in any directory rooted at Dir. + // language for the translation files is determined as follows: + // 1. From the Language field in the file. + // 2. If not present, from a valid language tag in the filename, separated + // by dots (e.g. "en-US.json" or "incoming.pt_PT.xmb"). + // 3. If not present, from a the closest subdirectory in which the file + // is contained that parses as a valid language tag. + TranslationsPattern string + + // OutPattern defines the location for translation files for a certain + // language. The default is "{{.Dir}}/{{.Language}}/out.{{.Ext}}" + OutPattern string + + // Format defines the file format for generated translation files. + // The default is XMB. Alternatives are GetText, XLIFF, L20n, GoText. + Format string + + Ext string + + // TODO: + // Actions are additional actions to be performed after the initial extract + // and merge. + // Actions []struct { + // Name string + // Options map[string]string + // } + + // --- Generation + + // GenFile may be in a different package. It is not defined, it will + // be written to stdout. + GenFile string + + // GenPackage is the package or relative path into which to generate the + // file. If not specified it is relative to the current directory. + GenPackage string + + // DeclareVar defines a variable to which to assign the generated Catalog. + DeclareVar string + + // SetDefault determines whether to assign the generated Catalog to + // message.DefaultCatalog. The default for this is true if DeclareVar is + // not defined, false otherwise. + SetDefault bool + + // TODO: + // - Printf-style configuration + // - Template-style configuration + // - Extraction options + // - Rewrite options + // - Generation options +} + +// Operations: +// - extract: get the strings +// - disambiguate: find messages with the same key, but possible different meaning. +// - create out: create a list of messages that need translations +// - load trans: load the list of current translations +// - merge: assign list of translations as done +// - (action)expand: analyze features and create example sentences for each version. +// - (action)googletrans: pre-populate messages with automatic translations. +// - (action)export: send out messages somewhere non-standard +// - (action)import: load messages from somewhere non-standard +// - vet program: don't pass "foo" + var + "bar" strings. Not using funcs for translated strings. +// - vet trans: coverage: all translations/ all features. +// - generate: generate Go code + +// State holds all accumulated information on translations during processing. +type State struct { + Config Config + + Package string + program *loader.Program + + Extracted Messages `json:"messages"` + + // Messages includes all messages for which there need to be translations. + // Duplicates may be eliminated. Generation will be done from these messages + // (usually after merging). + Messages []Messages + + // Translations are incoming translations for the application messages. + Translations []Messages +} + +func (s *State) dir() string { + if d := s.Config.Dir; d != "" { + return d + } + return "./locales" +} + +func outPattern(s *State) (string, error) { + c := s.Config + pat := c.OutPattern + if pat == "" { + pat = "{{.Dir}}/{{.Language}}/out.{{.Ext}}" + } + + ext := c.Ext + if ext == "" { + ext = c.Format + } + if ext == "" { + ext = gotextSuffix + } + t, err := template.New("").Parse(pat) + if err != nil { + return "", wrap(err, "error parsing template") + } + buf := bytes.Buffer{} + err = t.Execute(&buf, map[string]string{ + "Dir": s.dir(), + "Language": "%s", + "Ext": ext, + }) + return filepath.FromSlash(buf.String()), wrap(err, "incorrect OutPattern") +} + +var transRE = regexp.MustCompile(`.*\.` + gotextSuffix) + +// Import loads existing translation files. +func (s *State) Import() error { + outPattern, err := outPattern(s) + if err != nil { + return err + } + re := transRE + if pat := s.Config.TranslationsPattern; pat != "" { + if re, err = regexp.Compile(pat); err != nil { + return wrapf(err, "error parsing regexp %q", s.Config.TranslationsPattern) + } + } + x := importer{s, outPattern, re} + return x.walkImport(s.dir(), s.Config.SourceLanguage) +} + +type importer struct { + state *State + outPattern string + transFile *regexp.Regexp +} + +func (i *importer) walkImport(path string, tag language.Tag) error { + files, err := ioutil.ReadDir(path) + if err != nil { + return nil + } + for _, f := range files { + name := f.Name() + tag := tag + if f.IsDir() { + if t, err := language.Parse(name); err == nil { + tag = t + } + // We ignore errors + if err := i.walkImport(filepath.Join(path, name), tag); err != nil { + return err + } + continue + } + for _, l := range strings.Split(name, ".") { + if t, err := language.Parse(l); err == nil { + tag = t + } + } + file := filepath.Join(path, name) + // TODO: Should we skip files that match output files? + if fmt.Sprintf(i.outPattern, tag) == file { + continue + } + // TODO: handle different file formats. + if !i.transFile.MatchString(name) { + continue + } + b, err := ioutil.ReadFile(file) + if err != nil { + return wrap(err, "read file failed") + } + var translations Messages + if err := json.Unmarshal(b, &translations); err != nil { + return wrap(err, "parsing translation file failed") + } + i.state.Translations = append(i.state.Translations, translations) + } + return nil +} + +// Merge merges the extracted messages with the existing translations. +func (s *State) Merge() error { + if s.Messages != nil { + panic("already merged") + } + // Create an index for each unique message. + // Duplicates are okay as long as the substitution arguments are okay as + // well. + // Top-level messages are okay to appear in multiple substitution points. + + // Collect key equivalence. + msgs := []*Message{} + keyToIDs := map[string]*Message{} + for _, m := range s.Extracted.Messages { + m := m + if prev, ok := keyToIDs[m.Key]; ok { + if err := checkEquivalence(&m, prev); err != nil { + warnf("Key %q matches conflicting messages: %v and %v", m.Key, prev.ID, m.ID) + // TODO: track enough information so that the rewriter can + // suggest/disambiguate messages. + } + // TODO: add position to message. + continue + } + i := len(msgs) + msgs = append(msgs, &m) + keyToIDs[m.Key] = msgs[i] + } + + // Messages with different keys may still refer to the same translated + // message (e.g. different whitespace). Filter these. + idMap := map[string]bool{} + filtered := []*Message{} + for _, m := range msgs { + found := false + for _, id := range m.ID { + found = found || idMap[id] + } + if !found { + filtered = append(filtered, m) + } + for _, id := range m.ID { + idMap[id] = true + } + } + + // Build index of translations. + translations := map[language.Tag]map[string]Message{} + languages := append([]language.Tag{}, s.Config.Supported...) + + for _, t := range s.Translations { + tag := t.Language + if _, ok := translations[tag]; !ok { + translations[tag] = map[string]Message{} + languages = append(languages, tag) + } + for _, m := range t.Messages { + if !m.Translation.IsEmpty() { + for _, id := range m.ID { + if _, ok := translations[tag][id]; ok { + warnf("Duplicate translation in locale %q for message %q", tag, id) + } + translations[tag][id] = m + } + } + } + } + languages = internal.UniqueTags(languages) + + for _, tag := range languages { + ms := Messages{Language: tag} + for _, orig := range filtered { + m := *orig + m.Key = "" + m.Position = "" + + for _, id := range m.ID { + if t, ok := translations[tag][id]; ok { + m.Translation = t.Translation + if t.TranslatorComment != "" { + m.TranslatorComment = t.TranslatorComment + m.Fuzzy = t.Fuzzy + } + break + } + } + if tag == s.Config.SourceLanguage && m.Translation.IsEmpty() { + m.Translation = m.Message + if m.TranslatorComment == "" { + m.TranslatorComment = "Copied from source." + m.Fuzzy = true + } + } + // TODO: if translation is empty: pre-expand based on available + // linguistic features. This may also be done as a plugin. + ms.Messages = append(ms.Messages, m) + } + s.Messages = append(s.Messages, ms) + } + return nil +} + +// Export writes out the messages to translation out files. +func (s *State) Export() error { + path, err := outPattern(s) + if err != nil { + return wrap(err, "export failed") + } + for _, out := range s.Messages { + // TODO: inject translations from existing files to avoid retranslation. + data, err := json.MarshalIndent(out, "", " ") + if err != nil { + return wrap(err, "JSON marshal failed") + } + file := fmt.Sprintf(path, out.Language) + if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil { + return wrap(err, "dir create failed") + } + if err := ioutil.WriteFile(file, data, 0644); err != nil { + return wrap(err, "write failed") + } + } + return nil +} + +var ( + ws = runes.In(unicode.White_Space).Contains + notWS = runes.NotIn(unicode.White_Space).Contains +) + +func trimWS(s string) (trimmed, leadWS, trailWS string) { + trimmed = strings.TrimRightFunc(s, ws) + trailWS = s[len(trimmed):] + if i := strings.IndexFunc(trimmed, notWS); i > 0 { + leadWS = trimmed[:i] + trimmed = trimmed[i:] + } + return trimmed, leadWS, trailWS +} + +// NOTE: The command line tool already prefixes with "gotext:". +var ( + wrap = func(err error, msg string) error { + if err == nil { + return nil + } + return fmt.Errorf("%s: %v", msg, err) + } + wrapf = func(err error, msg string, args ...interface{}) error { + if err == nil { + return nil + } + return wrap(err, fmt.Sprintf(msg, args...)) + } + errorf = fmt.Errorf +) + +func warnf(format string, args ...interface{}) { + // TODO: don't log. + log.Printf(format, args...) +} + +func loadPackages(conf *loader.Config, args []string) (*loader.Program, error) { + if len(args) == 0 { + args = []string{"."} + } + + conf.Build = &build.Default + conf.ParserMode = parser.ParseComments + + // Use the initial packages from the command line. + args, err := conf.FromArgs(args, false) + if err != nil { + return nil, wrap(err, "loading packages failed") + } + + // Load, parse and type-check the whole program. + return conf.Load() +} diff --git a/vendor/golang.org/x/text/message/pipeline/rewrite.go b/vendor/golang.org/x/text/message/pipeline/rewrite.go new file mode 100644 index 0000000..cf1511f --- /dev/null +++ b/vendor/golang.org/x/text/message/pipeline/rewrite.go @@ -0,0 +1,268 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pipeline + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/format" + "go/token" + "io" + "os" + "strings" + + "golang.org/x/tools/go/loader" +) + +const printerType = "golang.org/x/text/message.Printer" + +// Rewrite rewrites the Go files in a single package to use the localization +// machinery and rewrites strings to adopt best practices when possible. +// If w is not nil the generated files are written to it, each files with a +// "--- " header. Otherwise the files are overwritten. +func Rewrite(w io.Writer, args ...string) error { + conf := &loader.Config{ + AllowErrors: true, // Allow unused instances of message.Printer. + } + prog, err := loadPackages(conf, args) + if err != nil { + return wrap(err, "") + } + + for _, info := range prog.InitialPackages() { + for _, f := range info.Files { + // Associate comments with nodes. + + // Pick up initialized Printers at the package level. + r := rewriter{info: info, conf: conf} + for _, n := range info.InitOrder { + if t := r.info.Types[n.Rhs].Type.String(); strings.HasSuffix(t, printerType) { + r.printerVar = n.Lhs[0].Name() + } + } + + ast.Walk(&r, f) + + w := w + if w == nil { + var err error + if w, err = os.Create(conf.Fset.File(f.Pos()).Name()); err != nil { + return wrap(err, "open failed") + } + } else { + fmt.Fprintln(w, "---", conf.Fset.File(f.Pos()).Name()) + } + + if err := format.Node(w, conf.Fset, f); err != nil { + return wrap(err, "go format failed") + } + } + } + + return nil +} + +type rewriter struct { + info *loader.PackageInfo + conf *loader.Config + printerVar string +} + +// print returns Go syntax for the specified node. +func (r *rewriter) print(n ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, r.conf.Fset, n) + return buf.String() +} + +func (r *rewriter) Visit(n ast.Node) ast.Visitor { + // Save the state by scope. + if _, ok := n.(*ast.BlockStmt); ok { + r := *r + return &r + } + // Find Printers created by assignment. + stmt, ok := n.(*ast.AssignStmt) + if ok { + for _, v := range stmt.Lhs { + if r.printerVar == r.print(v) { + r.printerVar = "" + } + } + for i, v := range stmt.Rhs { + if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) { + r.printerVar = r.print(stmt.Lhs[i]) + return r + } + } + } + // Find Printers created by variable declaration. + spec, ok := n.(*ast.ValueSpec) + if ok { + for _, v := range spec.Names { + if r.printerVar == r.print(v) { + r.printerVar = "" + } + } + for i, v := range spec.Values { + if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) { + r.printerVar = r.print(spec.Names[i]) + return r + } + } + } + if r.printerVar == "" { + return r + } + call, ok := n.(*ast.CallExpr) + if !ok { + return r + } + + // TODO: Handle literal values? + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return r + } + meth := r.info.Selections[sel] + + source := r.print(sel.X) + fun := r.print(sel.Sel) + if meth != nil { + source = meth.Recv().String() + fun = meth.Obj().Name() + } + + // TODO: remove cheap hack and check if the type either + // implements some interface or is specifically of type + // "golang.org/x/text/message".Printer. + m, ok := rewriteFuncs[source] + if !ok { + return r + } + + rewriteType, ok := m[fun] + if !ok { + return r + } + ident := ast.NewIdent(r.printerVar) + ident.NamePos = sel.X.Pos() + sel.X = ident + if rewriteType.method != "" { + sel.Sel.Name = rewriteType.method + } + + // Analyze arguments. + argn := rewriteType.arg + if rewriteType.format || argn >= len(call.Args) { + return r + } + hasConst := false + for _, a := range call.Args[argn:] { + if v := r.info.Types[a].Value; v != nil && v.Kind() == constant.String { + hasConst = true + break + } + } + if !hasConst { + return r + } + sel.Sel.Name = rewriteType.methodf + + // We are done if there is only a single string that does not need to be + // escaped. + if len(call.Args) == 1 { + s, ok := constStr(r.info, call.Args[0]) + if ok && !strings.Contains(s, "%") && !rewriteType.newLine { + return r + } + } + + // Rewrite arguments as format string. + expr := &ast.BasicLit{ + ValuePos: call.Lparen, + Kind: token.STRING, + } + newArgs := append(call.Args[:argn:argn], expr) + newStr := []string{} + for i, a := range call.Args[argn:] { + if s, ok := constStr(r.info, a); ok { + newStr = append(newStr, strings.Replace(s, "%", "%%", -1)) + } else { + newStr = append(newStr, "%v") + newArgs = append(newArgs, call.Args[argn+i]) + } + } + s := strings.Join(newStr, rewriteType.sep) + if rewriteType.newLine { + s += "\n" + } + expr.Value = fmt.Sprintf("%q", s) + + call.Args = newArgs + + // TODO: consider creating an expression instead of a constant string and + // then wrapping it in an escape function or so: + // call.Args[argn+i] = &ast.CallExpr{ + // Fun: &ast.SelectorExpr{ + // X: ast.NewIdent("message"), + // Sel: ast.NewIdent("Lookup"), + // }, + // Args: []ast.Expr{a}, + // } + // } + + return r +} + +type rewriteType struct { + // method is the name of the equivalent method on a printer, or "" if it is + // the same. + method string + + // methodf is the method to use if the arguments can be rewritten as a + // arguments to a printf-style call. + methodf string + + // format is true if the method takes a formatting string followed by + // substitution arguments. + format bool + + // arg indicates the position of the argument to extract. If all is + // positive, all arguments from this argument onwards needs to be extracted. + arg int + + sep string + newLine bool +} + +// rewriteFuncs list functions that can be directly mapped to the printer +// functions of the message package. +var rewriteFuncs = map[string]map[string]rewriteType{ + // TODO: Printer -> *golang.org/x/text/message.Printer + "fmt": { + "Print": rewriteType{methodf: "Printf"}, + "Sprint": rewriteType{methodf: "Sprintf"}, + "Fprint": rewriteType{methodf: "Fprintf"}, + + "Println": rewriteType{methodf: "Printf", sep: " ", newLine: true}, + "Sprintln": rewriteType{methodf: "Sprintf", sep: " ", newLine: true}, + "Fprintln": rewriteType{methodf: "Fprintf", sep: " ", newLine: true}, + + "Printf": rewriteType{method: "Printf", format: true}, + "Sprintf": rewriteType{method: "Sprintf", format: true}, + "Fprintf": rewriteType{method: "Fprintf", format: true}, + }, +} + +func constStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) { + v := info.Types[e].Value + if v == nil || v.Kind() != constant.String { + return "", false + } + return constant.StringVal(v), true +} diff --git a/vendor/golang.org/x/text/runes/cond.go b/vendor/golang.org/x/text/runes/cond.go new file mode 100644 index 0000000..df7aa02 --- /dev/null +++ b/vendor/golang.org/x/text/runes/cond.go @@ -0,0 +1,187 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runes + +import ( + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// Note: below we pass invalid UTF-8 to the tIn and tNotIn transformers as is. +// This is done for various reasons: +// - To retain the semantics of the Nop transformer: if input is passed to a Nop +// one would expect it to be unchanged. +// - It would be very expensive to pass a converted RuneError to a transformer: +// a transformer might need more source bytes after RuneError, meaning that +// the only way to pass it safely is to create a new buffer and manage the +// intermingling of RuneErrors and normal input. +// - Many transformers leave ill-formed UTF-8 as is, so this is not +// inconsistent. Generally ill-formed UTF-8 is only replaced if it is a +// logical consequence of the operation (as for Map) or if it otherwise would +// pose security concerns (as for Remove). +// - An alternative would be to return an error on ill-formed UTF-8, but this +// would be inconsistent with other operations. + +// If returns a transformer that applies tIn to consecutive runes for which +// s.Contains(r) and tNotIn to consecutive runes for which !s.Contains(r). Reset +// is called on tIn and tNotIn at the start of each run. A Nop transformer will +// substitute a nil value passed to tIn or tNotIn. Invalid UTF-8 is translated +// to RuneError to determine which transformer to apply, but is passed as is to +// the respective transformer. +func If(s Set, tIn, tNotIn transform.Transformer) Transformer { + if tIn == nil && tNotIn == nil { + return Transformer{transform.Nop} + } + if tIn == nil { + tIn = transform.Nop + } + if tNotIn == nil { + tNotIn = transform.Nop + } + sIn, ok := tIn.(transform.SpanningTransformer) + if !ok { + sIn = dummySpan{tIn} + } + sNotIn, ok := tNotIn.(transform.SpanningTransformer) + if !ok { + sNotIn = dummySpan{tNotIn} + } + + a := &cond{ + tIn: sIn, + tNotIn: sNotIn, + f: s.Contains, + } + a.Reset() + return Transformer{a} +} + +type dummySpan struct{ transform.Transformer } + +func (d dummySpan) Span(src []byte, atEOF bool) (n int, err error) { + return 0, transform.ErrEndOfSpan +} + +type cond struct { + tIn, tNotIn transform.SpanningTransformer + f func(rune) bool + check func(rune) bool // current check to perform + t transform.SpanningTransformer // current transformer to use +} + +// Reset implements transform.Transformer. +func (t *cond) Reset() { + t.check = t.is + t.t = t.tIn + t.t.Reset() // notIn will be reset on first usage. +} + +func (t *cond) is(r rune) bool { + if t.f(r) { + return true + } + t.check = t.isNot + t.t = t.tNotIn + t.tNotIn.Reset() + return false +} + +func (t *cond) isNot(r rune) bool { + if !t.f(r) { + return true + } + t.check = t.is + t.t = t.tIn + t.tIn.Reset() + return false +} + +// This implementation of Span doesn't help all too much, but it needs to be +// there to satisfy this package's Transformer interface. +// TODO: there are certainly room for improvements, though. For example, if +// t.t == transform.Nop (which will a common occurrence) it will save a bundle +// to special-case that loop. +func (t *cond) Span(src []byte, atEOF bool) (n int, err error) { + p := 0 + for n < len(src) && err == nil { + // Don't process too much at a time as the Spanner that will be + // called on this block may terminate early. + const maxChunk = 4096 + max := len(src) + if v := n + maxChunk; v < max { + max = v + } + atEnd := false + size := 0 + current := t.t + for ; p < max; p += size { + r := rune(src[p]) + if r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[p:]); size == 1 { + if !atEOF && !utf8.FullRune(src[p:]) { + err = transform.ErrShortSrc + break + } + } + if !t.check(r) { + // The next rune will be the start of a new run. + atEnd = true + break + } + } + n2, err2 := current.Span(src[n:p], atEnd || (atEOF && p == len(src))) + n += n2 + if err2 != nil { + return n, err2 + } + // At this point either err != nil or t.check will pass for the rune at p. + p = n + size + } + return n, err +} + +func (t *cond) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + p := 0 + for nSrc < len(src) && err == nil { + // Don't process too much at a time, as the work might be wasted if the + // destination buffer isn't large enough to hold the result or a + // transform returns an error early. + const maxChunk = 4096 + max := len(src) + if n := nSrc + maxChunk; n < len(src) { + max = n + } + atEnd := false + size := 0 + current := t.t + for ; p < max; p += size { + r := rune(src[p]) + if r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[p:]); size == 1 { + if !atEOF && !utf8.FullRune(src[p:]) { + err = transform.ErrShortSrc + break + } + } + if !t.check(r) { + // The next rune will be the start of a new run. + atEnd = true + break + } + } + nDst2, nSrc2, err2 := current.Transform(dst[nDst:], src[nSrc:p], atEnd || (atEOF && p == len(src))) + nDst += nDst2 + nSrc += nSrc2 + if err2 != nil { + return nDst, nSrc, err2 + } + // At this point either err != nil or t.check will pass for the rune at p. + p = nSrc + size + } + return nDst, nSrc, err +} diff --git a/vendor/golang.org/x/text/runes/runes.go b/vendor/golang.org/x/text/runes/runes.go new file mode 100644 index 0000000..930e87f --- /dev/null +++ b/vendor/golang.org/x/text/runes/runes.go @@ -0,0 +1,355 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package runes provide transforms for UTF-8 encoded text. +package runes // import "golang.org/x/text/runes" + +import ( + "unicode" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// A Set is a collection of runes. +type Set interface { + // Contains returns true if r is contained in the set. + Contains(r rune) bool +} + +type setFunc func(rune) bool + +func (s setFunc) Contains(r rune) bool { + return s(r) +} + +// Note: using funcs here instead of wrapping types result in cleaner +// documentation and a smaller API. + +// In creates a Set with a Contains method that returns true for all runes in +// the given RangeTable. +func In(rt *unicode.RangeTable) Set { + return setFunc(func(r rune) bool { return unicode.Is(rt, r) }) +} + +// NotIn creates a Set with a Contains method that returns true for all runes not +// in the given RangeTable. +func NotIn(rt *unicode.RangeTable) Set { + return setFunc(func(r rune) bool { return !unicode.Is(rt, r) }) +} + +// Predicate creates a Set with a Contains method that returns f(r). +func Predicate(f func(rune) bool) Set { + return setFunc(f) +} + +// Transformer implements the transform.Transformer interface. +type Transformer struct { + t transform.SpanningTransformer +} + +func (t Transformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + return t.t.Transform(dst, src, atEOF) +} + +func (t Transformer) Span(b []byte, atEOF bool) (n int, err error) { + return t.t.Span(b, atEOF) +} + +func (t Transformer) Reset() { t.t.Reset() } + +// Bytes returns a new byte slice with the result of converting b using t. It +// calls Reset on t. It returns nil if any error was found. This can only happen +// if an error-producing Transformer is passed to If. +func (t Transformer) Bytes(b []byte) []byte { + b, _, err := transform.Bytes(t, b) + if err != nil { + return nil + } + return b +} + +// String returns a string with the result of converting s using t. It calls +// Reset on t. It returns the empty string if any error was found. This can only +// happen if an error-producing Transformer is passed to If. +func (t Transformer) String(s string) string { + s, _, err := transform.String(t, s) + if err != nil { + return "" + } + return s +} + +// TODO: +// - Copy: copying strings and bytes in whole-rune units. +// - Validation (maybe) +// - Well-formed-ness (maybe) + +const runeErrorString = string(utf8.RuneError) + +// Remove returns a Transformer that removes runes r for which s.Contains(r). +// Illegal input bytes are replaced by RuneError before being passed to f. +func Remove(s Set) Transformer { + if f, ok := s.(setFunc); ok { + // This little trick cuts the running time of BenchmarkRemove for sets + // created by Predicate roughly in half. + // TODO: special-case RangeTables as well. + return Transformer{remove(f)} + } + return Transformer{remove(s.Contains)} +} + +// TODO: remove transform.RemoveFunc. + +type remove func(r rune) bool + +func (remove) Reset() {} + +// Span implements transform.Spanner. +func (t remove) Span(src []byte, atEOF bool) (n int, err error) { + for r, size := rune(0), 0; n < len(src); { + if r = rune(src[n]); r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[n:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[n:]) { + err = transform.ErrShortSrc + } else { + err = transform.ErrEndOfSpan + } + break + } + if t(r) { + err = transform.ErrEndOfSpan + break + } + n += size + } + return +} + +// Transform implements transform.Transformer. +func (t remove) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for r, size := rune(0), 0; nSrc < len(src); { + if r = rune(src[nSrc]); r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[nSrc:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + // We replace illegal bytes with RuneError. Not doing so might + // otherwise turn a sequence of invalid UTF-8 into valid UTF-8. + // The resulting byte sequence may subsequently contain runes + // for which t(r) is true that were passed unnoticed. + if !t(utf8.RuneError) { + if nDst+3 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = runeErrorString[0] + dst[nDst+1] = runeErrorString[1] + dst[nDst+2] = runeErrorString[2] + nDst += 3 + } + nSrc++ + continue + } + if t(r) { + nSrc += size + continue + } + if nDst+size > len(dst) { + err = transform.ErrShortDst + break + } + for i := 0; i < size; i++ { + dst[nDst] = src[nSrc] + nDst++ + nSrc++ + } + } + return +} + +// Map returns a Transformer that maps the runes in the input using the given +// mapping. Illegal bytes in the input are converted to utf8.RuneError before +// being passed to the mapping func. +func Map(mapping func(rune) rune) Transformer { + return Transformer{mapper(mapping)} +} + +type mapper func(rune) rune + +func (mapper) Reset() {} + +// Span implements transform.Spanner. +func (t mapper) Span(src []byte, atEOF bool) (n int, err error) { + for r, size := rune(0), 0; n < len(src); n += size { + if r = rune(src[n]); r < utf8.RuneSelf { + size = 1 + } else if r, size = utf8.DecodeRune(src[n:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[n:]) { + err = transform.ErrShortSrc + } else { + err = transform.ErrEndOfSpan + } + break + } + if t(r) != r { + err = transform.ErrEndOfSpan + break + } + } + return n, err +} + +// Transform implements transform.Transformer. +func (t mapper) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + var replacement rune + var b [utf8.UTFMax]byte + + for r, size := rune(0), 0; nSrc < len(src); { + if r = rune(src[nSrc]); r < utf8.RuneSelf { + if replacement = t(r); replacement < utf8.RuneSelf { + if nDst == len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst] = byte(replacement) + nDst++ + nSrc++ + continue + } + size = 1 + } else if r, size = utf8.DecodeRune(src[nSrc:]); size == 1 { + // Invalid rune. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + + if replacement = t(utf8.RuneError); replacement == utf8.RuneError { + if nDst+3 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = runeErrorString[0] + dst[nDst+1] = runeErrorString[1] + dst[nDst+2] = runeErrorString[2] + nDst += 3 + nSrc++ + continue + } + } else if replacement = t(r); replacement == r { + if nDst+size > len(dst) { + err = transform.ErrShortDst + break + } + for i := 0; i < size; i++ { + dst[nDst] = src[nSrc] + nDst++ + nSrc++ + } + continue + } + + n := utf8.EncodeRune(b[:], replacement) + + if nDst+n > len(dst) { + err = transform.ErrShortDst + break + } + for i := 0; i < n; i++ { + dst[nDst] = b[i] + nDst++ + } + nSrc += size + } + return +} + +// ReplaceIllFormed returns a transformer that replaces all input bytes that are +// not part of a well-formed UTF-8 code sequence with utf8.RuneError. +func ReplaceIllFormed() Transformer { + return Transformer{&replaceIllFormed{}} +} + +type replaceIllFormed struct{ transform.NopResetter } + +func (t replaceIllFormed) Span(src []byte, atEOF bool) (n int, err error) { + for n < len(src) { + // ASCII fast path. + if src[n] < utf8.RuneSelf { + n++ + continue + } + + r, size := utf8.DecodeRune(src[n:]) + + // Look for a valid non-ASCII rune. + if r != utf8.RuneError || size != 1 { + n += size + continue + } + + // Look for short source data. + if !atEOF && !utf8.FullRune(src[n:]) { + err = transform.ErrShortSrc + break + } + + // We have an invalid rune. + err = transform.ErrEndOfSpan + break + } + return n, err +} + +func (t replaceIllFormed) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + for nSrc < len(src) { + // ASCII fast path. + if r := src[nSrc]; r < utf8.RuneSelf { + if nDst == len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst] = r + nDst++ + nSrc++ + continue + } + + // Look for a valid non-ASCII rune. + if _, size := utf8.DecodeRune(src[nSrc:]); size != 1 { + if size != copy(dst[nDst:], src[nSrc:nSrc+size]) { + err = transform.ErrShortDst + break + } + nDst += size + nSrc += size + continue + } + + // Look for short source data. + if !atEOF && !utf8.FullRune(src[nSrc:]) { + err = transform.ErrShortSrc + break + } + + // We have an invalid rune. + if nDst+3 > len(dst) { + err = transform.ErrShortDst + break + } + dst[nDst+0] = runeErrorString[0] + dst[nDst+1] = runeErrorString[1] + dst[nDst+2] = runeErrorString[2] + nDst += 3 + nSrc++ + } + return nDst, nSrc, err +} diff --git a/vendor/golang.org/x/text/unicode/cldr/base.go b/vendor/golang.org/x/text/unicode/cldr/base.go new file mode 100644 index 0000000..b71420c --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/base.go @@ -0,0 +1,105 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +import ( + "encoding/xml" + "regexp" + "strconv" +) + +// Elem is implemented by every XML element. +type Elem interface { + setEnclosing(Elem) + setName(string) + enclosing() Elem + + GetCommon() *Common +} + +type hidden struct { + CharData string `xml:",chardata"` + Alias *struct { + Common + Source string `xml:"source,attr"` + Path string `xml:"path,attr"` + } `xml:"alias"` + Def *struct { + Common + Choice string `xml:"choice,attr,omitempty"` + Type string `xml:"type,attr,omitempty"` + } `xml:"default"` +} + +// Common holds several of the most common attributes and sub elements +// of an XML element. +type Common struct { + XMLName xml.Name + name string + enclElem Elem + Type string `xml:"type,attr,omitempty"` + Reference string `xml:"reference,attr,omitempty"` + Alt string `xml:"alt,attr,omitempty"` + ValidSubLocales string `xml:"validSubLocales,attr,omitempty"` + Draft string `xml:"draft,attr,omitempty"` + hidden +} + +// Default returns the default type to select from the enclosed list +// or "" if no default value is specified. +func (e *Common) Default() string { + if e.Def == nil { + return "" + } + if e.Def.Choice != "" { + return e.Def.Choice + } else if e.Def.Type != "" { + // Type is still used by the default element in collation. + return e.Def.Type + } + return "" +} + +// Element returns the XML element name. +func (e *Common) Element() string { + return e.name +} + +// GetCommon returns e. It is provided such that Common implements Elem. +func (e *Common) GetCommon() *Common { + return e +} + +// Data returns the character data accumulated for this element. +func (e *Common) Data() string { + e.CharData = charRe.ReplaceAllStringFunc(e.CharData, replaceUnicode) + return e.CharData +} + +func (e *Common) setName(s string) { + e.name = s +} + +func (e *Common) enclosing() Elem { + return e.enclElem +} + +func (e *Common) setEnclosing(en Elem) { + e.enclElem = en +} + +// Escape characters that can be escaped without further escaping the string. +var charRe = regexp.MustCompile(`&#x[0-9a-fA-F]*;|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\[abtnvfr]`) + +// replaceUnicode converts hexadecimal Unicode codepoint notations to a one-rune string. +// It assumes the input string is correctly formatted. +func replaceUnicode(s string) string { + if s[1] == '#' { + r, _ := strconv.ParseInt(s[3:len(s)-1], 16, 32) + return string(rune(r)) + } + r, _, _, _ := strconv.UnquoteChar(s, 0) + return string(r) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/cldr.go b/vendor/golang.org/x/text/unicode/cldr/cldr.go new file mode 100644 index 0000000..f39b2e3 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/cldr.go @@ -0,0 +1,137 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run makexml.go -output xml.go + +// Package cldr provides a parser for LDML and related XML formats. +// +// This package is intended to be used by the table generation tools for the +// various packages in x/text and is not internal for historical reasons. +// +// As the XML types are generated from the CLDR DTD, and as the CLDR standard is +// periodically amended, this package may change considerably over time. This +// mostly means that data may appear and disappear between versions. That is, +// old code should keep compiling for newer versions, but data may have moved or +// changed. CLDR version 22 is the first version supported by this package. +// Older versions may not work. +package cldr // import "golang.org/x/text/unicode/cldr" + +import ( + "fmt" + "sort" +) + +// CLDR provides access to parsed data of the Unicode Common Locale Data Repository. +type CLDR struct { + parent map[string][]string + locale map[string]*LDML + resolved map[string]*LDML + bcp47 *LDMLBCP47 + supp *SupplementalData +} + +func makeCLDR() *CLDR { + return &CLDR{ + parent: make(map[string][]string), + locale: make(map[string]*LDML), + resolved: make(map[string]*LDML), + bcp47: &LDMLBCP47{}, + supp: &SupplementalData{}, + } +} + +// BCP47 returns the parsed BCP47 LDML data. If no such data was parsed, nil is returned. +func (cldr *CLDR) BCP47() *LDMLBCP47 { + return nil +} + +// Draft indicates the draft level of an element. +type Draft int + +const ( + Approved Draft = iota + Contributed + Provisional + Unconfirmed +) + +var drafts = []string{"unconfirmed", "provisional", "contributed", "approved", ""} + +// ParseDraft returns the Draft value corresponding to the given string. The +// empty string corresponds to Approved. +func ParseDraft(level string) (Draft, error) { + if level == "" { + return Approved, nil + } + for i, s := range drafts { + if level == s { + return Unconfirmed - Draft(i), nil + } + } + return Approved, fmt.Errorf("cldr: unknown draft level %q", level) +} + +func (d Draft) String() string { + return drafts[len(drafts)-1-int(d)] +} + +// SetDraftLevel sets which draft levels to include in the evaluated LDML. +// Any draft element for which the draft level is higher than lev will be excluded. +// If multiple draft levels are available for a single element, the one with the +// lowest draft level will be selected, unless preferDraft is true, in which case +// the highest draft will be chosen. +// It is assumed that the underlying LDML is canonicalized. +func (cldr *CLDR) SetDraftLevel(lev Draft, preferDraft bool) { + // TODO: implement + cldr.resolved = make(map[string]*LDML) +} + +// RawLDML returns the LDML XML for id in unresolved form. +// id must be one of the strings returned by Locales. +func (cldr *CLDR) RawLDML(loc string) *LDML { + return cldr.locale[loc] +} + +// LDML returns the fully resolved LDML XML for loc, which must be one of +// the strings returned by Locales. +// +// Deprecated: Use RawLDML and implement inheritance manually or using the +// internal cldrtree package. +// Inheritance has changed quite a bit since the onset of this package and in +// practice data often represented in a way where knowledge of how it was +// inherited is relevant. +func (cldr *CLDR) LDML(loc string) (*LDML, error) { + return cldr.resolve(loc) +} + +// Supplemental returns the parsed supplemental data. If no such data was parsed, +// nil is returned. +func (cldr *CLDR) Supplemental() *SupplementalData { + return cldr.supp +} + +// Locales returns the locales for which there exist files. +// Valid sublocales for which there is no file are not included. +// The root locale is always sorted first. +func (cldr *CLDR) Locales() []string { + loc := []string{"root"} + hasRoot := false + for l, _ := range cldr.locale { + if l == "root" { + hasRoot = true + continue + } + loc = append(loc, l) + } + sort.Strings(loc[1:]) + if !hasRoot { + return loc[1:] + } + return loc +} + +// Get fills in the fields of x based on the XPath path. +func Get(e Elem, path string) (res Elem, err error) { + return walkXPath(e, path) +} diff --git a/vendor/golang.org/x/text/unicode/cldr/collate.go b/vendor/golang.org/x/text/unicode/cldr/collate.go new file mode 100644 index 0000000..5794ae4 --- /dev/null +++ b/vendor/golang.org/x/text/unicode/cldr/collate.go @@ -0,0 +1,363 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cldr + +import ( + "bufio" + "encoding/xml" + "errors" + "fmt" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// RuleProcessor can be passed to Collator's Process method, which +// parses the rules and calls the respective method for each rule found. +type RuleProcessor interface { + Reset(anchor string, before int) error + Insert(level int, str, context, extend string) error + Index(id string) +} + +const ( + // cldrIndex is a Unicode-reserved sentinel value used to mark the start + // of a grouping within an index. + // We ignore any rule that starts with this rune. + // See https://unicode.org/reports/tr35/#Collation_Elements for details. + cldrIndex = "\uFDD0" + + // specialAnchor is the format in which to represent logical reset positions, + // such as "first tertiary ignorable". + specialAnchor = "<%s/>" +) + +// Process parses the rules for the tailorings of this collation +// and calls the respective methods of p for each rule found. +func (c Collation) Process(p RuleProcessor) (err error) { + if len(c.Cr) > 0 { + if len(c.Cr) > 1 { + return fmt.Errorf("multiple cr elements, want 0 or 1") + } + return processRules(p, c.Cr[0].Data()) + } + if c.Rules.Any != nil { + return c.processXML(p) + } + return errors.New("no tailoring data") +} + +// processRules parses rules in the Collation Rule Syntax defined in +// https://www.unicode.org/reports/tr35/tr35-collation.html#Collation_Tailorings. +func processRules(p RuleProcessor, s string) (err error) { + chk := func(s string, e error) string { + if err == nil { + err = e + } + return s + } + i := 0 // Save the line number for use after the loop. + scanner := bufio.NewScanner(strings.NewReader(s)) + for ; scanner.Scan() && err == nil; i++ { + for s := skipSpace(scanner.Text()); s != "" && s[0] != '#'; s = skipSpace(s) { + level := 5 + var ch byte + switch ch, s = s[0], s[1:]; ch { + case '&': // followed by or '[' ']' + if s = skipSpace(s); consume(&s, '[') { + s = chk(parseSpecialAnchor(p, s)) + } else { + s = chk(parseAnchor(p, 0, s)) + } + case '<': // sort relation '<'{1,4}, optionally followed by '*'. + for level = 1; consume(&s, '<'); level++ { + } + if level > 4 { + err = fmt.Errorf("level %d > 4", level) + } + fallthrough + case '=': // identity relation, optionally followed by *. + if consume(&s, '*') { + s = chk(parseSequence(p, level, s)) + } else { + s = chk(parseOrder(p, level, s)) + } + default: + chk("", fmt.Errorf("illegal operator %q", ch)) + break + } + } + } + if chk("", scanner.Err()); err != nil { + return fmt.Errorf("%d: %v", i, err) + } + return nil +} + +// parseSpecialAnchor parses the anchor syntax which is either of the form +// +// ['before' ] +// +// or +// +// [