aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Carlos <jose.carlos@menteslibres.net>2016-02-07 06:07:42 -0600
committerJosé Carlos <jose.carlos@menteslibres.net>2016-02-07 06:07:42 -0600
commita71245786daed22190964db64b70a9a8c93d44a7 (patch)
tree868c9c083d735f893b6479da615f26a9c7536d4a
parentMerge pull request #2 from sqp/nil_string (diff)
parentAdd native go xgettext (diff)
downloadgotext-a71245786daed22190964db64b70a9a8c93d44a7.tar.gz
gotext-a71245786daed22190964db64b70a9a8c93d44a7.tar.xz
Merge pull request #6 from mvo5/feature/xgettext
Add native go xgettext
-rw-r--r--xgettext/main.go332
-rw-r--r--xgettext/main_test.go523
2 files changed, 855 insertions, 0 deletions
diff --git a/xgettext/main.go b/xgettext/main.go
new file mode 100644
index 0000000..34c4bfe
--- /dev/null
+++ b/xgettext/main.go
@@ -0,0 +1,332 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015-2016 Canonical Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package main
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "io"
+ "io/ioutil"
+ "log"
+ "os"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/jessevdk/go-flags"
+)
+
+type msgID struct {
+ msgidPlural string
+ comment string
+ fname string
+ line int
+ formatHint string
+}
+
+var msgIDs map[string][]msgID
+
+func formatComment(com string) string {
+ out := ""
+ for _, rawline := range strings.Split(com, "\n") {
+ line := rawline
+ line = strings.TrimPrefix(line, "//")
+ line = strings.TrimPrefix(line, "/*")
+ line = strings.TrimSuffix(line, "*/")
+ line = strings.TrimSpace(line)
+ if line != "" {
+ out += fmt.Sprintf("#. %s\n", line)
+ }
+ }
+
+ return out
+}
+
+func findCommentsForTranslation(fset *token.FileSet, f *ast.File, posCall token.Position) string {
+ com := ""
+ for _, cg := range f.Comments {
+ // search for all comments in the previous line
+ for i := len(cg.List) - 1; i >= 0; i-- {
+ c := cg.List[i]
+
+ posComment := fset.Position(c.End())
+ //println(posCall.Line, posComment.Line, c.Text)
+ if posCall.Line == posComment.Line+1 {
+ posCall = posComment
+ com = fmt.Sprintf("%s\n%s", c.Text, com)
+ }
+ }
+ }
+
+ // only return if we have a matching prefix
+ formatedComment := formatComment(com)
+ needle := fmt.Sprintf("#. %s", opts.AddCommentsTag)
+ if !strings.HasPrefix(formatedComment, needle) {
+ formatedComment = ""
+ }
+
+ return formatedComment
+}
+
+func constructValue(val interface{}) string {
+ switch val.(type) {
+ case *ast.BasicLit:
+ return val.(*ast.BasicLit).Value
+ // this happens for constructs like:
+ // gettext.Gettext("foo" + "bar")
+ case *ast.BinaryExpr:
+ // we only support string concat
+ if val.(*ast.BinaryExpr).Op != token.ADD {
+ return ""
+ }
+ left := constructValue(val.(*ast.BinaryExpr).X)
+ // strip right " (or `)
+ left = left[0 : len(left)-1]
+ right := constructValue(val.(*ast.BinaryExpr).Y)
+ // strip left " (or `)
+ right = right[1:len(right)]
+ return left + right
+ default:
+ panic(fmt.Sprintf("unknown type: %v", val))
+ }
+}
+
+func inspectNodeForTranslations(fset *token.FileSet, f *ast.File, n ast.Node) bool {
+ // FIXME: this assume we always have a "gettext.Gettext" style keyword
+ l := strings.Split(opts.Keyword, ".")
+ gettextSelector := l[0]
+ gettextFuncName := l[1]
+
+ l = strings.Split(opts.KeywordPlural, ".")
+ gettextSelectorPlural := l[0]
+ gettextFuncNamePlural := l[1]
+
+ switch x := n.(type) {
+ case *ast.CallExpr:
+ if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
+ i18nStr := ""
+ i18nStrPlural := ""
+ 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 == gettextFuncName && sel.X.(*ast.Ident).Name == gettextSelector {
+ i18nStr = constructValue(x.Args[0])
+ }
+
+ formatI18nStr := func(s string) string {
+ if s == "" {
+ return ""
+ }
+ // the "`" is special
+ if s[0] == '`' {
+ // replace inner " with \"
+ s = strings.Replace(s, "\"", "\\\"", -1)
+ // replace \n with \\n
+ s = strings.Replace(s, "\n", "\\n", -1)
+ }
+ // strip leading and trailing " (or `)
+ s = s[1 : len(s)-1]
+ return s
+ }
+
+ // FIXME: too simplistic(?), no %% is considered
+ formatHint := ""
+ if strings.Contains(i18nStr, "%") || strings.Contains(i18nStrPlural, "%") {
+ // well, not quite correct but close enough
+ formatHint = "c-format"
+ }
+
+ if i18nStr != "" {
+ msgidStr := formatI18nStr(i18nStr)
+ posCall := fset.Position(n.Pos())
+ msgIDs[msgidStr] = append(msgIDs[msgidStr], msgID{
+ formatHint: formatHint,
+ msgidPlural: formatI18nStr(i18nStrPlural),
+ fname: posCall.Filename,
+ line: posCall.Line,
+ comment: findCommentsForTranslation(fset, f, posCall),
+ })
+ }
+ }
+ }
+
+ return true
+}
+
+func processFiles(args []string) error {
+ // go over the input files
+ msgIDs = make(map[string][]msgID)
+
+ fset := token.NewFileSet()
+ for _, fname := range args {
+ if err := processSingleGoSource(fset, fname); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func processSingleGoSource(fset *token.FileSet, fname string) error {
+ fnameContent, err := ioutil.ReadFile(fname)
+ if err != nil {
+ panic(err)
+ }
+
+ // Create the AST by parsing src.
+ f, err := parser.ParseFile(fset, fname, fnameContent, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+
+ ast.Inspect(f, func(n ast.Node) bool {
+ return inspectNodeForTranslations(fset, f, n)
+ })
+
+ return nil
+}
+
+var formatTime = func() string {
+ return time.Now().Format("2006-01-02 15:04-0700")
+}
+
+func writePotFile(out io.Writer) {
+
+ header := fmt.Sprintf(`# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+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 <EMAIL@ADDRESS>\n"
+ "Language-Team: LANGUAGE <LL@li.org>\n"
+ "Language: \n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=CHARSET\n"
+ "Content-Transfer-Encoding: 8bit\n"
+
+`, opts.PackageName, opts.MsgIDBugsAddress, formatTime())
+ fmt.Fprintf(out, "%s", header)
+
+ // yes, this is the way to do it in go
+ sortedKeys := []string{}
+ for k := range msgIDs {
+ sortedKeys = append(sortedKeys, k)
+ }
+ if opts.SortOutput {
+ sort.Strings(sortedKeys)
+ }
+
+ // FIXME: use template here?
+ for _, k := range sortedKeys {
+ msgidList := msgIDs[k]
+ for _, msgid := range msgidList {
+ if opts.AddComments || opts.AddCommentsTag != "" {
+ fmt.Fprintf(out, "%s", msgid.comment)
+ }
+ }
+ if !opts.NoLocation {
+ fmt.Fprintf(out, "#:")
+ for _, msgid := range msgidList {
+ fmt.Fprintf(out, " %s:%d", msgid.fname, msgid.line)
+ }
+ fmt.Fprintf(out, "\n")
+ }
+ msgid := msgidList[0]
+ if msgid.formatHint != "" {
+ fmt.Fprintf(out, "#, %s\n", msgid.formatHint)
+ }
+ 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 \"")
+ }
+ fmt.Fprintf(out, "msgid \"%v\"\n", formatOutput(k))
+ if msgid.msgidPlural != "" {
+ fmt.Fprintf(out, "msgid_plural \"%v\"\n", formatOutput(msgid.msgidPlural))
+ fmt.Fprintf(out, "msgstr[0] \"\"\n")
+ fmt.Fprintf(out, "msgstr[1] \"\"\n")
+ } else {
+ fmt.Fprintf(out, "msgstr \"\"\n")
+ }
+ fmt.Fprintf(out, "\n")
+ }
+
+}
+
+// FIXME: this must be setable via go-flags
+var opts struct {
+ Output string `short:"o" long:"output" description:"output to specified file"`
+
+ AddComments bool `short:"c" long:"add-comments" description:"place all comment blocks preceding keyword lines in output file"`
+
+ AddCommentsTag string `long:"add-comments-tag" description:"place comment blocks starting with TAG and prceding keyword lines in output file"`
+
+ SortOutput bool `short:"s" long:"sort-output" description:"generate sorted output"`
+
+ NoLocation bool `long:"no-location" description:"do not write '#: filename:line' lines"`
+
+ MsgIDBugsAddress string `long:"msgid-bugs-address" default:"EMAIL" description:"set report address for msgid bugs"`
+
+ 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"`
+}
+
+func main() {
+ // parse args
+ args, err := flags.ParseArgs(&opts, os.Args)
+ if err != nil {
+ log.Fatalf("ParseArgs failed %s", err)
+ }
+
+ if err := processFiles(args[1:]); err != nil {
+ log.Fatalf("processFiles failed with: %s", err)
+ }
+
+ out := os.Stdout
+ if opts.Output != "" {
+ var err error
+ out, err = os.Create(opts.Output)
+ if err != nil {
+ log.Fatalf("failed to create %s: %s", opts.Output, err)
+ }
+ }
+ writePotFile(out)
+}
diff --git a/xgettext/main_test.go b/xgettext/main_test.go
new file mode 100644
index 0000000..59159dc
--- /dev/null
+++ b/xgettext/main_test.go
@@ -0,0 +1,523 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015-2016 Canonical Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type xgettextTestSuite struct {
+}
+
+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)
+ c.Assert(err, IsNil)
+
+ return fname
+}
+
+func (s *xgettextTestSuite) SetUpTest(c *C) {
+ // our test defaults
+ opts.NoLocation = false
+ opts.AddCommentsTag = "TRANSLATORS:"
+ opts.Keyword = "i18n.G"
+ opts.KeywordPlural = "i18n.NG"
+ opts.SortOutput = true
+ opts.PackageName = "snappy"
+ opts.MsgIDBugsAddress = "snappy-devel@lists.ubuntu.com"
+
+ // mock time
+ formatTime = func() string {
+ return "2015-06-30 14:48+0200"
+ }
+}
+
+func (s *xgettextTestSuite) TestFormatComment(c *C) {
+ var tests = []struct {
+ in string
+ out string
+ }{
+ {in: "// foo ", out: "#. foo\n"},
+ {in: "/* foo */", out: "#. foo\n"},
+ {in: "/* foo\n */", out: "#. foo\n"},
+ {in: "/* foo\nbar */", out: "#. foo\n#. bar\n"},
+ }
+
+ for _, test := range tests {
+ c.Assert(formatComment(test.in), Equals, test.out)
+ }
+}
+
+func (s *xgettextTestSuite) TestProcessFilesSimple(c *C) {
+ fname := makeGoSourceFile(c, []byte(`package main
+
+func main() {
+ // TRANSLATORS: foo comment
+ i18n.G("foo")
+}
+`))
+ err := processFiles([]string{fname})
+ c.Assert(err, IsNil)
+
+ c.Assert(msgIDs, DeepEquals, map[string][]msgID{
+ "foo": []msgID{
+ {
+ comment: "#. TRANSLATORS: foo comment\n",
+ fname: fname,
+ line: 5,
+ },
+ },
+ })
+}
+
+func (s *xgettextTestSuite) TestProcessFilesMultiple(c *C) {
+ fname := makeGoSourceFile(c, []byte(`package main
+
+func main() {
+ // TRANSLATORS: foo comment
+ i18n.G("foo")
+
+ // TRANSLATORS: bar comment
+ i18n.G("foo")
+}
+`))
+ err := processFiles([]string{fname})
+ c.Assert(err, IsNil)
+
+ c.Assert(msgIDs, DeepEquals, map[string][]msgID{
+ "foo": []msgID{
+ {
+ comment: "#. TRANSLATORS: foo comment\n",
+ fname: fname,
+ line: 5,
+ },
+ {
+ comment: "#. TRANSLATORS: bar comment\n",
+ 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.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+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 <EMAIL@ADDRESS>\n"
+ "Language-Team: LANGUAGE <LL@li.org>\n"
+ "Language: \n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=CHARSET\n"
+ "Content-Transfer-Encoding: 8bit\n"
+`
+
+func (s *xgettextTestSuite) TestWriteOutputSimple(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ comment: "#. foo\n",
+ },
+ },
+ }
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#. foo
+#: fname:2
+msgid "foo"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ comment: "#. comment1\n",
+ },
+ {
+ fname: "fname",
+ line: 4,
+ comment: "#. comment2\n",
+ },
+ },
+ }
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#. comment1
+#. comment2
+#: fname:2 fname:4
+msgid "foo"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ },
+ },
+ }
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#: fname:2
+msgid "foo"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputNoLocation(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ },
+ },
+ }
+
+ opts.NoLocation = true
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+msgid "foo"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputFormatHint(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ formatHint: "c-format",
+ },
+ },
+ }
+
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#: fname:2
+#, c-format
+msgid "foo"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputPlural(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo": []msgID{
+ {
+ msgidPlural: "plural",
+ fname: "fname",
+ line: 2,
+ },
+ },
+ }
+
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#: fname:2
+msgid "foo"
+msgid_plural "plural"
+msgstr[0] ""
+msgstr[1] ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputSorted(c *C) {
+ msgIDs = map[string][]msgID{
+ "aaa": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ },
+ },
+ "zzz": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ },
+ },
+ }
+
+ opts.SortOutput = true
+ // we need to run this a bunch of times as the ordering might
+ // be right by pure chance
+ for i := 0; i < 10; i++ {
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#: fname:2
+msgid "aaa"
+msgstr ""
+
+#: fname:2
+msgid "zzz"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+ }
+}
+
+func (s *xgettextTestSuite) TestIntegration(c *C) {
+ fname := makeGoSourceFile(c, []byte(`package main
+
+func main() {
+ // TRANSLATORS: foo comment
+ // with multiple lines
+ i18n.G("foo")
+
+ // this comment has no translators tag
+ i18n.G("abc")
+
+ // TRANSLATORS: plural
+ i18n.NG("singular", "plural", 99)
+
+ i18n.G("zz %s")
+}
+`))
+
+ // a real integration test :)
+ outName := filepath.Join(c.MkDir(), "snappy.pot")
+ os.Args = []string{"test-binary",
+ "--output", outName,
+ "--keyword", "i18n.G",
+ "--keyword-plural", "i18n.NG",
+ "--msgid-bugs-address", "snappy-devel@lists.ubuntu.com",
+ "--package-name", "snappy",
+ fname,
+ }
+ main()
+
+ // verify its what we expect
+ got, err := ioutil.ReadFile(outName)
+ c.Assert(err, IsNil)
+ expected := fmt.Sprintf(`%s
+#: %[2]s:9
+msgid "abc"
+msgstr ""
+
+#. TRANSLATORS: foo comment
+#. with multiple lines
+#: %[2]s:6
+msgid "foo"
+msgstr ""
+
+#. TRANSLATORS: plural
+#: %[2]s:12
+msgid "singular"
+msgid_plural "plural"
+msgstr[0] ""
+msgstr[1] ""
+
+#: %[2]s:14
+#, c-format
+msgid "zz %%s"
+msgstr ""
+
+`, header, fname)
+ c.Assert(string(got), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestProcessFilesConcat(c *C) {
+ fname := makeGoSourceFile(c, []byte(`package main
+
+func main() {
+ // TRANSLATORS: foo comment
+ i18n.G("foo\n" + "bar\n" + "baz")
+}
+`))
+ err := processFiles([]string{fname})
+ c.Assert(err, IsNil)
+
+ c.Assert(msgIDs, DeepEquals, map[string][]msgID{
+ "foo\\nbar\\nbaz": []msgID{
+ {
+ comment: "#. TRANSLATORS: foo comment\n",
+ fname: fname,
+ line: 5,
+ },
+ },
+ })
+}
+
+func (s *xgettextTestSuite) TestProcessFilesWithQuote(c *C) {
+ fname := makeGoSourceFile(c, []byte(fmt.Sprintf(`package main
+
+func main() {
+ i18n.G(%[1]s foo "bar"%[1]s)
+}
+`, "`")))
+ err := processFiles([]string{fname})
+ c.Assert(err, IsNil)
+
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#: %[2]s:4
+msgid " foo \"bar\""
+msgstr ""
+
+`, header, fname)
+ c.Check(out.String(), Equals, expected)
+
+}
+
+func (s *xgettextTestSuite) TestWriteOutputMultilines(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo\\nbar\\nbaz": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ comment: "#. foo\n",
+ },
+ },
+ }
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+ expected := fmt.Sprintf(`%s
+#. foo
+#: fname:2
+msgid "foo\n"
+ "bar\n"
+ "baz"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestWriteOutputTidy(c *C) {
+ msgIDs = map[string][]msgID{
+ "foo\\nbar\\nbaz": []msgID{
+ {
+ fname: "fname",
+ line: 2,
+ },
+ },
+ "zzz\\n": []msgID{
+ {
+ fname: "fname",
+ line: 4,
+ },
+ },
+ }
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+ expected := fmt.Sprintf(`%s
+#: fname:2
+msgid "foo\n"
+ "bar\n"
+ "baz"
+msgstr ""
+
+#: fname:4
+msgid "zzz\n"
+msgstr ""
+
+`, header)
+ c.Assert(out.String(), Equals, expected)
+}
+
+func (s *xgettextTestSuite) TestProcessFilesWithDoubleQuote(c *C) {
+ fname := makeGoSourceFile(c, []byte(`package main
+
+func main() {
+ i18n.G("foo \"bar\"")
+}
+`))
+ err := processFiles([]string{fname})
+ c.Assert(err, IsNil)
+
+ out := bytes.NewBuffer([]byte(""))
+ writePotFile(out)
+
+ expected := fmt.Sprintf(`%s
+#: %[2]s:4
+msgid "foo \"bar\""
+msgstr ""
+
+`, header, fname)
+ c.Check(out.String(), Equals, expected)
+
+}