Skip to content

Commit

Permalink
Merge pull request #82 from didrocks/fix-multiline-export
Browse files Browse the repository at this point in the history
Fix multiline export
  • Loading branch information
leonelquinteros authored Aug 29, 2023
2 parents 1f7d156 + 4d8518b commit b2e5003
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 30 deletions.
72 changes: 65 additions & 7 deletions cli/xgotext/fixtures/i18n/default.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,80 @@ msgstr ""
"Language: \n"
"X-Generator: xgotext\n"


#: fixtures/main.go:23
#. gotext.Get
#: fixtures/main.go:35
#: fixtures/main.go:37
msgid "My text on 'domain-name' domain"
msgstr ""

#: fixtures/main.go:38
#. l.GetN
#: fixtures/main.go:75
msgid "Singular"
msgid_plural "Plural"
msgstr[0] ""
msgstr[1] ""

#: fixtures/main.go:40
#. l.GetN
#: fixtures/main.go:77
msgid "SingularVar"
msgid_plural "PluralVar"
msgstr[0] ""
msgstr[1] ""

#: fixtures/main.go:44
msgid "alias call"
msgstr ""

#: fixtures/main.go:104
msgid "inside dummy"
msgstr ""

#: fixtures/pkg/pkg.go:15
msgid "inside sub package"
msgstr ""

#: fixtures/main.go:51
msgid ""
"multi\n"
"line\n"
"string\n"
msgstr ""

#: fixtures/main.go:54
msgid ""
"multi\n"
"line\n"
"string\n"
"ending with\n"
"EOL\n"
msgstr ""

#: fixtures/main.go:59
msgid ""
"multline\n"
"ending with EOL\n"
msgstr ""

#: fixtures/main.go:50
msgid "raw string with\nmultiple\nEOL"
msgstr ""

#: fixtures/main.go:48
msgid "string ending with EOL\n"
msgstr ""

#: fixtures/main.go:49
msgid ""
"string with\n"
"multiple\n"
"EOL\n"
msgstr ""

#: fixtures/main.go:47
msgid "string with backquotes"
msgstr ""

#: fixtures/main.go:91
msgid "translate package"
msgstr ""

#: fixtures/main.go:92
msgid "translate sub package"
msgstr ""
9 changes: 6 additions & 3 deletions cli/xgotext/fixtures/i18n/domain2.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ msgstr ""
"Language: \n"
"X-Generator: xgotext\n"


#: fixtures/main.go:26
#. gotext.GetD
#: fixtures/main.go:61
msgid "Another text on a different domain"
msgstr ""

#: fixtures/main.go:78
msgctxt "ctx"
msgid "string"
msgstr ""
9 changes: 3 additions & 6 deletions cli/xgotext/fixtures/i18n/translations.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@ msgstr ""
"Language: \n"
"X-Generator: xgotext\n"


#: fixtures/main.go:35
#. l.GetD
#: fixtures/main.go:71
msgid "Translate this"
msgstr ""

#: fixtures/main.go:43
#. l.GetNDC
#: fixtures/main.go:79
msgctxt "NDC-CTX"
msgid "ndc"
msgid_plural "ndcs"
msgstr[0] ""
msgstr[1] ""
msgstr[1] ""
15 changes: 15 additions & 0 deletions cli/xgotext/fixtures/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ func main() {
// same with alias package name
fmt.Println(alias.Get("alias call"))

// Special strings
fmt.Println(gotext.Get(`string with backquotes`))
fmt.Println(gotext.Get("string ending with EOL\n"))
fmt.Println(gotext.Get("string with\nmultiple\nEOL"))
fmt.Println(gotext.Get(`raw string with\nmultiple\nEOL`))
fmt.Println(gotext.Get(`multi
line
string`))
fmt.Println(gotext.Get(`multi
line
string
ending with
EOL`))
fmt.Println(gotext.Get("multline\nending with EOL\n"))

// Translate text from a different domain without reconfigure
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))

Expand Down
6 changes: 4 additions & 2 deletions cli/xgotext/parser/dir/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,9 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
return
}

msgID, _ := strconv.Unquote(args[def.Id].Value)
trans := parser.Translation{
MsgId: args[def.Id].Value,
MsgId: msgID,
SourceLocations: []string{pos},
}
if def.Plural > 0 {
Expand All @@ -258,7 +259,8 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos)
return
}
trans.MsgIdPlural = args[def.Plural].Value
msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value)
trans.MsgIdPlural = msgIDPlural
}
if def.Context > 0 {
// Context must be a string
Expand Down
36 changes: 34 additions & 2 deletions cli/xgotext/parser/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,52 @@ func (t *Translation) Dump() string {
data = append(data, "msgctxt "+t.Context)
}

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

if t.MsgIdPlural == "" {
data = append(data, "msgstr \"\"")
} else {
data = append(data, toMsgIDString("msgid_plural", t.MsgIdPlural)...)
data = append(data,
"msgid_plural "+t.MsgIdPlural,
"msgstr[0] \"\"",
"msgstr[1] \"\"")
}

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

// toMsgIDString returns the spec implementation of multi line support of po files by aligning msgid on it.
func toMsgIDString(prefix, msgID string) []string {
elems := strings.Split(msgID, "\n")
// Main case: single line.
if len(elems) == 1 {
return []string{fmt.Sprintf(`%s "%s"`, prefix, msgID)}
}

// Only one line, but finishing with \n
if strings.Count(msgID, "\n") == 1 && strings.HasSuffix(msgID, "\n") {
return []string{fmt.Sprintf(`%s "%s\n"`, prefix, strings.TrimSuffix(msgID, "\n"))}
}

// Skip last element for multiline which is an empty
var shouldEndWithEOL bool
if elems[len(elems)-1] == "" {
elems = elems[:len(elems)-1]
shouldEndWithEOL = true
}
data := []string{fmt.Sprintf(`%s ""`, prefix)}
for i, v := range elems {
l := fmt.Sprintf(`"%s\n"`, v)
// Last element without EOL
if i == len(elems)-1 && !shouldEndWithEOL {
l = fmt.Sprintf(`"%s"`, v)
}
data = append(data, l)
}

return data
}

// TranslationMap contains a map of translations with the ID as key
type TranslationMap map[string]*Translation

Expand Down
7 changes: 4 additions & 3 deletions cli/xgotext/parser/pkg-tree/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (

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


type GetterDef struct {
Id int
Plural int
Expand Down Expand Up @@ -287,8 +286,9 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
return
}

msgID, _ := strconv.Unquote(args[def.Id].Value)
trans := parser.Translation{
MsgId: args[def.Id].Value,
MsgId: msgID,
SourceLocations: []string{pos},
}
if def.Plural > 0 {
Expand All @@ -297,7 +297,8 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos)
return
}
trans.MsgIdPlural = args[def.Plural].Value
msgIDPlural, _ := strconv.Unquote(args[def.Plural].Value)
trans.MsgIdPlural = msgIDPlural
}
if def.Context > 0 {
// Context must be a string
Expand Down
16 changes: 14 additions & 2 deletions cli/xgotext/parser/pkg-tree/golang_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package pkg_tree

import (
"github.com/leonelquinteros/gotext/cli/xgotext/parser"
"os"
"path/filepath"
"testing"

"github.com/leonelquinteros/gotext/cli/xgotext/parser"
)

func TestParsePkgTree(t *testing.T) {
Expand All @@ -23,7 +24,18 @@ func TestParsePkgTree(t *testing.T) {
t.Error(err)
}

translations := []string{"\"inside sub package\"", "\"My text on 'domain-name' domain\"", "\"alias call\"", "\"Singular\"", "\"SingularVar\"", "\"translate package\"", "\"translate sub package\"", "\"inside dummy\""}
translations := []string{"inside sub package", "My text on 'domain-name' domain", "alias call", "Singular", "SingularVar", "translate package", "translate sub package", "inside dummy",
`string with backquotes`, "string ending with EOL\n", "string with\nmultiple\nEOL", `raw string with\nmultiple\nEOL`,
`multi
line
string`,
`multi
line
string
ending with
EOL`,
"multline\nending with EOL\n",
}

if len(translations) != len(data.Domains[defaultDomain].Translations) {
t.Error("translations count mismatch")
Expand Down
37 changes: 34 additions & 3 deletions domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gotext
import (
"bytes"
"encoding/gob"
"fmt"
"regexp"
"sort"
"strconv"
Expand Down Expand Up @@ -653,9 +654,39 @@ func (do *Domain) MarshalText() ([]byte, error) {
}

func EscapeSpecialCharacters(s string) string {
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
s = strings.ReplaceAll(s, "\n", "\"\n\"") // Convert newlines into multi-line strings
return s
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks

if strings.Count(s, "\n") == 0 {
return s
}

// Handle EOL and multi-lines
// Only one line, but finishing with \n
if strings.Count(s, "\n") == 1 && strings.HasSuffix(s, "\n") {
return strings.ReplaceAll(s, "\n", "\\n")
}

elems := strings.Split(s, "\n")
// Skip last element for multiline which is an empty
var shouldEndWithEOL bool
if elems[len(elems)-1] == "" {
elems = elems[:len(elems)-1]
shouldEndWithEOL = true
}
data := []string{(`"`)}
for i, v := range elems {
l := fmt.Sprintf(`"%s\n"`, v)
// Last element without EOL
if i == len(elems)-1 && !shouldEndWithEOL {
l = fmt.Sprintf(`"%s"`, v)
}
// Remove finale " to last element as the whole string will be quoted
if i == len(elems)-1 {
l = strings.TrimSuffix(l, `"`)
}
data = append(data, l)
}
return strings.Join(data, "\n")
}

// MarshalBinary implements encoding.BinaryMarshaler interface
Expand Down
5 changes: 3 additions & 2 deletions domain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ func TestDomain_CheckExportFormatting(t *testing.T) {
msgstr ""
msgid "myid"
msgstr "test string"
msgstr ""
"test string\n"
"with \"newline\""`

if string(poBytes) != expectedOutput {
t.Errorf("Exported PO format does not match. Received:\n\n%v\n\n\nExpected:\n\n%v", string(poBytes), expectedOutput)
}
Expand Down

0 comments on commit b2e5003

Please sign in to comment.