package main import ( "io/ioutil" "log" "os" "path/filepath" "regexp" "strings" "text/template" "time" "code.osinet.fr/fgm/kurz/translations" "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/spf13/cobra" "golang.org/x/text/language" "gopkg.in/yaml.v2" ) var cmdGenerateTranslations = &cobra.Command{ Args: cobra.NoArgs, Long: "Generate the translations package", Run: generateTranslationsHandler, Short: "Converts the messages.*.yaml files to a compilable i18n messages embed package. Only useful during Kurz development", Use: "generate-translations", } func init() { cmd.AddCommand(cmdGenerateTranslations) } /* dumpMessages is a debug helper, logging a messageMap in YAML format for readability. */ func dumpMessages(messageMap translations.MessageMap) { out, err := yaml.Marshal(messageMap) if err != nil { panic(err) } log.Println("Messages:\n" + string(out)) } func generateTranslationsHandler(cmd *cobra.Command, args []string) { cwd, err := os.Getwd() if err != nil { panic(err) } t0 := time.Now() log.Printf("Finding message catalogs below %s\n", cwd) tags := []string{ language.English.String(), language.French.String(), } messageMap := make(translations.MessageMap) for _, tag := range tags { messageMap[language.Make(tag)] = [](*i18n.Message){} } // Build the regex matching message file names. re := regexp.MustCompile("messages\\.(?:" + strings.Join(tags, "|") + ")\\.yaml") scanned := 0 catalogCount := 0 walkErr := filepath.Walk(cwd, scanCatalog(messageMap, re, &scanned, &catalogCount)) if walkErr != nil { panic(walkErr) } t1 := time.Now() diff := t1.Sub(t0) log.Printf("%d items scanned, %d catalogs loaded in %d msec\n", scanned, catalogCount, diff/time.Millisecond) // dumpMessages(messageMap) pwd, err := os.Getwd() if err != nil { panic(err) } println(pwd) translationsDir := pwd + "/translations" tpl := template.Must(template.ParseFiles(translationsDir + "/template.go.gotext")) gen, err := os.Create(translationsDir + "/generated.go") if err != nil { panic(err) } err = tpl.ExecuteTemplate(gen, "template.go.gotext", messageMap) if err != nil { panic(err) } } /* parseMessageFileBytes merges the messages found in "buf" into the messageMap. See scanCatalog(). */ func parseMessages(messagesMap translations.MessageMap, buf []byte, tag language.Tag) error { if len(buf) == 0 { return nil } raw := make(map[string]interface{}) if err := yaml.Unmarshal(buf, &raw); err != nil { return err } for id, item := range raw { switch item.(type) { // Degenerate case: "key: one" case string: message := &i18n.Message{ ID: id, Other: item.(string), } messagesMap[tag] = append(messagesMap[tag], message) // Normal case: key: { ID: ..., one: ... }. case map[interface{}]interface{}: serial, err := yaml.Marshal(item) if err != nil { panic(err) } message := &i18n.Message{ ID: id, } err = yaml.Unmarshal(serial, message) if err != nil { panic(err) } messagesMap[tag] = append(messagesMap[tag], message) default: log.Fatalf("Unsupported message format %#v", item) } } return nil } // parsePath is a copy of go-i18n.internal.parsePath // // Used under its MIT license. func parsePath(path string) (langTag, format string) { formatStartIdx := -1 for i := len(path) - 1; i >= 0; i-- { c := path[i] if os.IsPathSeparator(c) { if formatStartIdx != -1 { langTag = path[i+1 : formatStartIdx] } return } if path[i] == '.' { if formatStartIdx != -1 { langTag = path[i+1 : formatStartIdx] return } if formatStartIdx == -1 { format = path[i+1:] formatStartIdx = i } } } if formatStartIdx != -1 { langTag = path[:formatStartIdx] } return } /* scanCatalog builds a WalkFn callback parsing each message file as it is found, and merging its values into messageMap. See generateTranslationsHandler */ func scanCatalog(messageMap translations.MessageMap, re *regexp.Regexp, scanned *int, catalogCount *int) filepath.WalkFunc { return func(path string, info os.FileInfo, err error) error { *scanned++ if err != nil { panic(err) } if info.IsDir() { return nil } if !re.MatchString(info.Name()) { return nil } langid, _ := parsePath(path) tag := language.Make(langid) _, ok := messageMap[tag] if !ok { log.Printf("Found message catalog %s in unexpected language %s, skipping.", path, tag.String()) return nil } buf, err := ioutil.ReadFile(path) if err != nil { panic(err) } err = parseMessages(messageMap, buf, tag) *catalogCount++ return err } }