diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..dbe865b --- /dev/null +++ b/.drone.yml @@ -0,0 +1,13 @@ +--- +kind: pipeline +name: coopcloud.tech/xgettext-go +steps: + - name: unit tests + image: golang:1.25 + commands: + - go test -v ./... + +trigger: + action: + exclude: + - synchronized diff --git a/README.md b/README.md index 2f22650..850553c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # xgettext-go +[![Build Status](https://build.coopcloud.tech/api/badges/toolshed/xgettext-go/status.svg?ref=refs/heads/main)](https://build.coopcloud.tech/toolshed/xgettext-go) + * [Forked from here](https://github.com/canonical/snapd/tree/master/i18n/xgettext-go) * [The reason why](https://git.coopcloud.tech/toolshed/abra/issues/647) diff --git a/main.go b/main.go index 09467ca..ab56134 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( type msgID struct { msgidPlural string comment string + context string fname string line int formatHint string @@ -102,16 +103,27 @@ func inspectNodeForTranslations(fset *token.FileSet, f *ast.File, n ast.Node) bo gettextSelectorPlural := l[0] gettextFuncNamePlural := l[1] + l = strings.Split(opts.KeywordContext, ".") + gettextSelectorContext := l[0] + gettextFuncNameContext := l[1] + switch x := n.(type) { case *ast.CallExpr: if sel, ok := x.Fun.(*ast.SelectorExpr); ok { i18nStr := "" i18nStrPlural := "" + i18nStrContext := "" + if sel.Sel.Name == gettextFuncNamePlural && sel.X.(*ast.Ident).Name == gettextSelectorPlural { i18nStr = x.Args[0].(*ast.BasicLit).Value i18nStrPlural = x.Args[1].(*ast.BasicLit).Value } + if sel.Sel.Name == gettextFuncNameContext && sel.X.(*ast.Ident).Name == gettextSelectorContext { + i18nStr = x.Args[0].(*ast.BasicLit).Value + i18nStrContext = x.Args[1].(*ast.BasicLit).Value + } + if sel.Sel.Name == gettextFuncName && sel.X.(*ast.Ident).Name == gettextSelector { i18nStr = constructValue(x.Args[0]) } @@ -144,6 +156,7 @@ func inspectNodeForTranslations(fset *token.FileSet, f *ast.File, n ast.Node) bo msgIDs[msgidStr] = append(msgIDs[msgidStr], msgID{ formatHint: formatHint, msgidPlural: formatI18nStr(i18nStrPlural), + context: formatI18nStr(i18nStrContext), fname: posCall.Filename, line: posCall.Line, comment: findCommentsForTranslation(fset, f, posCall), @@ -219,6 +232,79 @@ func mustFprintf(w io.Writer, format string, a ...any) { } } +func formatOutput(in string) string { + // split string with \n into multiple lines + // to make the output nicer + out := strings.Replace(in, "\\n", "\\n\"\n \"", -1) + // cleanup too aggressive splitting (empty "" lines) + return strings.TrimSuffix(out, "\"\n \"") +} + +func writeMsg(out io.Writer, k string, msgidList []msgID) { + for _, msgid := range msgidList { + if opts.AddComments || opts.AddCommentsTag != "" { + mustFprintf(out, "%s", msgid.comment) + } + } + + if !opts.NoLocation { + mustFprintf(out, "#:") + for _, msgid := range msgidList { + mustFprintf(out, " %s:%d", msgid.fname, msgid.line) + } + mustFprintf(out, "\n") + } + + msgid := msgidList[0] + if msgid.formatHint != "" { + mustFprintf(out, "#, %s\n", msgid.formatHint) + } + + mustFprintf(out, "msgid \"%v\"\n", formatOutput(k)) + + if msgid.msgidPlural != "" { + mustFprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural)) + mustFprintf(out, "msgstr[0] \"\"\n") + mustFprintf(out, "msgstr[1] \"\"\n") + } else { + mustFprintf(out, "msgstr \"\"\n") + } + + mustFprintf(out, "\n") +} + +func writeMsgWithContext(out io.Writer, k string, msgidList []msgID) { + for _, msgid := range msgidList { + if opts.AddComments || opts.AddCommentsTag != "" { + mustFprintf(out, "%s", msgid.comment) + } + + if !opts.NoLocation { + mustFprintf(out, "#: %s:%d\n", msgid.fname, msgid.line) + } + + if msgid.formatHint != "" { + mustFprintf(out, "#, %s\n", msgid.formatHint) + } + + if msgid.context != "" { + mustFprintf(out, "msgctxt \"%v\"\n", formatOutput(msgid.context)) + } + + mustFprintf(out, "msgid \"%v\"\n", formatOutput(k)) + + if msgid.msgidPlural != "" { + mustFprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural)) + mustFprintf(out, "msgstr[0] \"\"\n") + mustFprintf(out, "msgstr[1] \"\"\n") + } else { + mustFprintf(out, "msgstr \"\"\n") + } + + mustFprintf(out, "\n") + } +} + func writePotFile(out io.Writer) { header := fmt.Sprintf(`# SOME DESCRIPTIVE TITLE. @@ -253,39 +339,25 @@ msgstr "Project-Id-Version: %s\n" // FIXME: use template here? for _, k := range sortedKeys { + var msgIDsWithoutContext []msgID + var msgIDsWithContext []msgID + msgidList := msgIDs[k] for _, msgid := range msgidList { - if opts.AddComments || opts.AddCommentsTag != "" { - mustFprintf(out, "%s", msgid.comment) + if msgid.context == "" { + msgIDsWithoutContext = append(msgIDsWithoutContext, msgid) + } else { + msgIDsWithContext = append(msgIDsWithContext, msgid) } } - if !opts.NoLocation { - mustFprintf(out, "#:") - for _, msgid := range msgidList { - mustFprintf(out, " %s:%d", msgid.fname, msgid.line) - } - mustFprintf(out, "\n") + + if len(msgIDsWithoutContext) > 0 { + writeMsg(out, k, msgIDsWithoutContext) } - msgid := msgidList[0] - if msgid.formatHint != "" { - mustFprintf(out, "#, %s\n", msgid.formatHint) + + if len(msgIDsWithContext) > 0 { + writeMsgWithContext(out, k, msgIDsWithContext) } - var formatOutput = func(in string) string { - // split string with \n into multiple lines - // to make the output nicer - out := strings.Replace(in, "\\n", "\\n\"\n \"", -1) - // cleanup too aggressive splitting (empty "" lines) - return strings.TrimSuffix(out, "\"\n \"") - } - mustFprintf(out, "msgid \"%v\"\n", formatOutput(k)) - if msgid.msgidPlural != "" { - mustFprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural)) - mustFprintf(out, "msgstr[0] \"\"\n") - mustFprintf(out, "msgstr[1] \"\"\n") - } else { - mustFprintf(out, "msgstr \"\"\n") - } - mustFprintf(out, "\n") } } @@ -310,8 +382,9 @@ var opts struct { PackageName string `long:"package-name" description:"set package name in output"` - Keyword string `short:"k" long:"keyword" default:"gettext.Gettext" description:"look for WORD as the keyword for singular strings"` - KeywordPlural string `long:"keyword-plural" default:"gettext.NGettext" description:"look for WORD as the keyword for plural strings"` + Keyword string `short:"k" long:"keyword" default:"gettext.Gettext" description:"look for WORD as the keyword for singular strings"` + KeywordContext string `long:"keyword-ctx" default:"gettext.PGettext" description:"look for WORD as the keyword for singular strings with context"` + KeywordPlural string `long:"keyword-plural" default:"gettext.NGettext" description:"look for WORD as the keyword for plural strings"` } func main() { diff --git a/main_test.go b/main_test.go index bb5a1ba..abee151 100644 --- a/main_test.go +++ b/main_test.go @@ -54,6 +54,7 @@ func (s *xgettextTestSuite) SetUpTest(c *C) { opts.AddCommentsTag = "TRANSLATORS:" opts.Keyword = "i18n.G" opts.KeywordPlural = "i18n.NG" + opts.KeywordContext = "i18n.GC" opts.SortOutput = true opts.PackageName = "snappy" opts.MsgIDBugsAddress = "snappy-devel@lists.ubuntu.com" @@ -102,6 +103,29 @@ func main() { }) } +func (s *xgettextTestSuite) TestProcessFilesSimpleWithContext(c *C) { + fname := makeGoSourceFile(c, []byte(`package main + +func main() { + // TRANSLATORS: foo comment + i18n.GC("foo", "foo context") +} +`)) + err := processFiles([]string{fname}) + c.Assert(err, IsNil) + + c.Assert(msgIDs, DeepEquals, map[string][]msgID{ + "foo": { + { + comment: "#. TRANSLATORS: foo comment\n", + context: "foo context", + fname: fname, + line: 5, + }, + }, + }) +} + func (s *xgettextTestSuite) TestProcessFilesMultiple(c *C) { fname := makeGoSourceFile(c, []byte(`package main @@ -132,6 +156,38 @@ func main() { }) } +func (s *xgettextTestSuite) TestProcessFilesMultipleWithContext(c *C) { + fname := makeGoSourceFile(c, []byte(`package main + +func main() { + // TRANSLATORS: foo comment + i18n.GC("foo", "foo context") + + // TRANSLATORS: bar comment + i18n.GC("foo", "foo context 2") +} +`)) + err := processFiles([]string{fname}) + c.Assert(err, IsNil) + + c.Assert(msgIDs, DeepEquals, map[string][]msgID{ + "foo": { + { + comment: "#. TRANSLATORS: foo comment\n", + context: "foo context", + fname: fname, + line: 5, + }, + { + comment: "#. TRANSLATORS: bar comment\n", + context: "foo context 2", + fname: fname, + line: 8, + }, + }, + }) +} + const header = `# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. @@ -174,6 +230,31 @@ msgstr "" c.Assert(out.String(), Equals, expected) } +func (s *xgettextTestSuite) TestWriteOutputSimpleWithContext(c *C) { + msgIDs = map[string][]msgID{ + "foo": { + { + fname: "fname", + line: 2, + comment: "#. foo\n", + context: "foo context", + }, + }, + } + out := bytes.NewBuffer([]byte("")) + writePotFile(out) + + expected := fmt.Sprintf(`%s +#. foo +#: fname:2 +msgctxt "foo context" +msgid "foo" +msgstr "" + +`, header) + c.Assert(out.String(), Equals, expected) +} + func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) { msgIDs = map[string][]msgID{ "foo": { @@ -203,6 +284,43 @@ msgstr "" c.Assert(out.String(), Equals, expected) } +func (s *xgettextTestSuite) TestWriteOutputMultipleWithContext(c *C) { + msgIDs = map[string][]msgID{ + "foo": { + { + fname: "fname", + context: "context1", + line: 2, + comment: "#. comment1\n", + }, + { + fname: "fname", + context: "context2", + line: 4, + comment: "#. comment2\n", + }, + }, + } + out := bytes.NewBuffer([]byte("")) + writePotFile(out) + + expected := fmt.Sprintf(`%s +#. comment1 +#: fname:2 +msgctxt "context1" +msgid "foo" +msgstr "" + +#. comment2 +#: fname:4 +msgctxt "context2" +msgid "foo" +msgstr "" + +`, header) + c.Assert(out.String(), Equals, expected) +} + func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) { msgIDs = map[string][]msgID{ "foo": {