Skip to content

Commit b2e5003

Browse files
Merge pull request #82 from didrocks/fix-multiline-export
Fix multiline export
2 parents 1f7d156 + 4d8518b commit b2e5003

File tree

10 files changed

+182
-30
lines changed

10 files changed

+182
-30
lines changed

cli/xgotext/fixtures/i18n/default.po

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,80 @@ msgstr ""
77
"Language: \n"
88
"X-Generator: xgotext\n"
99

10-
11-
#: fixtures/main.go:23
12-
#. gotext.Get
10+
#: fixtures/main.go:35
11+
#: fixtures/main.go:37
1312
msgid "My text on 'domain-name' domain"
1413
msgstr ""
1514

16-
#: fixtures/main.go:38
17-
#. l.GetN
15+
#: fixtures/main.go:75
1816
msgid "Singular"
1917
msgid_plural "Plural"
2018
msgstr[0] ""
2119
msgstr[1] ""
2220

23-
#: fixtures/main.go:40
24-
#. l.GetN
21+
#: fixtures/main.go:77
2522
msgid "SingularVar"
2623
msgid_plural "PluralVar"
2724
msgstr[0] ""
2825
msgstr[1] ""
26+
27+
#: fixtures/main.go:44
28+
msgid "alias call"
29+
msgstr ""
30+
31+
#: fixtures/main.go:104
32+
msgid "inside dummy"
33+
msgstr ""
34+
35+
#: fixtures/pkg/pkg.go:15
36+
msgid "inside sub package"
37+
msgstr ""
38+
39+
#: fixtures/main.go:51
40+
msgid ""
41+
"multi\n"
42+
"line\n"
43+
"string\n"
44+
msgstr ""
45+
46+
#: fixtures/main.go:54
47+
msgid ""
48+
"multi\n"
49+
"line\n"
50+
"string\n"
51+
"ending with\n"
52+
"EOL\n"
53+
msgstr ""
54+
55+
#: fixtures/main.go:59
56+
msgid ""
57+
"multline\n"
58+
"ending with EOL\n"
59+
msgstr ""
60+
61+
#: fixtures/main.go:50
62+
msgid "raw string with\nmultiple\nEOL"
63+
msgstr ""
64+
65+
#: fixtures/main.go:48
66+
msgid "string ending with EOL\n"
67+
msgstr ""
68+
69+
#: fixtures/main.go:49
70+
msgid ""
71+
"string with\n"
72+
"multiple\n"
73+
"EOL\n"
74+
msgstr ""
75+
76+
#: fixtures/main.go:47
77+
msgid "string with backquotes"
78+
msgstr ""
79+
80+
#: fixtures/main.go:91
81+
msgid "translate package"
82+
msgstr ""
83+
84+
#: fixtures/main.go:92
85+
msgid "translate sub package"
86+
msgstr ""

cli/xgotext/fixtures/i18n/domain2.po

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ msgstr ""
77
"Language: \n"
88
"X-Generator: xgotext\n"
99

10-
11-
#: fixtures/main.go:26
12-
#. gotext.GetD
10+
#: fixtures/main.go:61
1311
msgid "Another text on a different domain"
1412
msgstr ""
13+
14+
#: fixtures/main.go:78
15+
msgctxt "ctx"
16+
msgid "string"
17+
msgstr ""

cli/xgotext/fixtures/i18n/translations.po

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,13 @@ msgstr ""
77
"Language: \n"
88
"X-Generator: xgotext\n"
99

10-
11-
#: fixtures/main.go:35
12-
#. l.GetD
10+
#: fixtures/main.go:71
1311
msgid "Translate this"
1412
msgstr ""
1513

16-
#: fixtures/main.go:43
17-
#. l.GetNDC
14+
#: fixtures/main.go:79
1815
msgctxt "NDC-CTX"
1916
msgid "ndc"
2017
msgid_plural "ndcs"
2118
msgstr[0] ""
22-
msgstr[1] ""
19+
msgstr[1] ""

cli/xgotext/fixtures/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ func main() {
4343
// same with alias package name
4444
fmt.Println(alias.Get("alias call"))
4545

46+
// Special strings
47+
fmt.Println(gotext.Get(`string with backquotes`))
48+
fmt.Println(gotext.Get("string ending with EOL\n"))
49+
fmt.Println(gotext.Get("string with\nmultiple\nEOL"))
50+
fmt.Println(gotext.Get(`raw string with\nmultiple\nEOL`))
51+
fmt.Println(gotext.Get(`multi
52+
line
53+
string`))
54+
fmt.Println(gotext.Get(`multi
55+
line
56+
string
57+
ending with
58+
EOL`))
59+
fmt.Println(gotext.Get("multline\nending with EOL\n"))
60+
4661
// Translate text from a different domain without reconfigure
4762
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
4863

cli/xgotext/parser/dir/golang.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,9 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
248248
return
249249
}
250250

251+
msgID, _ := strconv.Unquote(args[def.Id].Value)
251252
trans := parser.Translation{
252-
MsgId: args[def.Id].Value,
253+
MsgId: msgID,
253254
SourceLocations: []string{pos},
254255
}
255256
if def.Plural > 0 {
@@ -258,7 +259,8 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
258259
log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos)
259260
return
260261
}
261-
trans.MsgIdPlural = args[def.Plural].Value
262+
msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value)
263+
trans.MsgIdPlural = msgIDPlural
262264
}
263265
if def.Context > 0 {
264266
// Context must be a string

cli/xgotext/parser/domain.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,52 @@ func (t *Translation) Dump() string {
3737
data = append(data, "msgctxt "+t.Context)
3838
}
3939

40-
data = append(data, "msgid "+t.MsgId)
40+
data = append(data, toMsgIDString("msgid", t.MsgId)...)
4141

4242
if t.MsgIdPlural == "" {
4343
data = append(data, "msgstr \"\"")
4444
} else {
45+
data = append(data, toMsgIDString("msgid_plural", t.MsgIdPlural)...)
4546
data = append(data,
46-
"msgid_plural "+t.MsgIdPlural,
4747
"msgstr[0] \"\"",
4848
"msgstr[1] \"\"")
4949
}
5050

5151
return strings.Join(data, "\n")
5252
}
5353

54+
// toMsgIDString returns the spec implementation of multi line support of po files by aligning msgid on it.
55+
func toMsgIDString(prefix, msgID string) []string {
56+
elems := strings.Split(msgID, "\n")
57+
// Main case: single line.
58+
if len(elems) == 1 {
59+
return []string{fmt.Sprintf(`%s "%s"`, prefix, msgID)}
60+
}
61+
62+
// Only one line, but finishing with \n
63+
if strings.Count(msgID, "\n") == 1 && strings.HasSuffix(msgID, "\n") {
64+
return []string{fmt.Sprintf(`%s "%s\n"`, prefix, strings.TrimSuffix(msgID, "\n"))}
65+
}
66+
67+
// Skip last element for multiline which is an empty
68+
var shouldEndWithEOL bool
69+
if elems[len(elems)-1] == "" {
70+
elems = elems[:len(elems)-1]
71+
shouldEndWithEOL = true
72+
}
73+
data := []string{fmt.Sprintf(`%s ""`, prefix)}
74+
for i, v := range elems {
75+
l := fmt.Sprintf(`"%s\n"`, v)
76+
// Last element without EOL
77+
if i == len(elems)-1 && !shouldEndWithEOL {
78+
l = fmt.Sprintf(`"%s"`, v)
79+
}
80+
data = append(data, l)
81+
}
82+
83+
return data
84+
}
85+
5486
// TranslationMap contains a map of translations with the ID as key
5587
type TranslationMap map[string]*Translation
5688

cli/xgotext/parser/pkg-tree/golang.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717

1818
const gotextPkgPath = "github.com/leonelquinteros/gotext"
1919

20-
2120
type GetterDef struct {
2221
Id int
2322
Plural int
@@ -287,8 +286,9 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
287286
return
288287
}
289288

289+
msgID, _ := strconv.Unquote(args[def.Id].Value)
290290
trans := parser.Translation{
291-
MsgId: args[def.Id].Value,
291+
MsgId: msgID,
292292
SourceLocations: []string{pos},
293293
}
294294
if def.Plural > 0 {
@@ -297,7 +297,8 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
297297
log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos)
298298
return
299299
}
300-
trans.MsgIdPlural = args[def.Plural].Value
300+
msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value)
301+
trans.MsgIdPlural = msgIDPlural
301302
}
302303
if def.Context > 0 {
303304
// Context must be a string

cli/xgotext/parser/pkg-tree/golang_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package pkg_tree
22

33
import (
4-
"github.com/leonelquinteros/gotext/cli/xgotext/parser"
54
"os"
65
"path/filepath"
76
"testing"
7+
8+
"github.com/leonelquinteros/gotext/cli/xgotext/parser"
89
)
910

1011
func TestParsePkgTree(t *testing.T) {
@@ -23,7 +24,18 @@ func TestParsePkgTree(t *testing.T) {
2324
t.Error(err)
2425
}
2526

26-
translations := []string{"\"inside sub package\"", "\"My text on 'domain-name' domain\"", "\"alias call\"", "\"Singular\"", "\"SingularVar\"", "\"translate package\"", "\"translate sub package\"", "\"inside dummy\""}
27+
translations := []string{"inside sub package", "My text on 'domain-name' domain", "alias call", "Singular", "SingularVar", "translate package", "translate sub package", "inside dummy",
28+
`string with backquotes`, "string ending with EOL\n", "string with\nmultiple\nEOL", `raw string with\nmultiple\nEOL`,
29+
`multi
30+
line
31+
string`,
32+
`multi
33+
line
34+
string
35+
ending with
36+
EOL`,
37+
"multline\nending with EOL\n",
38+
}
2739

2840
if len(translations) != len(data.Domains[defaultDomain].Translations) {
2941
t.Error("translations count mismatch")

domain.go

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gotext
33
import (
44
"bytes"
55
"encoding/gob"
6+
"fmt"
67
"regexp"
78
"sort"
89
"strconv"
@@ -653,9 +654,39 @@ func (do *Domain) MarshalText() ([]byte, error) {
653654
}
654655

655656
func EscapeSpecialCharacters(s string) string {
656-
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
657-
s = strings.ReplaceAll(s, "\n", "\"\n\"") // Convert newlines into multi-line strings
658-
return s
657+
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
658+
659+
if strings.Count(s, "\n") == 0 {
660+
return s
661+
}
662+
663+
// Handle EOL and multi-lines
664+
// Only one line, but finishing with \n
665+
if strings.Count(s, "\n") == 1 && strings.HasSuffix(s, "\n") {
666+
return strings.ReplaceAll(s, "\n", "\\n")
667+
}
668+
669+
elems := strings.Split(s, "\n")
670+
// Skip last element for multiline which is an empty
671+
var shouldEndWithEOL bool
672+
if elems[len(elems)-1] == "" {
673+
elems = elems[:len(elems)-1]
674+
shouldEndWithEOL = true
675+
}
676+
data := []string{(`"`)}
677+
for i, v := range elems {
678+
l := fmt.Sprintf(`"%s\n"`, v)
679+
// Last element without EOL
680+
if i == len(elems)-1 && !shouldEndWithEOL {
681+
l = fmt.Sprintf(`"%s"`, v)
682+
}
683+
// Remove finale " to last element as the whole string will be quoted
684+
if i == len(elems)-1 {
685+
l = strings.TrimSuffix(l, `"`)
686+
}
687+
data = append(data, l)
688+
}
689+
return strings.Join(data, "\n")
659690
}
660691

661692
// MarshalBinary implements encoding.BinaryMarshaler interface

domain_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,10 @@ func TestDomain_CheckExportFormatting(t *testing.T) {
134134
msgstr ""
135135
136136
msgid "myid"
137-
msgstr "test string"
137+
msgstr ""
138+
"test string\n"
138139
"with \"newline\""`
139-
140+
140141
if string(poBytes) != expectedOutput {
141142
t.Errorf("Exported PO format does not match. Received:\n\n%v\n\n\nExpected:\n\n%v", string(poBytes), expectedOutput)
142143
}

0 commit comments

Comments
 (0)