Merge lp:~mvo/snappy/snapy-out-own-xgettext into lp:~snappy-dev/snappy/snappy-moved-to-github
- snapy-out-own-xgettext
- Merge into snappy-moved-to-github
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 |
Related bugs: |
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:/
Leo Arias (elopio) wrote : | # |
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"
John Lenton (chipaca) wrote : | # |
Looks good to go; needs fixing a conflict and then set the commit message (i'll top approve now).
Snappy Tarmac (snappydevtarmac) wrote : | # |
Attempt to merge into lp:snappy failed due to conflicts:
text conflict in po/snappy.pot
Michael Vogt (mvo) : | # |
Snappy Tarmac (snappydevtarmac) wrote : | # |
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.
update github.
github.
update github.
github.
update github.
github.
update github.
github.
update github.
github.
update golang.org/x/crypto failed; trying to fetch newer version
github.
update gopkg.in/check.v1 failed; trying to fetch newer version
golang.org/x/crypto now at 60052bd85f2d912
update gopkg.in/yaml.v2 failed; trying to fetch newer version
gopkg.in/check.v1 now at 64131543e7896d5
gopkg.in/yaml.v2 now at 49c95bdc2184325
Building
Running tests from /tmp/tmp.
=== RUN Test
OK: 16 passed
--- PASS: Test (0.35 seconds)
PASS
coverage: 74.8% of statements
ok launchpad.
=== 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.
=== RUN Test
OK: 24 passed
--- PASS: Test (0.06 seconds)
PASS
coverage: 100.0% of statements
ok launchpad.
=== RUN Test
OK: 55 passed
--- PASS: Test (0.30 seconds)
PASS
coverage: 81.4% of statements
ok launchpad.
=== RUN Test
OK: 0 passed, 2 skipped
--- PASS: Test (0.01 seconds)
PASS
coverage: 33.3% of statements
ok launchpad.
=== RUN Test
OK: 11 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 93.4% of statements
ok launchpad.
=== RUN Test
OK: 6 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 93.5% of statements
ok launchpad.
=== RUN Test
OK: 4 passed
--- PASS: Test (0.00 seconds)
PASS
coverage: 100.0% of statements
ok launchpad.
=== RUN Test
OK: 39 passed
--- PASS: Test (0.35 seconds)
PASS
coverage: 79.1% of statements
ok launchpad.
=== RUN Test
OK: 3 passed
--- PASS: Test (0.00 seconds)
PAS...
Michael Vogt (mvo) : | # |
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
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 |
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.