Merge lp:~mvo/snappy/snapy-out-own-xgettext into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
Status: Merged
Approved by: John Lenton
Approved revision: 572
Merged at revision: 608
Proposed branch: lp:~mvo/snappy/snapy-out-own-xgettext
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 859 lines (+709/-29)
4 files modified
i18n/xgettext-go/main.go (+269/-0)
i18n/xgettext-go/main_test.go (+392/-0)
po/snappy.pot (+28/-23)
update-pot (+20/-6)
To merge this branch: bzr merge lp:~mvo/snappy/snapy-out-own-xgettext
Reviewer Review Type Date Requested Status
Michael Vogt (community) Approve
John Lenton (community) Approve
Review via email: mp+263659@code.launchpad.net

Commit message

Use a native (go) xgettext to get translation support for go string of the `` type.

Description of the change

This is a bit of a RFC branch.

It implements a native xgettext to allow us to extract strings that use the `` style. I looked into what it would take to add that to the gettext upstream xgettext.c but that looks like it will be a lot more work. The powerful go ast package makes this pretty straightforward to implement. I send a wishlist bugreport to https://github.com/gosexy/gettext/issues/3 and intend to eventually move it upstream (if upstream is open for this).

To post a comment you must log in.
Revision history for this message
Leo Arias (elopio) wrote :

I think this works for our purposes. We lose some niceties that gettext has, like optional location, sorting the messages and choosing the translators comment tag; but maybe we don't need those. Or are you planning to add more features?

There's one thing that gettext does nicely that I would miss, that's not duplicating the messages with the same string: http://git.savannah.gnu.org/cgit/gettext.git/tree/gettext-tools/tests/xgettext-7

I've a comment on the diff.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for your excellent comments Leo! I think most of this is addressed now (tests are not quite where I want them to be though :/), I wonder if our version should also implement the normalization for the lines,
i.e.
 "very-long-line"
is split by gettext into
 "very"
 "long"
 "line"

Revision history for this message
John Lenton (chipaca) wrote :

Looks good to go; needs fixing a conflict and then set the commit message (i'll top approve now).

review: Approve
Revision history for this message
Snappy Tarmac (snappydevtarmac) wrote :

Attempt to merge into lp:snappy failed due to conflicts:

text conflict in po/snappy.pot

Revision history for this message
Michael Vogt (mvo) :
review: Approve
Revision history for this message
Snappy Tarmac (snappydevtarmac) wrote :
Download full text (6.5 KiB)

The attempt to merge lp:~mvo/snappy/snapy-out-own-xgettext into lp:snappy failed. Below is the output from the failed tests.

Checking docs
Checking formatting
Installing godeps
Install golint
Obtaining dependencies
update github.com/gosexy/gettext failed; trying to fetch newer version
update github.com/mvo5/goconfigparser failed; trying to fetch newer version
github.com/gosexy/gettext now at 98b7b91596d20b96909e6b60d57411547dd9959c
update github.com/blakesmith/ar failed; trying to fetch newer version
github.com/mvo5/goconfigparser now at 26426272dda20cc76aa1fa44286dc743d2972fe8
update github.com/cheggaaa/pb failed; trying to fetch newer version
github.com/blakesmith/ar now at c9a977dd0cc1392b023382c7bfa5a22af8d3b730
update github.com/jessevdk/go-flags failed; trying to fetch newer version
github.com/cheggaaa/pb now at e8c7cc515bfde3e267957a3b110080ceed51354e
update github.com/mvo5/uboot-go failed; trying to fetch newer version
github.com/jessevdk/go-flags now at 15347ef417a300349807983f15af9e65cd2e1b3a
update golang.org/x/crypto failed; trying to fetch newer version
github.com/mvo5/uboot-go now at 361f6ebcbb54f389d15dc9faefa000e996ba3e37
update gopkg.in/check.v1 failed; trying to fetch newer version
golang.org/x/crypto now at 60052bd85f2d91293457e8811b0cf26b773de469
update gopkg.in/yaml.v2 failed; trying to fetch newer version
gopkg.in/check.v1 now at 64131543e7896d5bcc6bd5a76287eb75ea96c673
gopkg.in/yaml.v2 now at 49c95bdc21843256fb6c4e0d370a05f24a0bf213
Building
Running tests from /tmp/tmp.jixSzlUJXd/src/launchpad.net/snappy
=== RUN Test
OK: 16 passed
--- PASS: Test (0.35 seconds)
PASS
coverage: 74.8% of statements
ok launchpad.net/snappy/clickdeb 0.353s coverage: 74.8% of statements
=== RUN Test
2015/07/24 12:02:08.786595 common.go:40: PANIC can not set option description for "package name"
OK: 9 passed
--- PASS: Test (0.02 seconds)
PASS
coverage: 20.0% of statements
ok launchpad.net/snappy/cmd/snappy 0.031s coverage: 20.0% of statements
=== RUN Test
OK: 24 passed
--- PASS: Test (0.06 seconds)
PASS
coverage: 100.0% of statements
ok launchpad.net/snappy/coreconfig 0.060s coverage: 100.0% of statements
=== RUN Test
OK: 55 passed
--- PASS: Test (0.30 seconds)
PASS
coverage: 81.4% of statements
ok launchpad.net/snappy/helpers 0.307s coverage: 81.4% of statements
=== RUN Test
OK: 0 passed, 2 skipped
--- PASS: Test (0.01 seconds)
PASS
coverage: 33.3% of statements
ok launchpad.net/snappy/i18n 0.013s coverage: 33.3% of statements
=== RUN Test
OK: 11 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 93.4% of statements
ok launchpad.net/snappy/i18n/xgettext-go 0.015s coverage: 93.4% of statements
=== RUN Test
OK: 6 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 93.5% of statements
ok launchpad.net/snappy/logger 0.015s coverage: 93.5% of statements
=== RUN Test
OK: 4 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 100.0% of statements
ok launchpad.net/snappy/oauth 0.005s coverage: 100.0% of statements
=== RUN Test
OK: 39 passed
--- PASS: Test (0.35 seconds)
PASS
coverage: 79.1% of statements
ok launchpad.net/snappy/partition 0.355s coverage: 79.1% of statements
=== RUN Test
OK: 3 passed
--- PASS: Test (0.00 seconds)
PAS...

Read more...

Revision history for this message
Michael Vogt (mvo) :
review: Approve
Revision history for this message
Snappy Tarmac (snappydevtarmac) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'i18n/xgettext-go'
2=== added file 'i18n/xgettext-go/main.go'
3--- i18n/xgettext-go/main.go 1970-01-01 00:00:00 +0000
4+++ i18n/xgettext-go/main.go 2015-07-24 12:08:04 +0000
5@@ -0,0 +1,269 @@
6+package main
7+
8+import (
9+ "fmt"
10+ "go/ast"
11+ "go/parser"
12+ "go/token"
13+ "io"
14+ "io/ioutil"
15+ "log"
16+ "os"
17+ "sort"
18+ "strings"
19+ "time"
20+
21+ "github.com/jessevdk/go-flags"
22+)
23+
24+type msgID struct {
25+ msgidPlural string
26+ comment string
27+ fname string
28+ line int
29+ formatHint string
30+}
31+
32+var msgIDs map[string][]msgID
33+
34+func formatComment(com string) string {
35+ out := ""
36+ for _, rawline := range strings.Split(com, "\n") {
37+ line := rawline
38+ line = strings.TrimPrefix(line, "//")
39+ line = strings.TrimPrefix(line, "/*")
40+ line = strings.TrimSuffix(line, "*/")
41+ line = strings.TrimSpace(line)
42+ if line != "" {
43+ out += fmt.Sprintf("#. %s\n", line)
44+ }
45+ }
46+
47+ return out
48+}
49+
50+func findCommentsForTranslation(fset *token.FileSet, f *ast.File, posCall token.Position) string {
51+ com := ""
52+ for _, cg := range f.Comments {
53+ // search for all comments in the previous line
54+ for i := len(cg.List) - 1; i >= 0; i-- {
55+ c := cg.List[i]
56+
57+ posComment := fset.Position(c.End())
58+ //println(posCall.Line, posComment.Line, c.Text)
59+ if posCall.Line == posComment.Line+1 {
60+ posCall = posComment
61+ com = fmt.Sprintf("%s\n%s", c.Text, com)
62+ }
63+ }
64+ }
65+
66+ // only return if we have a matching prefix
67+ formatedComment := formatComment(com)
68+ needle := fmt.Sprintf("#. %s", opts.AddCommentsTag)
69+ if !strings.HasPrefix(formatedComment, needle) {
70+ formatedComment = ""
71+ }
72+
73+ return formatedComment
74+}
75+
76+func inspectNodeForTranslations(fset *token.FileSet, f *ast.File, n ast.Node) bool {
77+ // FIXME: this assume we always have a "gettext.Gettext" style keyword
78+ l := strings.Split(opts.Keyword, ".")
79+ gettextSelector := l[0]
80+ gettextFuncName := l[1]
81+
82+ l = strings.Split(opts.KeywordPlural, ".")
83+ gettextSelectorPlural := l[0]
84+ gettextFuncNamePlural := l[1]
85+
86+ switch x := n.(type) {
87+ case *ast.CallExpr:
88+ if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
89+ i18nStr := ""
90+ i18nStrPlural := ""
91+ if sel.Sel.Name == gettextFuncNamePlural && sel.X.(*ast.Ident).Name == gettextSelectorPlural {
92+ i18nStr = x.Args[0].(*ast.BasicLit).Value
93+ i18nStrPlural = x.Args[1].(*ast.BasicLit).Value
94+ }
95+
96+ if sel.Sel.Name == gettextFuncName && sel.X.(*ast.Ident).Name == gettextSelector {
97+ i18nStr = x.Args[0].(*ast.BasicLit).Value
98+ }
99+
100+ formatI18nStr := func(s string) string {
101+ if s == "" {
102+ return ""
103+ }
104+ // strip " (or `)
105+ s = s[1 : len(s)-1]
106+ return strings.Replace(s, "\n", "\\n", -1)
107+ }
108+
109+ // FIXME: too simplistic(?), no %% is considered
110+ formatHint := ""
111+ if strings.Contains(i18nStr, "%") || strings.Contains(i18nStrPlural, "%") {
112+ // well, not quite correct but close enough
113+ formatHint = "c-format"
114+ }
115+
116+ if i18nStr != "" {
117+ msgidStr := formatI18nStr(i18nStr)
118+ posCall := fset.Position(n.Pos())
119+ msgIDs[msgidStr] = append(msgIDs[msgidStr], msgID{
120+ formatHint: formatHint,
121+ msgidPlural: formatI18nStr(i18nStrPlural),
122+ fname: posCall.Filename,
123+ line: posCall.Line,
124+ comment: findCommentsForTranslation(fset, f, posCall),
125+ })
126+ }
127+ }
128+ }
129+
130+ return true
131+}
132+
133+func processFiles(args []string) error {
134+ // go over the input files
135+ msgIDs = make(map[string][]msgID)
136+
137+ fset := token.NewFileSet()
138+ for _, fname := range args {
139+ if err := processSingleGoSource(fset, fname); err != nil {
140+ return err
141+ }
142+ }
143+
144+ return nil
145+}
146+
147+func processSingleGoSource(fset *token.FileSet, fname string) error {
148+ fnameContent, err := ioutil.ReadFile(fname)
149+ if err != nil {
150+ panic(err)
151+ }
152+
153+ // Create the AST by parsing src.
154+ f, err := parser.ParseFile(fset, fname, fnameContent, parser.ParseComments)
155+ if err != nil {
156+ panic(err)
157+ }
158+
159+ ast.Inspect(f, func(n ast.Node) bool {
160+ return inspectNodeForTranslations(fset, f, n)
161+ })
162+
163+ return nil
164+}
165+
166+var formatTime = func() string {
167+ return time.Now().Format("2006-01-02 15:04-0700")
168+}
169+
170+func writePotFile(out io.Writer) {
171+
172+ header := fmt.Sprintf(`# SOME DESCRIPTIVE TITLE.
173+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
174+# This file is distributed under the same license as the PACKAGE package.
175+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
176+#
177+#, fuzzy
178+msgid ""
179+msgstr "Project-Id-Version: %s\n"
180+ "Report-Msgid-Bugs-To: %s\n"
181+ "POT-Creation-Date: %s\n"
182+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
183+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
184+ "Language-Team: LANGUAGE <LL@li.org>\n"
185+ "Language: \n"
186+ "MIME-Version: 1.0\n"
187+ "Content-Type: text/plain; charset=CHARSET\n"
188+ "Content-Transfer-Encoding: 8bit\n"
189+
190+`, opts.PackageName, opts.MsgIDBugsAddress, formatTime())
191+ fmt.Fprintf(out, "%s", header)
192+
193+ // yes, this is the way to do it in go
194+ sortedKeys := []string{}
195+ for k := range msgIDs {
196+ sortedKeys = append(sortedKeys, k)
197+ }
198+ if opts.SortOutput {
199+ sort.Strings(sortedKeys)
200+ }
201+
202+ // FIXME: use template here?
203+ for _, k := range sortedKeys {
204+ msgidList := msgIDs[k]
205+ for _, msgid := range msgidList {
206+ if opts.AddComments || opts.AddCommentsTag != "" {
207+ fmt.Fprintf(out, "%s", msgid.comment)
208+ }
209+ }
210+ if !opts.NoLocation {
211+ fmt.Fprintf(out, "#:")
212+ for _, msgid := range msgidList {
213+ fmt.Fprintf(out, " %s:%d", msgid.fname, msgid.line)
214+ }
215+ fmt.Fprintf(out, "\n")
216+ }
217+ msgid := msgidList[0]
218+ if msgid.formatHint != "" {
219+ fmt.Fprintf(out, "#, %s\n", msgid.formatHint)
220+ }
221+ fmt.Fprintf(out, "msgid \"%v\"\n", k)
222+ if msgid.msgidPlural != "" {
223+ fmt.Fprintf(out, "msgid_plural \"%v\"\n", msgid.msgidPlural)
224+ fmt.Fprintf(out, "msgstr[0] \"\"\n")
225+ fmt.Fprintf(out, "msgstr[1] \"\"\n")
226+ } else {
227+ fmt.Fprintf(out, "msgstr \"\"\n")
228+ }
229+ fmt.Fprintf(out, "\n")
230+ }
231+
232+}
233+
234+// FIXME: this must be setable via go-flags
235+var opts struct {
236+ Output string `short:"o" long:"output" description:"output to specified file"`
237+
238+ AddComments bool `short:"c" long:"add-comments" description:"place all comment blocks preceding keyword lines in output file"`
239+
240+ AddCommentsTag string `long:"add-comments-tag" description:"place comment blocks starting with TAG and prceding keyword lines in output file"`
241+
242+ SortOutput bool `short:"s" long:"sort-output" description:"generate sorted output"`
243+
244+ NoLocation bool `long:"no-location" description:"do not write '#: filename:line' lines"`
245+
246+ MsgIDBugsAddress string `long:"msgid-bugs-address" default:"EMAIL" description:"set report address for msgid bugs"`
247+
248+ PackageName string `long:"package-name" description:"set package name in output"`
249+
250+ Keyword string `short:"k" long:"keyword" default:"gettext.Gettext" description:"look for WORD as the keyword for singular strings"`
251+ KeywordPlural string `long:"keyword-plural" default:"gettext.NGettext" description:"look for WORD as the keyword for plural strings"`
252+}
253+
254+func main() {
255+ // parse args
256+ args, err := flags.ParseArgs(&opts, os.Args)
257+ if err != nil {
258+ log.Fatalf("ParseArgs failed %s", err)
259+ }
260+
261+ if err := processFiles(args[1:]); err != nil {
262+ log.Fatalf("processFiles failed with: %s", err)
263+ }
264+
265+ out := os.Stdout
266+ if opts.Output != "" {
267+ var err error
268+ out, err = os.Create(opts.Output)
269+ if err != nil {
270+ log.Fatalf("failed to create %s: %s", opts.Output, err)
271+ }
272+ }
273+ writePotFile(out)
274+}
275
276=== added file 'i18n/xgettext-go/main_test.go'
277--- i18n/xgettext-go/main_test.go 1970-01-01 00:00:00 +0000
278+++ i18n/xgettext-go/main_test.go 2015-07-24 12:08:04 +0000
279@@ -0,0 +1,392 @@
280+// -*- Mode: Go; indent-tabs-mode: t -*-
281+
282+/*
283+ * Copyright (C) 2014-2015 Canonical Ltd
284+ *
285+ * This program is free software: you can redistribute it and/or modify
286+ * it under the terms of the GNU General Public License version 3 as
287+ * published by the Free Software Foundation.
288+ *
289+ * This program is distributed in the hope that it will be useful,
290+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
291+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
292+ * GNU General Public License for more details.
293+ *
294+ * You should have received a copy of the GNU General Public License
295+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
296+ *
297+ */
298+
299+package main
300+
301+import (
302+ "bytes"
303+ "fmt"
304+ "io/ioutil"
305+ "os"
306+ "path/filepath"
307+ "testing"
308+
309+ . "gopkg.in/check.v1"
310+)
311+
312+// Hook up check.v1 into the "go test" runner
313+func Test(t *testing.T) { TestingT(t) }
314+
315+type xgettextTestSuite struct {
316+}
317+
318+var _ = Suite(&xgettextTestSuite{})
319+
320+// test helper
321+func makeGoSourceFile(c *C, content []byte) string {
322+ fname := filepath.Join(c.MkDir(), "foo.go")
323+ err := ioutil.WriteFile(fname, []byte(content), 0644)
324+ c.Assert(err, IsNil)
325+
326+ return fname
327+}
328+
329+func (s *xgettextTestSuite) SetUpTest(c *C) {
330+ // our test defaults
331+ opts.NoLocation = false
332+ opts.AddCommentsTag = "TRANSLATORS:"
333+ opts.Keyword = "i18n.G"
334+ opts.KeywordPlural = "i18n.NG"
335+ opts.SortOutput = true
336+ opts.PackageName = "snappy"
337+ opts.MsgIDBugsAddress = "snappy-devel@lists.ubuntu.com"
338+
339+ // mock time
340+ formatTime = func() string {
341+ return "2015-06-30 14:48+0200"
342+ }
343+}
344+
345+func (s *xgettextTestSuite) TestFormatComment(c *C) {
346+ var tests = []struct {
347+ in string
348+ out string
349+ }{
350+ {in: "// foo ", out: "#. foo\n"},
351+ {in: "/* foo */", out: "#. foo\n"},
352+ {in: "/* foo\n */", out: "#. foo\n"},
353+ {in: "/* foo\nbar */", out: "#. foo\n#. bar\n"},
354+ }
355+
356+ for _, test := range tests {
357+ c.Assert(formatComment(test.in), Equals, test.out)
358+ }
359+}
360+
361+func (s *xgettextTestSuite) TestProcessFilesSimple(c *C) {
362+ fname := makeGoSourceFile(c, []byte(`package main
363+
364+func main() {
365+ // TRANSLATORS: foo comment
366+ i18n.G("foo")
367+}
368+`))
369+ err := processFiles([]string{fname})
370+ c.Assert(err, IsNil)
371+
372+ c.Assert(msgIDs, DeepEquals, map[string][]msgID{
373+ "foo": []msgID{
374+ {
375+ comment: "#. TRANSLATORS: foo comment\n",
376+ fname: fname,
377+ line: 5,
378+ },
379+ },
380+ })
381+}
382+
383+func (s *xgettextTestSuite) TestProcessFilesMultiple(c *C) {
384+ fname := makeGoSourceFile(c, []byte(`package main
385+
386+func main() {
387+ // TRANSLATORS: foo comment
388+ i18n.G("foo")
389+
390+ // TRANSLATORS: bar comment
391+ i18n.G("foo")
392+}
393+`))
394+ err := processFiles([]string{fname})
395+ c.Assert(err, IsNil)
396+
397+ c.Assert(msgIDs, DeepEquals, map[string][]msgID{
398+ "foo": []msgID{
399+ {
400+ comment: "#. TRANSLATORS: foo comment\n",
401+ fname: fname,
402+ line: 5,
403+ },
404+ {
405+ comment: "#. TRANSLATORS: bar comment\n",
406+ fname: fname,
407+ line: 8,
408+ },
409+ },
410+ })
411+}
412+
413+const header = `# SOME DESCRIPTIVE TITLE.
414+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
415+# This file is distributed under the same license as the PACKAGE package.
416+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
417+#
418+#, fuzzy
419+msgid ""
420+msgstr "Project-Id-Version: snappy\n"
421+ "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"
422+ "POT-Creation-Date: 2015-06-30 14:48+0200\n"
423+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
424+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
425+ "Language-Team: LANGUAGE <LL@li.org>\n"
426+ "Language: \n"
427+ "MIME-Version: 1.0\n"
428+ "Content-Type: text/plain; charset=CHARSET\n"
429+ "Content-Transfer-Encoding: 8bit\n"
430+`
431+
432+func (s *xgettextTestSuite) TestWriteOutputSimple(c *C) {
433+ msgIDs = map[string][]msgID{
434+ "foo": []msgID{
435+ {
436+ fname: "fname",
437+ line: 2,
438+ comment: "#. foo\n",
439+ },
440+ },
441+ }
442+ out := bytes.NewBuffer([]byte(""))
443+ writePotFile(out)
444+
445+ expected := fmt.Sprintf(`%s
446+#. foo
447+#: fname:2
448+msgid "foo"
449+msgstr ""
450+
451+`, header)
452+ c.Assert(out.String(), Equals, expected)
453+}
454+
455+func (s *xgettextTestSuite) TestWriteOutputMultiple(c *C) {
456+ msgIDs = map[string][]msgID{
457+ "foo": []msgID{
458+ {
459+ fname: "fname",
460+ line: 2,
461+ comment: "#. comment1\n",
462+ },
463+ {
464+ fname: "fname",
465+ line: 4,
466+ comment: "#. comment2\n",
467+ },
468+ },
469+ }
470+ out := bytes.NewBuffer([]byte(""))
471+ writePotFile(out)
472+
473+ expected := fmt.Sprintf(`%s
474+#. comment1
475+#. comment2
476+#: fname:2 fname:4
477+msgid "foo"
478+msgstr ""
479+
480+`, header)
481+ c.Assert(out.String(), Equals, expected)
482+}
483+
484+func (s *xgettextTestSuite) TestWriteOutputNoComment(c *C) {
485+ msgIDs = map[string][]msgID{
486+ "foo": []msgID{
487+ {
488+ fname: "fname",
489+ line: 2,
490+ },
491+ },
492+ }
493+ out := bytes.NewBuffer([]byte(""))
494+ writePotFile(out)
495+
496+ expected := fmt.Sprintf(`%s
497+#: fname:2
498+msgid "foo"
499+msgstr ""
500+
501+`, header)
502+ c.Assert(out.String(), Equals, expected)
503+}
504+
505+func (s *xgettextTestSuite) TestWriteOutputNoLocation(c *C) {
506+ msgIDs = map[string][]msgID{
507+ "foo": []msgID{
508+ {
509+ fname: "fname",
510+ line: 2,
511+ },
512+ },
513+ }
514+
515+ opts.NoLocation = true
516+ out := bytes.NewBuffer([]byte(""))
517+ writePotFile(out)
518+
519+ expected := fmt.Sprintf(`%s
520+msgid "foo"
521+msgstr ""
522+
523+`, header)
524+ c.Assert(out.String(), Equals, expected)
525+}
526+
527+func (s *xgettextTestSuite) TestWriteOutputFormatHint(c *C) {
528+ msgIDs = map[string][]msgID{
529+ "foo": []msgID{
530+ {
531+ fname: "fname",
532+ line: 2,
533+ formatHint: "c-format",
534+ },
535+ },
536+ }
537+
538+ out := bytes.NewBuffer([]byte(""))
539+ writePotFile(out)
540+
541+ expected := fmt.Sprintf(`%s
542+#: fname:2
543+#, c-format
544+msgid "foo"
545+msgstr ""
546+
547+`, header)
548+ c.Assert(out.String(), Equals, expected)
549+}
550+
551+func (s *xgettextTestSuite) TestWriteOutputPlural(c *C) {
552+ msgIDs = map[string][]msgID{
553+ "foo": []msgID{
554+ {
555+ msgidPlural: "plural",
556+ fname: "fname",
557+ line: 2,
558+ },
559+ },
560+ }
561+
562+ out := bytes.NewBuffer([]byte(""))
563+ writePotFile(out)
564+
565+ expected := fmt.Sprintf(`%s
566+#: fname:2
567+msgid "foo"
568+msgid_plural "plural"
569+msgstr[0] ""
570+msgstr[1] ""
571+
572+`, header)
573+ c.Assert(out.String(), Equals, expected)
574+}
575+
576+func (s *xgettextTestSuite) TestWriteOutputSorted(c *C) {
577+ msgIDs = map[string][]msgID{
578+ "aaa": []msgID{
579+ {
580+ fname: "fname",
581+ line: 2,
582+ },
583+ },
584+ "zzz": []msgID{
585+ {
586+ fname: "fname",
587+ line: 2,
588+ },
589+ },
590+ }
591+
592+ opts.SortOutput = true
593+ // we need to run this a bunch of times as the ordering might
594+ // be right by pure chance
595+ for i := 0; i < 10; i++ {
596+ out := bytes.NewBuffer([]byte(""))
597+ writePotFile(out)
598+
599+ expected := fmt.Sprintf(`%s
600+#: fname:2
601+msgid "aaa"
602+msgstr ""
603+
604+#: fname:2
605+msgid "zzz"
606+msgstr ""
607+
608+`, header)
609+ c.Assert(out.String(), Equals, expected)
610+ }
611+}
612+
613+func (s *xgettextTestSuite) TestIntegration(c *C) {
614+ fname := makeGoSourceFile(c, []byte(`package main
615+
616+func main() {
617+ // TRANSLATORS: foo comment
618+ // with multiple lines
619+ i18n.G("foo")
620+
621+ // this comment has no translators tag
622+ i18n.G("abc")
623+
624+ // TRANSLATORS: plural
625+ i18n.NG("singular", "plural", 99)
626+
627+ i18n.G("zz %s")
628+}
629+`))
630+
631+ // a real integration test :)
632+ outName := filepath.Join(c.MkDir(), "snappy.pot")
633+ os.Args = []string{"test-binary",
634+ "--output", outName,
635+ "--keyword", "i18n.G",
636+ "--keyword-plural", "i18n.NG",
637+ "--msgid-bugs-address", "snappy-devel@lists.ubuntu.com",
638+ "--package-name", "snappy",
639+ fname,
640+ }
641+ main()
642+
643+ // verify its what we expect
644+ got, err := ioutil.ReadFile(outName)
645+ c.Assert(err, IsNil)
646+ expected := fmt.Sprintf(`%s
647+#: %[2]s:9
648+msgid "abc"
649+msgstr ""
650+
651+#. TRANSLATORS: foo comment
652+#. with multiple lines
653+#: %[2]s:6
654+msgid "foo"
655+msgstr ""
656+
657+#. TRANSLATORS: plural
658+#: %[2]s:12
659+msgid "singular"
660+msgid_plural "plural"
661+msgstr[0] ""
662+msgstr[1] ""
663+
664+#: %[2]s:14
665+#, c-format
666+msgid "zz %%s"
667+msgstr ""
668+
669+`, header, fname)
670+ c.Assert(string(got), Equals, expected)
671+}
672
673=== modified file 'po/snappy.pot'
674--- po/snappy.pot 2015-07-08 08:00:32 +0000
675+++ po/snappy.pot 2015-07-24 12:08:04 +0000
676@@ -7,7 +7,7 @@
677 msgid ""
678 msgstr "Project-Id-Version: snappy\n"
679 "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n"
680- "POT-Creation-Date: 2015-07-08 10:00+0200\n"
681+ "POT-Creation-Date: 2015-07-24 14:07+0200\n"
682 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
683 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
684 "Language-Team: LANGUAGE <LL@li.org>\n"
685@@ -16,16 +16,16 @@
686 "Content-Type: text/plain; charset=CHARSET\n"
687 "Content-Transfer-Encoding: 8bit\n"
688
689+#. TRANSLATORS: the %s is a pkgname, the second a comma separated list of paths
690+#, c-format
691+msgid "%s: %s\n"
692+msgstr ""
693+
694 #. TRANSLATORS: the %s stand for "name", "version", "description"
695 #, c-format
696 msgid "%s\t%s\t%s (forks not shown: %d)\t"
697 msgstr ""
698
699-#. TRANSLATORS: the %s is a pkgname, the second a comma separated list of paths
700-#, c-format
701-msgid "%s: %s\n"
702-msgstr ""
703-
704 #. TRANSLATORS: the first %s is a pkgname, the second %s is a path
705 #, c-format
706 msgid "'%s' is no longer allowed to access '%s'\n"
707@@ -52,10 +52,10 @@
708 msgid "2fa code: "
709 msgstr ""
710
711-msgid "Allows rollback of a snap to a previous installed version. Without "
712- "any arguments, the previous installed version is selected. It is "
713- "also possible to specify the version to rollback to as a additional "
714- "argument.\n"
715+msgid "A concise summary of key attributes of the snappy system, such as the release and channel.\n\nThe verbose output includes the specific version information for the factory image, the running image and the image that will be run on reboot, together with a list of the available channels for this image.\n\nProviding a package name will display information about a specific installed package.\n\nThe verbose version of the info command for a package will also tell you the available channels for that package, when it was installed for the first time, disk space utilization, and in the case of frameworks, which apps are able to use the framework."
716+msgstr ""
717+
718+msgid "Allows rollback of a snap to a previous installed version. Without any arguments, the previous installed version is selected. It is also possible to specify the version to rollback to as a additional argument.\n"
719 msgstr ""
720
721 msgid "Assign a hardware device to a package"
722@@ -68,13 +68,11 @@
723 msgstr ""
724
725 #. TRANSLATORS: the first %q is the file that can not be read and %v is the error message
726+#, c-format
727 msgid "Can't read hook file %q: %v"
728 msgstr ""
729
730-msgid "Configures a package. The configuration is a YAML file, provided in "
731- "the specified file which can be \"-\" for stdin. Output of the "
732- "command is the current configuration, so running this command with "
733- "no input file provides a snapshot of the app's current config."
734+msgid "Configures a package. The configuration is a YAML file, provided in the specified file which can be \"-\" for stdin. Output of the command is the current configuration, so running this command with no input file provides a snapshot of the app's current config."
735 msgstr ""
736
737 msgid "Creates a snap package and if available, runs the review scripts."
738@@ -143,6 +141,9 @@
739 msgid "Provide information about a specific installed package"
740 msgstr ""
741
742+msgid "Provides a list of all active components installed on a snappy system.\n\nIf requested, the command will find out if there are updates for any of the components and indicate that by appending a * to the date. This will be slower as it requires a round trip to the app store on the network.\n\nThe developer information refers to non-mainline versions of a package (much like PPAs in deb-based Ubuntu). If the package is the primary version of that package in Ubuntu then the developer info is not shown. This allows one to identify packages which have custom, non-standard versions installed. As a special case, the “sideload” developer refers to packages installed manually on the system.\n\nWhen a verbose listing is requested, information about the channel used is displayed; which is one of alpha, beta, rc or stable, and all fields are fully expanded too. In some cases, older (inactive) versions of snappy packages will be installed, these will be shown in the verbose output and the active version indicated with a * appended to the name of the component."
743+msgstr ""
744+
745 msgid "Provides more detailed information"
746 msgstr ""
747
748@@ -182,6 +183,9 @@
749 msgid "Remove all the data from the listed packages"
750 msgstr ""
751
752+msgid "Remove all the data from the listed packages. Normally this is used for packages that have been removed and attempting to purge data for an installed package will result in an error. The --installed option overrides that and enables the administrator to purge all data for an installed package (effectively resetting the package completely)."
753+msgstr ""
754+
755 msgid "Remove hardware from a specific installed package"
756 msgstr ""
757
758@@ -205,6 +209,9 @@
759 msgid "Set properties of system or package"
760 msgstr ""
761
762+msgid "Set properties of system or package\n\nSupported properties are:\n active=VERSION\n\nExample:\n set hello-world active=1.0\n"
763+msgstr ""
764+
765 #. TRANSLATORS: the first %s is a pkgname, the second %s is the new version
766 #, c-format
767 msgid "Setting %s to version %s\n"
768@@ -222,6 +229,9 @@
769 msgid "Specify an alternate output directory for the resulting package"
770 msgstr ""
771
772+msgid "The "versions" command is no longer available.\n\nPlease use the "list" command instead to see what is installed.\nThe "list -u" (or "list --updates") will show you the available updates\nand "list -v" (or "list --verbose") will show all installed versions.\n"
773+msgstr ""
774+
775 msgid "The Package to install (name or path)"
776 msgstr ""
777
778@@ -240,8 +250,7 @@
779 msgid "The version to rollback to"
780 msgstr ""
781
782-msgid "This command adds access to a specific hardware device (e.g. /dev/"
783- "ttyUSB0) for an installed package."
784+msgid "This command adds access to a specific hardware device (e.g. /dev/ttyUSB0) for an installed package."
785 msgstr ""
786
787 msgid "This command is no longer available, please use the \"list\" command"
788@@ -253,8 +262,7 @@
789 msgid "This command logs the given username into the store"
790 msgstr ""
791
792-msgid "This command removes access of a specific hardware device (e.g. /dev/"
793- "ttyUSB0) for an installed package."
794+msgid "This command removes access of a specific hardware device (e.g. /dev/ttyUSB0) for an installed package."
795 msgstr ""
796
797 msgid "Unassign a hardware device to a package"
798@@ -281,6 +289,7 @@
799 msgstr ""
800
801 #. TRANSLATORS: the %s is a size
802+#, c-format
803 msgid "binary-size: %v\n"
804 msgstr ""
805
806@@ -312,9 +321,7 @@
807 msgid "release: %s\n"
808 msgstr ""
809
810-msgid "snappy autopilot triggered a reboot to boot into an up to date "
811- "system -- temprorarily disable the reboot by running 'sudo shutdown -"
812- "c'"
813+msgid "snappy autopilot triggered a reboot to boot into an up to date system -- temprorarily disable the reboot by running 'sudo shutdown -c'"
814 msgstr ""
815
816 #. TRANSLATORS: the %s is a date
817@@ -327,5 +334,3 @@
818 msgid "version: %s\n"
819 msgstr ""
820
821-msgid "versions"
822-msgstr ""
823
824=== modified file 'update-pot'
825--- update-pot 2015-07-02 17:16:11 +0000
826+++ update-pot 2015-07-24 12:08:04 +0000
827@@ -10,12 +10,26 @@
828 OUTPUT="$1"
829 fi
830
831-xgettext -d snappy -o "$OUTPUT" --c++ --from-code=UTF-8 \
832- --indent --add-comments=TRANSLATORS: --no-location --sort-output \
833- --package-name=snappy \
834- --msgid-bugs-address=snappy-devel@lists.ubuntu.com \
835- --keyword=NG:1,2 --keyword=G \
836- $HERE/*/*.go $HERE/cmd/*/*.go
837+# ensure we have it
838+go install launchpad.net/snappy/i18n/xgettext-go
839+
840+$GOPATH/bin/xgettext-go \
841+ -o "$OUTPUT" \
842+ --add-comments-tag=TRANSLATORS: \
843+ --no-location \
844+ --sort-output \
845+ --package-name=snappy\
846+ --msgid-bugs-address=snappy-devel@lists.ubuntu.com \
847+ --keyword=i18n.G \
848+ --keyword-plural=i18n.DG \
849+ $HERE/*/*.go $HERE/cmd/*/*.go
850+
851+#xgettext -d snappy -o "$OUTPUT" --c++ --from-code=UTF-8 \
852+# --indent --add-comments=TRANSLATORS: --no-location --sort-output \
853+# --package-name=snappy \
854+# --msgid-bugs-address=snappy-devel@lists.ubuntu.com \
855+# --keyword=NG:1,2 --keyword=G \
856+# $HERE/*/*.go $HERE/cmd/*/*.go
857
858 # language packs
859 for p in ${HERE}/po/*.po; do

Subscribers

People subscribed via source and target branches