Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multiline export #82

Merged
merged 4 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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