package main import ( "bytes" "embed" "flag" "fmt" "io/fs" "os" omni "git.yuntuo.hk/k2safe/go-omni" apierrors "git.yuntuo.hk/k2safe/go-omni/errors" "git.yuntuo.hk/k2safe/go-omni/httpx" "git.yuntuo.hk/k2safe/go-omni/i18n" browseri18n "git.yuntuo.hk/k2safe/go-omni/i18n/browser" "git.yuntuo.hk/k2safe/go-omni/i18n/errori18n" "git.yuntuo.hk/k2safe/go-omni/i18n/httpi18n" "git.yuntuo.hk/k2safe/go-omni/response" ) //go:embed config.json locales/*.json index.html var resources embed.FS func catalog() apierrors.Catalog { return errori18n.Bind(apierrors.Catalog{Definitions: map[string]apierrors.Definition{ "LIMIT_EXCEEDED": {Status: 409, Code: 400001, Message: "Count must not exceed {max}", Params: []apierrors.Parameter{{Name: "max", Type: "int64"}}}, }}, "errors") } func bundle() (*i18n.Bundle, error) { raw, err := resources.ReadFile("config.json") if err != nil { return nil, err } var cfg i18n.Config if err := i18n.DecodeJSON(raw, &cfg); err != nil { return nil, err } docs, err := i18n.LoadFS(resources, "locales/ui.en.json", "locales/ui.zh-Hans.json", "locales/errors.zh-Hans.json") if err != nil { return nil, err } source, err := errori18n.Source(catalog(), cfg.SourceLocale) if err != nil { return nil, err } docs = append(docs, source...) return i18n.New(cfg, docs...) } func application(b *i18n.Bundle) (*omni.App, error) { policy, err := httpi18n.New(b) if err != nil { return nil, err } localizer := errori18n.New(b, nil) if err := localizer.Validate(catalog()); err != nil { return nil, err } a := omni.New(omni.WithErrors(catalog()), omni.WithResponse(response.Config{CodeType: "int32", SuccessCode: 0, ErrorCode: 9999}), omni.WithLanguagePolicy(policy), omni.WithErrorLocalizer(localizer)) a.GET("/", func(c *httpx.Context) error { data, err := resources.ReadFile("index.html") if err != nil { return err } return c.Bytes(200, "text/html; charset=utf-8", data) }) a.GET("/runtime.mjs", func(c *httpx.Context) error { data, err := fs.ReadFile(browseri18n.Files(), "runtime.mjs") if err != nil { return err } return c.Bytes(200, "text/javascript; charset=utf-8", data) }) a.GET("/locales/:file", func(c *httpx.Context) error { var locale string switch c.Param("file") { case "en.json": locale = "en" case "zh-Hans.json": locale = "zh-Hans" default: return apierrors.NotFound("NOT_FOUND", "language pack not found") } var out bytes.Buffer if err := b.Export(&out, locale, "ui"); err != nil { return err } return c.Bytes(200, "application/json; charset=utf-8", out.Bytes()) }) a.GET("/api/limit", func(*httpx.Context) error { return apierrors.ByCode("LIMIT_EXCEEDED").WithParams(map[string]any{"max": int64(100)}) }) a.POST("/api/action", func(c *httpx.Context) error { return c.OK(map[string]bool{"accepted": true}) }) return a, nil } func main() { serve := flag.String("serve", "", "optional local HTTP listen address") locale := flag.String("export-locale", "", "export a UI language pack to stdout") exportCatalog := flag.Bool("catalog", false, "export the Go error catalog to stdout") flag.Parse() if err := run(*serve, *locale, *exportCatalog); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } func run(serve, locale string, exportCatalog bool) error { if exportCatalog { return apierrors.ExportCatalog(os.Stdout, catalog()) } b, err := bundle() if err != nil { return err } if locale != "" { return b.Export(os.Stdout, locale, "ui") } if serve != "" { a, err := application(b) if err != nil { return err } defer a.Close() return a.Run(serve) } for _, l := range b.Config().Locales { result, err := b.Render(l, i18n.Ref{Namespace: "ui", Key: "greeting"}, map[string]any{"name": "Alex"}) if err != nil { return err } fmt.Println(result.Locale + ": " + result.Text) } return nil }