feat: msgctxt #1
13
.drone.yml
Normal file
13
.drone.yml
Normal file
@ -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
|
@ -1,4 +1,6 @@
|
|||||||
# xgettext-go
|
# xgettext-go
|
||||||
|
|
||||||
|
[](https://build.coopcloud.tech/toolshed/xgettext-go)
|
||||||
|
|
||||||
* [Forked from here](https://github.com/canonical/snapd/tree/master/i18n/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)
|
* [The reason why](https://git.coopcloud.tech/toolshed/abra/issues/647)
|
||||||
|
131
main.go
131
main.go
@ -20,6 +20,7 @@ import (
|
|||||||
type msgID struct {
|
type msgID struct {
|
||||||
msgidPlural string
|
msgidPlural string
|
||||||
comment string
|
comment string
|
||||||
|
context string
|
||||||
fname string
|
fname string
|
||||||
line int
|
line int
|
||||||
formatHint string
|
formatHint string
|
||||||
@ -102,16 +103,27 @@ func inspectNodeForTranslations(fset *token.FileSet, f *ast.File, n ast.Node) bo
|
|||||||
gettextSelectorPlural := l[0]
|
gettextSelectorPlural := l[0]
|
||||||
gettextFuncNamePlural := l[1]
|
gettextFuncNamePlural := l[1]
|
||||||
|
|
||||||
|
l = strings.Split(opts.KeywordContext, ".")
|
||||||
|
gettextSelectorContext := l[0]
|
||||||
|
gettextFuncNameContext := l[1]
|
||||||
|
|
||||||
switch x := n.(type) {
|
switch x := n.(type) {
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
|
if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
|
||||||
i18nStr := ""
|
i18nStr := ""
|
||||||
i18nStrPlural := ""
|
i18nStrPlural := ""
|
||||||
|
i18nStrContext := ""
|
||||||
|
|
||||||
if sel.Sel.Name == gettextFuncNamePlural && sel.X.(*ast.Ident).Name == gettextSelectorPlural {
|
if sel.Sel.Name == gettextFuncNamePlural && sel.X.(*ast.Ident).Name == gettextSelectorPlural {
|
||||||
i18nStr = x.Args[0].(*ast.BasicLit).Value
|
i18nStr = x.Args[0].(*ast.BasicLit).Value
|
||||||
i18nStrPlural = x.Args[1].(*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 {
|
if sel.Sel.Name == gettextFuncName && sel.X.(*ast.Ident).Name == gettextSelector {
|
||||||
i18nStr = constructValue(x.Args[0])
|
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{
|
msgIDs[msgidStr] = append(msgIDs[msgidStr], msgID{
|
||||||
formatHint: formatHint,
|
formatHint: formatHint,
|
||||||
msgidPlural: formatI18nStr(i18nStrPlural),
|
msgidPlural: formatI18nStr(i18nStrPlural),
|
||||||
|
context: formatI18nStr(i18nStrContext),
|
||||||
fname: posCall.Filename,
|
fname: posCall.Filename,
|
||||||
line: posCall.Line,
|
line: posCall.Line,
|
||||||
comment: findCommentsForTranslation(fset, f, posCall),
|
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) {
|
func writePotFile(out io.Writer) {
|
||||||
|
|
||||||
header := fmt.Sprintf(`# SOME DESCRIPTIVE TITLE.
|
header := fmt.Sprintf(`# SOME DESCRIPTIVE TITLE.
|
||||||
@ -253,39 +339,25 @@ msgstr "Project-Id-Version: %s\n"
|
|||||||
|
|
||||||
// FIXME: use template here?
|
// FIXME: use template here?
|
||||||
for _, k := range sortedKeys {
|
for _, k := range sortedKeys {
|
||||||
|
var msgIDsWithoutContext []msgID
|
||||||
|
var msgIDsWithContext []msgID
|
||||||
|
|
||||||
msgidList := msgIDs[k]
|
msgidList := msgIDs[k]
|
||||||
for _, msgid := range msgidList {
|
for _, msgid := range msgidList {
|
||||||
if opts.AddComments || opts.AddCommentsTag != "" {
|
if msgid.context == "" {
|
||||||
mustFprintf(out, "%s", msgid.comment)
|
msgIDsWithoutContext = append(msgIDsWithoutContext, msgid)
|
||||||
|
} else {
|
||||||
|
msgIDsWithContext = append(msgIDsWithContext, msgid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !opts.NoLocation {
|
|
||||||
mustFprintf(out, "#:")
|
if len(msgIDsWithoutContext) > 0 {
|
||||||
for _, msgid := range msgidList {
|
writeMsg(out, k, msgIDsWithoutContext)
|
||||||
mustFprintf(out, " %s:%d", msgid.fname, msgid.line)
|
|
||||||
}
|
|
||||||
mustFprintf(out, "\n")
|
|
||||||
}
|
}
|
||||||
msgid := msgidList[0]
|
|
||||||
if msgid.formatHint != "" {
|
if len(msgIDsWithContext) > 0 {
|
||||||
mustFprintf(out, "#, %s\n", msgid.formatHint)
|
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"`
|
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"`
|
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"`
|
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() {
|
func main() {
|
||||||
|
118
main_test.go
118
main_test.go
@ -54,6 +54,7 @@ func (s *xgettextTestSuite) SetUpTest(c *C) {
|
|||||||
opts.AddCommentsTag = "TRANSLATORS:"
|
opts.AddCommentsTag = "TRANSLATORS:"
|
||||||
opts.Keyword = "i18n.G"
|
opts.Keyword = "i18n.G"
|
||||||
opts.KeywordPlural = "i18n.NG"
|
opts.KeywordPlural = "i18n.NG"
|
||||||
|
opts.KeywordContext = "i18n.GC"
|
||||||
opts.SortOutput = true
|
opts.SortOutput = true
|
||||||
opts.PackageName = "snappy"
|
opts.PackageName = "snappy"
|
||||||
opts.MsgIDBugsAddress = "snappy-devel@lists.ubuntu.com"
|
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) {
|
func (s *xgettextTestSuite) TestProcessFilesMultiple(c *C) {
|
||||||
fname := makeGoSourceFile(c, []byte(`package main
|
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.
|
const header = `# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
@ -174,6 +230,31 @@ msgstr ""
|
|||||||
c.Assert(out.String(), Equals, expected)
|
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) {
|
func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) {
|
||||||
msgIDs = map[string][]msgID{
|
msgIDs = map[string][]msgID{
|
||||||
"foo": {
|
"foo": {
|
||||||
@ -203,6 +284,43 @@ msgstr ""
|
|||||||
c.Assert(out.String(), Equals, expected)
|
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) {
|
func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) {
|
||||||
msgIDs = map[string][]msgID{
|
msgIDs = map[string][]msgID{
|
||||||
"foo": {
|
"foo": {
|
||||||
|
Reference in New Issue
Block a user