|
@@ -0,0 +1,205 @@
|
|
|
+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
|
|
|
+ }
|
|
|
+}
|