From 400e3429f53720739a824d98f969b40147a6ea29 Mon Sep 17 00:00:00 2001 From: Alexander Lourier Date: Sun, 18 Mar 2018 12:21:45 +0100 Subject: [PATCH 1/7] Do not deduplicate identical messages with different contexts --- go-xgettext/main.go | 79 +++++++++++++++++++++++----------------- go-xgettext/main_test.go | 57 ++++++++++++++--------------- go.mod | 17 +++++++++ go.sum | 23 ++++++++++++ 4 files changed, 114 insertions(+), 62 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go-xgettext/main.go b/go-xgettext/main.go index 5f89abe..e9af417 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -34,7 +34,6 @@ import ( "go/parser" "go/token" "io" - "io/ioutil" "log" "os" "sort" @@ -51,10 +50,9 @@ var ( msgIDBugsAddress = flag.String("msgid-bugs-address", "EMAIL", "set report address for msgid bugs.") packageName = flag.String("package-name", "", "Set package name in output.") - keyword = flag.String("keyword", "gettext.Gettext", "Look for WORD as the keyword for singular strings.") - keywordPlural = flag.String("keyword-plural", "gettext.NGettext", "Look for WORD as the keyword for plural strings.") - keywordContextual = flag.String("keyword-contextual", "gettext.CGettext", "Look for WORD as the keyword for contextual strings.") - keywordPluralContextual = flag.String("keyword-plural-contextual", "gettext.CNGettext", "Look for WORD as the keyword for plural contextual strings.") + keyword = flag.String("keyword", "gettext.Gettext", "Look for WORD as the keyword for singular strings.") + keywordPlural = flag.String("keyword-plural", "gettext.NGettext", "Look for WORD as the keyword for plural strings.") + keywordContextual = flag.String("keyword-contextual", "gettext.CGettext", "Look for WORD as the keyword for contextual strings.") skipArgs = flag.Int("skip-args", 0, "Number of arguments to skip in gettext function call before considering a text message argument.") @@ -76,18 +74,31 @@ type keywordDef struct { type keywords map[string]*keywordDef -type allKeywordsConfig []*keywordDef +type msgKey struct { + msgctxt string + msgtext string +} -type msgID struct { +type msgData struct { msgidPlural string - msgctxt string comment string fname string line int formatHint string } -var msgIDs map[string][]msgID +type msgKeyList []msgKey + +func (l msgKeyList) Len() int { return len(l) } +func (l msgKeyList) Less(i, j int) bool { + if l[i].msgctxt != l[j].msgctxt { + return l[i].msgctxt < l[j].msgctxt + } + return l[i].msgtext < l[j].msgtext +} +func (l msgKeyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] } + +var msgIDs map[msgKey][]msgData func formatComment(com string) string { out := "" @@ -132,28 +143,28 @@ func findCommentsForTranslation(fset *token.FileSet, f *ast.File, posCall token. } func constructValue(val interface{}) (string, error) { - switch val.(type) { + switch val := val.(type) { case *ast.BasicLit: - return val.(*ast.BasicLit).Value, nil + return val.Value, nil // this happens for constructs like: // gettext.Gettext("foo" + "bar") case *ast.BinaryExpr: // we only support string concat - if val.(*ast.BinaryExpr).Op != token.ADD { + if val.Op != token.ADD { return "", nil } - left, err := constructValue(val.(*ast.BinaryExpr).X) + left, err := constructValue(val.X) if err != nil { return "", err } // strip right " (or `) left = left[0 : len(left)-1] - right, err := constructValue(val.(*ast.BinaryExpr).Y) + right, err := constructValue(val.Y) if err != nil { return "", err } // strip left " (or `) - right = right[1:len(right)] + right = right[1:] return left + right, nil default: return "", fmt.Errorf("unknown type: %v", val) @@ -230,12 +241,15 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n formatHint = "c-format" } - msgidStr := formatI18nStr(i18nStr) + i18nCtxt = formatI18nStr(i18nCtxt) + i18nStr = formatI18nStr(i18nStr) + i18nStrPlural = formatI18nStr(i18nStrPlural) + posCall := fset.Position(n.Pos()) - msgIDs[msgidStr] = append(msgIDs[msgidStr], msgID{ + k := msgKey{i18nCtxt, i18nStr} + msgIDs[k] = append(msgIDs[k], msgData{ formatHint: formatHint, - msgidPlural: formatI18nStr(i18nStrPlural), - msgctxt: formatI18nStr(i18nCtxt), + msgidPlural: i18nStrPlural, fname: posCall.Filename, line: posCall.Line, comment: findCommentsForTranslation(fset, f, posCall), @@ -263,7 +277,7 @@ func formatI18nStr(s string) string { func processFiles(args []string) error { // go over the input files - msgIDs = make(map[string][]msgID) + msgIDs = make(map[msgKey][]msgData) fset := token.NewFileSet() for _, fname := range args { @@ -278,7 +292,7 @@ func processFiles(args []string) error { func parseKeywords() (keywords, error) { k := make(keywords) if *keywordCfg != "" { - data, err := ioutil.ReadFile(*keywordCfg) + data, err := os.ReadFile(*keywordCfg) if err != nil { return nil, err } @@ -310,7 +324,7 @@ func parseKeywords() (keywords, error) { } func processSingleGoSource(fset *token.FileSet, fname string) error { - fnameContent, err := ioutil.ReadFile(fname) + fnameContent, err := os.ReadFile(fname) if err != nil { panic(err) } @@ -361,30 +375,30 @@ msgstr "Project-Id-Version: %s\n" fmt.Fprintf(out, "%s", header) // yes, this is the way to do it in go - sortedKeys := []string{} + var sortedKeys msgKeyList for k := range msgIDs { sortedKeys = append(sortedKeys, k) } if *sortOutput { - sort.Strings(sortedKeys) + sort.Sort(sortedKeys) } // FIXME: use template here? - for _, k := range sortedKeys { - msgidList := msgIDs[k] - for _, msgid := range msgidList { + for _, key := range sortedKeys { + msgList := msgIDs[key] + for _, msgid := range msgList { if *addComments || *addCommentsTag != "" { fmt.Fprintf(out, "%s", msgid.comment) } } if !*noLocation { fmt.Fprintf(out, "#:") - for _, msgid := range msgidList { + for _, msgid := range msgList { fmt.Fprintf(out, " %s:%d", msgid.fname, msgid.line) } fmt.Fprintf(out, "\n") } - msgid := msgidList[0] + msgid := msgList[0] if msgid.formatHint != "" { fmt.Fprintf(out, "#, %s\n", msgid.formatHint) } @@ -395,10 +409,10 @@ msgstr "Project-Id-Version: %s\n" // cleanup too aggressive splitting (empty "" lines) return strings.TrimSuffix(out, "\"\n \"") } - if msgid.msgctxt != "" { - fmt.Fprintf(out, "msgctxt \"%v\"\n", formatOutput(msgid.msgctxt)) + if key.msgctxt != "" { + fmt.Fprintf(out, "msgctxt \"%v\"\n", formatOutput(key.msgctxt)) } - fmt.Fprintf(out, "msgid \"%v\"\n", formatOutput(k)) + fmt.Fprintf(out, "msgid \"%v\"\n", formatOutput(key.msgtext)) if msgid.msgidPlural != "" { fmt.Fprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural)) fmt.Fprintf(out, "msgstr[0] \"\"\n") @@ -408,7 +422,6 @@ msgstr "Project-Id-Version: %s\n" } fmt.Fprintf(out, "\n") } - } func main() { diff --git a/go-xgettext/main_test.go b/go-xgettext/main_test.go index eada037..85f6b8a 100644 --- a/go-xgettext/main_test.go +++ b/go-xgettext/main_test.go @@ -29,7 +29,6 @@ package main import ( "bytes" "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -48,7 +47,7 @@ var _ = Suite(&xgettextTestSuite{}) // test helper func makeGoSourceFile(c *C, content []byte) string { fname := filepath.Join(c.MkDir(), "foo.go") - err := ioutil.WriteFile(fname, []byte(content), 0644) + err := os.WriteFile(fname, []byte(content), 0644) c.Assert(err, IsNil) return fname @@ -98,8 +97,8 @@ func main() { err := processFiles([]string{fname}) c.Assert(err, IsNil) - c.Assert(msgIDs, DeepEquals, map[string][]msgID{ - "foo": []msgID{ + c.Assert(msgIDs, DeepEquals, map[msgKey][]msgData{ + {"", "foo"}: { { comment: "#. TRANSLATORS: foo comment\n", fname: fname, @@ -123,8 +122,8 @@ func main() { err := processFiles([]string{fname}) c.Assert(err, IsNil) - c.Assert(msgIDs, DeepEquals, map[string][]msgID{ - "foo": []msgID{ + c.Assert(msgIDs, DeepEquals, map[msgKey][]msgData{ + {"", "foo"}: { { comment: "#. TRANSLATORS: foo comment\n", fname: fname, @@ -159,8 +158,8 @@ msgstr "Project-Id-Version: snappy\n" ` func (s *xgettextTestSuite) TestWriteOutputSimple(c *C) { - msgIDs = map[string][]msgID{ - "foo": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo"}: { { fname: "fname", line: 2, @@ -182,8 +181,8 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) { - msgIDs = map[string][]msgID{ - "foo": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo"}: { { fname: "fname", line: 2, @@ -211,8 +210,8 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) { - msgIDs = map[string][]msgID{ - "foo": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo"}: { { fname: "fname", line: 2, @@ -232,8 +231,8 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputNoLocation(c *C) { - msgIDs = map[string][]msgID{ - "foo": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo"}: { { fname: "fname", line: 2, @@ -254,8 +253,8 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputFormatHint(c *C) { - msgIDs = map[string][]msgID{ - "foo": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo"}: { { fname: "fname", line: 2, @@ -278,8 +277,8 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputPlural(c *C) { - msgIDs = map[string][]msgID{ - "foo": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo"}: { { msgidPlural: "plural", fname: "fname", @@ -303,14 +302,14 @@ msgstr[1] "" } func (s *xgettextTestSuite) TestWriteOutputSorted(c *C) { - msgIDs = map[string][]msgID{ - "aaa": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "aaa"}: { { fname: "fname", line: 2, }, }, - "zzz": []msgID{ + {"", "zzz"}: { { fname: "fname", line: 2, @@ -370,7 +369,7 @@ func main() { main() // verify its what we expect - got, err := ioutil.ReadFile(outName) + got, err := os.ReadFile(outName) c.Assert(err, IsNil) expected := fmt.Sprintf(`%s #: %[2]s:9 @@ -410,8 +409,8 @@ func main() { err := processFiles([]string{fname}) c.Assert(err, IsNil) - c.Assert(msgIDs, DeepEquals, map[string][]msgID{ - "foo\\nbar\\nbaz": []msgID{ + c.Assert(msgIDs, DeepEquals, map[msgKey][]msgData{ + {"", "foo\\nbar\\nbaz"}: { { comment: "#. TRANSLATORS: foo comment\n", fname: fname, @@ -445,8 +444,8 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputMultilines(c *C) { - msgIDs = map[string][]msgID{ - "foo\\nbar\\nbaz": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo\\nbar\\nbaz"}: { { fname: "fname", line: 2, @@ -469,14 +468,14 @@ msgstr "" } func (s *xgettextTestSuite) TestWriteOutputTidy(c *C) { - msgIDs = map[string][]msgID{ - "foo\\nbar\\nbaz": []msgID{ + msgIDs = map[msgKey][]msgData{ + {"", "foo\\nbar\\nbaz"}: { { fname: "fname", line: 2, }, }, - "zzz\\n": []msgID{ + {"", "zzz\\n"}: { { fname: "fname", line: 4, diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..618a9e7 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/joyteam/gettext + +go 1.19 + +require ( + github.com/stretchr/testify v1.9.0 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a1dc9ac --- /dev/null +++ b/go.sum @@ -0,0 +1,23 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7027d5d8021fd52c065c05cfba23505d48d518a3 Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Sat, 25 May 2024 11:16:19 +0200 Subject: [PATCH 2/7] Remove unnecessary fields from the header and make output deterministic --- go-xgettext/main.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/go-xgettext/main.go b/go-xgettext/main.go index e9af417..468d0e1 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -49,6 +49,7 @@ var ( noLocation = flag.Bool("no-location", false, "Do not write '#: filename:line' lines.") msgIDBugsAddress = flag.String("msgid-bugs-address", "EMAIL", "set report address for msgid bugs.") packageName = flag.String("package-name", "", "Set package name in output.") + deterministic = flag.Bool("deterministic", false, "Generate deterministic output (e.g. no timestamps)") keyword = flag.String("keyword", "gettext.Gettext", "Look for WORD as the keyword for singular strings.") keywordPlural = flag.String("keyword-plural", "gettext.NGettext", "Look for WORD as the keyword for plural strings.") @@ -363,12 +364,8 @@ msgid "" msgstr "Project-Id-Version: %s\n" "Report-Msgid-Bugs-To: %s\n" "POT-Creation-Date: %s\n" - "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" - "Last-Translator: FULL NAME \n" - "Language-Team: LANGUAGE \n" - "Language: \n" "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=CHARSET\n" + "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" `, *packageName, *msgIDBugsAddress, formatTime()) @@ -433,6 +430,12 @@ func main() { flag.PrintDefaults() os.Exit(0) } + if *deterministic { + *sortOutput = true + formatTime = func() string { + return "1970-01-01 00:00+0000" + } + } if err := processFiles(args); err != nil { log.Fatalf("processFiles failed with: %s", err) From ad027e09e1f1930bb01be63afcb8f056107f81fb Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Sun, 26 May 2024 12:23:20 +0200 Subject: [PATCH 3/7] Make format hints configurable --- go-xgettext/main.go | 17 ++++++----------- go-xgettext/main_test.go | 7 +------ 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/go-xgettext/main.go b/go-xgettext/main.go index 468d0e1..43709de 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -68,9 +68,10 @@ const ( ) type keywordDef struct { - Type string `json:"type"` - Name string `json:"name"` - SkipArgs int `json:"skipArgs"` + Type string `json:"type"` + Name string `json:"name"` + SkipArgs int `json:"skipArgs"` + FormatHint string `json:"formatHint"` } type keywords map[string]*keywordDef @@ -191,7 +192,7 @@ func parseFunExpr(path string, expr ast.Expr) string { func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: - var i18nStr, i18nStrPlural, i18nCtxt string + var i18nStr, i18nStrPlural, i18nCtxt, formatHint string var err error name := parseFunExpr("", x.Fun) if name == "" { @@ -225,6 +226,7 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n } i18nStrPlural, err = constructValue(x.Args[idx+2]) } + formatHint = keyword.FormatHint } if err != nil { fmt.Fprintf(os.Stderr, "WARN: Unable to obtain value at %s: %v\n", fset.Position(n.Pos()), err) @@ -235,13 +237,6 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n break } - // FIXME: too simplistic(?), no %% is considered - formatHint := "" - if strings.Contains(i18nStr, "%") || strings.Contains(i18nStrPlural, "%") { - // well, not quite correct but close enough - formatHint = "c-format" - } - i18nCtxt = formatI18nStr(i18nCtxt) i18nStr = formatI18nStr(i18nStr) i18nStrPlural = formatI18nStr(i18nStrPlural) diff --git a/go-xgettext/main_test.go b/go-xgettext/main_test.go index 85f6b8a..8f632b7 100644 --- a/go-xgettext/main_test.go +++ b/go-xgettext/main_test.go @@ -148,12 +148,8 @@ msgid "" msgstr "Project-Id-Version: snappy\n" "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n" "POT-Creation-Date: 2015-06-30 14:48+0200\n" - "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" - "Last-Translator: FULL NAME \n" - "Language-Team: LANGUAGE \n" - "Language: \n" "MIME-Version: 1.0\n" - "Content-Type: text/plain; charset=CHARSET\n" + "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" ` @@ -390,7 +386,6 @@ msgstr[0] "" msgstr[1] "" #: %[2]s:14 -#, c-format msgid "zz %%s" msgstr "" From d6d388434260087a1681108842746d74fffb300b Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Sun, 26 May 2024 12:38:32 +0200 Subject: [PATCH 4/7] Support for extra format hint arguments --- go-xgettext/main.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/go-xgettext/main.go b/go-xgettext/main.go index 43709de..d022e20 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -68,10 +68,11 @@ const ( ) type keywordDef struct { - Type string `json:"type"` - Name string `json:"name"` - SkipArgs int `json:"skipArgs"` - FormatHint string `json:"formatHint"` + Type string `json:"type"` + Name string `json:"name"` + SkipArgs int `json:"skipArgs"` + FormatHint string `json:"formatHint"` + FormatHintArgs int `json:"formatHintArgs"` } type keywords map[string]*keywordDef @@ -200,6 +201,16 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n } if keyword, ok := k[name]; ok { idx := keyword.SkipArgs + formatHint = keyword.FormatHint + for i := 0; i < keyword.FormatHintArgs; i++ { + var argVal string + argVal, err = constructValue(x.Args[idx]) + if err != nil { + break + } + formatHint = fmt.Sprintf("%s,%s", formatHint, argVal) + idx++ + } switch keyword.Type { case kTypeSingular: i18nStr, err = constructValue(x.Args[idx]) @@ -226,7 +237,6 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n } i18nStrPlural, err = constructValue(x.Args[idx+2]) } - formatHint = keyword.FormatHint } if err != nil { fmt.Fprintf(os.Stderr, "WARN: Unable to obtain value at %s: %v\n", fset.Position(n.Pos()), err) From 2680edcf62d8e1f42193a6686d9c1a102e0c16b2 Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Sun, 26 May 2024 12:49:05 +0200 Subject: [PATCH 5/7] Error handling --- go-xgettext/main.go | 68 +++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/go-xgettext/main.go b/go-xgettext/main.go index d022e20..834950b 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -203,6 +203,10 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n idx := keyword.SkipArgs formatHint = keyword.FormatHint for i := 0; i < keyword.FormatHintArgs; i++ { + if idx >= len(x.Args) { + err = fmt.Errorf("not enough arguments") + break + } var argVal string argVal, err = constructValue(x.Args[idx]) if err != nil { @@ -211,31 +215,47 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n formatHint = fmt.Sprintf("%s,%s", formatHint, argVal) idx++ } - switch keyword.Type { - case kTypeSingular: - i18nStr, err = constructValue(x.Args[idx]) - case kTypePlural: - i18nStr, err = constructValue(x.Args[idx]) - if err != nil { - break - } - i18nStrPlural, err = constructValue(x.Args[idx+1]) - case kTypeContextual: - i18nCtxt, err = constructValue(x.Args[idx]) - if err != nil { - break - } - i18nStr, err = constructValue(x.Args[idx+1]) - case kTypePluralContextual: - i18nCtxt, err = constructValue(x.Args[idx]) - if err != nil { - break - } - i18nStr, err = constructValue(x.Args[idx+1]) - if err != nil { - break + if idx >= len(x.Args) { + err = fmt.Errorf("not enough arguments") + } else { + switch keyword.Type { + case kTypeSingular: + i18nStr, err = constructValue(x.Args[idx]) + case kTypePlural: + if idx+1 >= len(x.Args) { + err = fmt.Errorf("not enough arguments") + break + } + i18nStr, err = constructValue(x.Args[idx]) + if err != nil { + break + } + i18nStrPlural, err = constructValue(x.Args[idx+1]) + case kTypeContextual: + if idx+1 >= len(x.Args) { + err = fmt.Errorf("not enough arguments") + break + } + i18nCtxt, err = constructValue(x.Args[idx]) + if err != nil { + break + } + i18nStr, err = constructValue(x.Args[idx+1]) + case kTypePluralContextual: + if idx+2 >= len(x.Args) { + err = fmt.Errorf("not enough arguments") + break + } + i18nCtxt, err = constructValue(x.Args[idx]) + if err != nil { + break + } + i18nStr, err = constructValue(x.Args[idx+1]) + if err != nil { + break + } + i18nStrPlural, err = constructValue(x.Args[idx+2]) } - i18nStrPlural, err = constructValue(x.Args[idx+2]) } } if err != nil { From af81cad09b3739f90c551aca738682342bcb8737 Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Sun, 26 May 2024 13:23:09 +0200 Subject: [PATCH 6/7] Strip quotes from format args --- go-xgettext/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go-xgettext/main.go b/go-xgettext/main.go index 834950b..d276943 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -212,6 +212,8 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n if err != nil { break } + // strip leading and trailing " (or `) + argVal = argVal[1 : len(argVal)-1] formatHint = fmt.Sprintf("%s,%s", formatHint, argVal) idx++ } From 661514ca80bc2db4bb47c24eb4d48863d75f9e0f Mon Sep 17 00:00:00 2001 From: Alex Lurye Date: Sun, 30 Jun 2024 16:51:59 +0200 Subject: [PATCH 7/7] Force context value for non-contextualized calls --- go-xgettext/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go-xgettext/main.go b/go-xgettext/main.go index d276943..64d7342 100644 --- a/go-xgettext/main.go +++ b/go-xgettext/main.go @@ -73,6 +73,7 @@ type keywordDef struct { SkipArgs int `json:"skipArgs"` FormatHint string `json:"formatHint"` FormatHintArgs int `json:"formatHintArgs"` + ForceContext string `json:"forceContext"` } type keywords map[string]*keywordDef @@ -222,8 +223,10 @@ func inspectNodeForTranslations(k keywords, fset *token.FileSet, f *ast.File, n } else { switch keyword.Type { case kTypeSingular: + i18nCtxt = fmt.Sprintf("%q", keyword.ForceContext) i18nStr, err = constructValue(x.Args[idx]) case kTypePlural: + i18nCtxt = fmt.Sprintf("%q", keyword.ForceContext) if idx+1 >= len(x.Args) { err = fmt.Errorf("not enough arguments") break