New Upstream Release - golang-github-leonelquinteros-gotext

Ready changes

Summary

Merged new upstream version: 1.5.2 (was: 1.5.1).

Resulting package

Built on 2023-05-21T05:33 (took 5m29s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-leonelquinteros-gotext-dev

Lintian Result

Diff

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3ad6b84..43c0086 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,10 +12,10 @@ jobs:
     runs-on: ubuntu-latest
     steps:
 
-    - name: Set up Go 1.13
+    - name: Set up Go 1.19
       uses: actions/setup-go@v1
       with:
-        go-version: 1.13
+        go-version: 1.19
       id: go
 
     - name: Check out code into the Go module directory
diff --git a/LICENSE b/LICENSE
index a753ef2..eba2976 100644
--- a/LICENSE
+++ b/LICENSE
@@ -19,3 +19,37 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
+
+
+Package `plurals`
+
+Original:
+    https://github.com/ojii/gettext.go/tree/b6dae1d7af8a8441285e42661565760b530a8a57/pluralforms
+
+License:
+    https://raw.githubusercontent.com/ojii/gettext.go/b6dae1d7af8a8441285e42661565760b530a8a57/LICENSE
+
+    Copyright (c) 2016, Jonas Obrist
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are met:
+        * Redistributions of source code must retain the above copyright
+          notice, this list of conditions and the following disclaimer.
+        * Redistributions in binary form must reproduce the above copyright
+          notice, this list of conditions and the following disclaimer in the
+          documentation and/or other materials provided with the distribution.
+        * Neither the name of Jonas Obrist nor the
+          names of its contributors may be used to endorse or promote products
+          derived from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL JONAS OBRIST BE LIABLE FOR ANY
+    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index 5a98f51..4c69289 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,8 @@ Stable releases use [semantic versioning](http://semver.org/spec/v2.0.0.html) ta
 
 You can rely on this to use your preferred vendoring tool or to manually retrieve the corresponding release tag from the GitHub repository.
 
+**NOTE:** v1.5.0 contains a breaking change on how `Po` objects are initialised, see (https://github.com/leonelquinteros/gotext/issues/56)
+
 
 ### Vendoring with [Go Modules](https://github.com/golang/go/wiki/Modules) (Recommended)
 
@@ -68,21 +70,6 @@ require (
 ```
 
 
-### Vendoring with [dep](https://golang.github.io/dep/)
-
-To use last stable version (v1.4.0 at the moment of writing)
-
-```
-dep ensure -add github.com/leonelquinteros/gotext@v1.4.0
-```
-
-Import as
-
-```go
-import "github.com/leonelquinteros/gotext"
-```
-
-
 ### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
 
 [http://gopkg.in/leonelquinteros/gotext.v1](http://gopkg.in/leonelquinteros/gotext.v1)
@@ -258,7 +245,7 @@ msgstr "This one sets the var: %s"
 `
 
     // Create Po object
-    po := new(gotext.Po)
+    po := gotext.NewPo()
     po.Parse(str)
 
     fmt.Println(po.Get("Translate this"))
diff --git a/cli/xgotext/main.go b/cli/xgotext/main.go
index cfce092..3d0a788 100644
--- a/cli/xgotext/main.go
+++ b/cli/xgotext/main.go
@@ -6,9 +6,12 @@ import (
 	"strings"
 
 	"github.com/leonelquinteros/gotext/cli/xgotext/parser"
+	"github.com/leonelquinteros/gotext/cli/xgotext/parser/dir"
+	pkg_tree "github.com/leonelquinteros/gotext/cli/xgotext/parser/pkg-tree"
 )
 
 var (
+	pkgTree       = flag.String("pkg-tree", "", "main path: /path/to/go/pkg")
 	dirName       = flag.String("in", "", "input dir: /path/to/go/pkg")
 	outputDir     = flag.String("out", "", "output dir: /path/to/i18n/files")
 	defaultDomain = flag.String("default", "default", "Name of default domain")
@@ -22,9 +25,12 @@ func main() {
 	// Init logger
 	log.SetFlags(0)
 
-	if *dirName == "" {
+	if *pkgTree == "" && *dirName == "" {
 		log.Fatal("No input directory given")
 	}
+	if *pkgTree != "" && *dirName != "" {
+		log.Fatal("Specify either main or in")
+	}
 	if *outputDir == "" {
 		log.Fatal("No output directory given")
 	}
@@ -33,12 +39,19 @@ func main() {
 		Default: *defaultDomain,
 	}
 
-	err := parser.ParseDirRec(*dirName, strings.Split(*excludeDirs, ","), data, *verbose)
-	if err != nil {
-		log.Fatal(err)
+	if *pkgTree != "" {
+		err := pkg_tree.ParsePkgTree(*pkgTree, data, *verbose)
+		if err != nil {
+			log.Fatal(err)
+		}
+	} else {
+		err := dir.ParseDirRec(*dirName, strings.Split(*excludeDirs, ","), data, *verbose)
+		if err != nil {
+			log.Fatal(err)
+		}
 	}
 
-	err = data.Save(*outputDir)
+	err := data.Save(*outputDir)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/cli/xgotext/parser/golang.go b/cli/xgotext/parser/dir/golang.go
similarity index 96%
rename from cli/xgotext/parser/golang.go
rename to cli/xgotext/parser/dir/golang.go
index 2693503..f65d572 100644
--- a/cli/xgotext/parser/golang.go
+++ b/cli/xgotext/parser/dir/golang.go
@@ -1,4 +1,4 @@
-package parser
+package dir
 
 import (
 	"fmt"
@@ -10,6 +10,8 @@ import (
 	"strconv"
 
 	"golang.org/x/tools/go/packages"
+
+	"github.com/leonelquinteros/gotext/cli/xgotext/parser"
 )
 
 // GetterDef describes a getter
@@ -53,7 +55,7 @@ func init() {
 }
 
 // parse go package
-func goParser(dirPath, basePath string, data *DomainMap) error {
+func goParser(dirPath, basePath string, data *parser.DomainMap) error {
 	fileSet := token.NewFileSet()
 
 	conf := packages.Config{
@@ -100,7 +102,7 @@ func goParser(dirPath, basePath string, data *DomainMap) error {
 type GoFile struct {
 	filePath string
 	basePath string
-	data     *DomainMap
+	data     *parser.DomainMap
 
 	fileSet *token.FileSet
 	pkgConf *packages.Config
@@ -246,7 +248,7 @@ func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
 		return
 	}
 
-	trans := Translation{
+	trans := parser.Translation{
 		MsgId:           args[def.Id].Value,
 		SourceLocations: []string{pos},
 	}
diff --git a/cli/xgotext/parser/parser.go b/cli/xgotext/parser/dir/parser.go
similarity index 78%
rename from cli/xgotext/parser/parser.go
rename to cli/xgotext/parser/dir/parser.go
index e109664..9f92c27 100644
--- a/cli/xgotext/parser/parser.go
+++ b/cli/xgotext/parser/dir/parser.go
@@ -1,14 +1,16 @@
-package parser
+package dir
 
 import (
 	"log"
 	"os"
 	"path/filepath"
 	"strings"
+
+	"github.com/leonelquinteros/gotext/cli/xgotext/parser"
 )
 
 // ParseDirFunc parses one directory
-type ParseDirFunc func(filePath, basePath string, data *DomainMap) error
+type ParseDirFunc func(filePath, basePath string, data *parser.DomainMap) error
 
 var knownParser []ParseDirFunc
 
@@ -22,7 +24,7 @@ func AddParser(parser ParseDirFunc) {
 }
 
 // ParseDir calls all known parser for each directory
-func ParseDir(dirPath, basePath string, data *DomainMap) error {
+func ParseDir(dirPath, basePath string, data *parser.DomainMap) error {
 	dirPath, _ = filepath.Abs(dirPath)
 	basePath, _ = filepath.Abs(basePath)
 
@@ -36,7 +38,7 @@ func ParseDir(dirPath, basePath string, data *DomainMap) error {
 }
 
 // ParseDirRec calls all known parser for each directory
-func ParseDirRec(dirPath string, exclude []string, data *DomainMap, verbose bool) error {
+func ParseDirRec(dirPath string, exclude []string, data *parser.DomainMap, verbose bool) error {
 	dirPath, _ = filepath.Abs(dirPath)
 
 	err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
diff --git a/cli/xgotext/parser/pkg-tree/golang.go b/cli/xgotext/parser/pkg-tree/golang.go
new file mode 100644
index 0000000..dd713ac
--- /dev/null
+++ b/cli/xgotext/parser/pkg-tree/golang.go
@@ -0,0 +1,314 @@
+package pkg_tree
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+	"go/types"
+	"golang.org/x/tools/go/packages"
+	"log"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"github.com/leonelquinteros/gotext/cli/xgotext/parser"
+)
+
+const gotextPkgPath = "github.com/leonelquinteros/gotext"
+
+
+type GetterDef struct {
+	Id      int
+	Plural  int
+	Context int
+	Domain  int
+}
+
+// MaxArgIndex returns the largest argument index
+func (d *GetterDef) maxArgIndex() int {
+	m := d.Id
+	if d.Plural > m {
+		m = d.Plural
+	}
+	if d.Context > m {
+		m = d.Context
+	}
+	if d.Domain > m {
+		m = d.Domain
+	}
+	return m
+}
+
+// list of supported getter
+var gotextGetter = map[string]GetterDef{
+	"Get":    {0, -1, -1, -1},
+	"GetN":   {0, 1, -1, -1},
+	"GetD":   {1, -1, -1, 0},
+	"GetND":  {1, 2, -1, 0},
+	"GetC":   {0, -1, 1, -1},
+	"GetNC":  {0, 1, 3, -1},
+	"GetDC":  {1, -1, 2, 0},
+	"GetNDC": {1, 2, 4, 0},
+}
+
+// ParsePkgTree parse go package tree
+func ParsePkgTree(pkgPath string, data *parser.DomainMap, verbose bool) error {
+	basePath, err := os.Getwd()
+	if err != nil {
+		log.Fatal(err)
+	}
+	return pkgParser(pkgPath, basePath, data, verbose)
+}
+
+
+func pkgParser(dirPath, basePath string, data *parser.DomainMap, verbose bool) error {
+	mainPkg, err := loadPackage(dirPath)
+	if err != nil {
+		return err
+	}
+
+	for _, pkg := range filterPkgs(mainPkg) {
+		if verbose {
+			fmt.Println(pkg.ID)
+		}
+		for _, node := range pkg.Syntax {
+			file := GoFile{
+				filePath: pkg.Fset.Position(node.Package).Filename,
+				basePath: basePath,
+				data:     data,
+				fileSet:  pkg.Fset,
+
+				importedPackages: map[string]*packages.Package{
+					pkg.Name: pkg,
+				},
+			}
+
+			ast.Inspect(node, file.inspectFile)
+		}
+	}
+
+	return nil
+}
+
+var pkgCache = make(map[string]*packages.Package)
+
+func loadPackage(name string) (*packages.Package, error) {
+	fileSet := token.NewFileSet()
+	conf := &packages.Config{
+		Mode: packages.NeedName |
+			packages.NeedFiles |
+			packages.NeedSyntax |
+			packages.NeedTypes |
+			packages.NeedTypesInfo |
+			packages.NeedImports |
+			packages.NeedDeps,
+		Fset: fileSet,
+		Dir:  name,
+	}
+	pkgs, err := packages.Load(conf)
+	if err != nil {
+		return nil, err
+	}
+
+	return pkgs[0], nil
+}
+
+func getPkgPath(pkg *packages.Package) string {
+	if len(pkg.GoFiles) == 0 {
+		return pkg.PkgPath
+	}
+	pkgPath, _ := filepath.Split(pkg.GoFiles[0])
+	return strings.TrimRight(pkgPath, "/")
+}
+
+func filterPkgs(pkg *packages.Package) []*packages.Package {
+	result := filterPkgsRec(pkg)
+	return result
+}
+
+func filterPkgsRec(pkg *packages.Package) []*packages.Package {
+	result := make([]*packages.Package, 0, 100)
+	pkgCache[pkg.ID] = pkg
+	for _, importedPkg := range pkg.Imports {
+		if importedPkg.ID == gotextPkgPath {
+			result = append(result, pkg)
+		}
+		if _, ok := pkgCache[importedPkg.ID]; ok {
+			continue
+		}
+		result = append(result, filterPkgsRec(importedPkg)...)
+	}
+	return result
+}
+
+
+// GoFile handles the parsing of one go file
+type GoFile struct {
+	filePath string
+	basePath string
+	data     *parser.DomainMap
+
+	fileSet *token.FileSet
+	pkgConf *packages.Config
+
+	importedPackages map[string]*packages.Package
+}
+
+// getPackage loads module by name
+func (g *GoFile) getPackage(name string) (*packages.Package, error) {
+	pkg, ok := pkgCache[name]
+	if !ok {
+		return nil, fmt.Errorf("not found in cache")
+	}
+	return pkg, nil
+}
+
+// getType from ident object
+func (g *GoFile) getType(ident *ast.Ident) types.Object {
+	for _, pkg := range g.importedPackages {
+		if pkg.Types == nil {
+			continue
+		}
+		if obj, ok := pkg.TypesInfo.Uses[ident]; ok {
+			return obj
+		}
+	}
+	return nil
+}
+
+func (g *GoFile) inspectFile(n ast.Node) bool {
+	switch x := n.(type) {
+	// get names of imported packages
+	case *ast.ImportSpec:
+		packageName, _ := strconv.Unquote(x.Path.Value)
+
+		pkg, err := g.getPackage(packageName)
+		if err != nil {
+			log.Printf("failed to load package %s: %s", packageName, err)
+		} else {
+			if x.Name == nil {
+				g.importedPackages[pkg.Name] = pkg
+			} else {
+				g.importedPackages[x.Name.Name] = pkg
+			}
+		}
+
+	// check each function call
+	case *ast.CallExpr:
+		g.inspectCallExpr(x)
+
+	default:
+		print()
+	}
+
+	return true
+}
+
+// checkType for gotext object
+func (g *GoFile) checkType(rawType types.Type) bool {
+	switch t := rawType.(type) {
+	case *types.Pointer:
+		return g.checkType(t.Elem())
+
+	case *types.Named:
+		if t.Obj().Pkg() == nil || t.Obj().Pkg().Path() != gotextPkgPath {
+			return false
+		}
+	default:
+		return false
+	}
+	return true
+}
+
+func (g *GoFile) inspectCallExpr(n *ast.CallExpr) {
+	// must be a selector expression otherwise it is a local function call
+	expr, ok := n.Fun.(*ast.SelectorExpr)
+	if !ok {
+		return
+	}
+
+	switch e := expr.X.(type) {
+	// direct call
+	case *ast.Ident:
+		// object is a package if the Obj is not set
+		if e.Obj != nil {
+			// validate type of object
+			t := g.getType(e)
+			if t == nil || !g.checkType(t.Type()) {
+				return
+			}
+		}
+
+	// call to attribute
+	case *ast.SelectorExpr:
+		// validate type of object
+		t := g.getType(e.Sel)
+		if t == nil || !g.checkType(t.Type()) {
+			return
+		}
+
+	default:
+		return
+	}
+
+	// convert args
+	args := make([]*ast.BasicLit, len(n.Args))
+	for idx, arg := range n.Args {
+		args[idx], _ = arg.(*ast.BasicLit)
+	}
+
+	// get position
+	path, _ := filepath.Rel(g.basePath, g.filePath)
+	position := fmt.Sprintf("%s:%d", path, g.fileSet.Position(n.Lparen).Line)
+
+	// handle getters
+	if def, ok := gotextGetter[expr.Sel.String()]; ok {
+		g.parseGetter(def, args, position)
+		return
+	}
+}
+
+func (g *GoFile) parseGetter(def GetterDef, args []*ast.BasicLit, pos string) {
+	// check if enough arguments are given
+	if len(args) < def.maxArgIndex() {
+		return
+	}
+
+	// get domain
+	var domain string
+	if def.Domain != -1 {
+		domain, _ = strconv.Unquote(args[def.Domain].Value)
+	}
+
+	// only handle function calls with strings as ID
+	if args[def.Id] == nil || args[def.Id].Kind != token.STRING {
+		log.Printf("ERR: Unsupported call at %s (ID not a string)", pos)
+		return
+	}
+
+	trans := parser.Translation{
+		MsgId:           args[def.Id].Value,
+		SourceLocations: []string{pos},
+	}
+	if def.Plural > 0 {
+		// plural ID must be a string
+		if args[def.Plural] == nil || args[def.Plural].Kind != token.STRING {
+			log.Printf("ERR: Unsupported call at %s (Plural not a string)", pos)
+			return
+		}
+		trans.MsgIdPlural = args[def.Plural].Value
+	}
+	if def.Context > 0 {
+		// Context must be a string
+		if args[def.Context] == nil || args[def.Context].Kind != token.STRING {
+			log.Printf("ERR: Unsupported call at %s (Context not a string)", pos)
+			return
+		}
+		trans.Context = args[def.Context].Value
+	}
+
+	g.data.AddTranslation(domain, &trans)
+}
+
+
diff --git a/cli/xgotext/parser/pkg-tree/golang_test.go b/cli/xgotext/parser/pkg-tree/golang_test.go
new file mode 100644
index 0000000..b79f686
--- /dev/null
+++ b/cli/xgotext/parser/pkg-tree/golang_test.go
@@ -0,0 +1,36 @@
+package pkg_tree
+
+import (
+	"github.com/leonelquinteros/gotext/cli/xgotext/parser"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestParsePkgTree(t *testing.T) {
+	defaultDomain := "default"
+	data := &parser.DomainMap{
+		Default: defaultDomain,
+	}
+	currentPath, err := os.Getwd()
+	pkgPath := filepath.Join(filepath.Dir(filepath.Dir(currentPath)), "fixtures")
+	println(pkgPath)
+	if err != nil {
+		t.Error(err)
+	}
+	err = ParsePkgTree(pkgPath, data, true)
+	if err != nil {
+		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\""}
+
+	if len(translations) != len(data.Domains[defaultDomain].Translations) {
+		t.Error("translations count mismatch")
+	}
+	for _, tr := range translations {
+		if _, ok := data.Domains[defaultDomain].Translations[tr]; !ok {
+			t.Errorf("translation '%v' not in result", tr)
+		}
+	}
+}
diff --git a/debian/changelog b/debian/changelog
index 1720ae8..a07c489 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-leonelquinteros-gotext (1.5.2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 21 May 2023 05:28:47 -0000
+
 golang-github-leonelquinteros-gotext (1.5.0-3) unstable; urgency=medium
 
   * Fix dependency: golang-x-text-dev -> golang-golang-x-text-dev
diff --git a/domain.go b/domain.go
index e9d13ba..2bd40a6 100644
--- a/domain.go
+++ b/domain.go
@@ -3,6 +3,7 @@ package gotext
 import (
 	"bytes"
 	"encoding/gob"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -589,17 +590,17 @@ func (do *Domain) MarshalText() ([]byte, error) {
 		}
 
 		if ref.context == "" {
-			buf.WriteString("\nmsgid \"" + trans.ID + "\"")
+			buf.WriteString("\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
 		} else {
-			buf.WriteString("\nmsgctxt \"" + ref.context + "\"\nmsgid \"" + trans.ID + "\"")
+			buf.WriteString("\nmsgctxt \"" + EscapeSpecialCharacters(ref.context) + "\"\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
 		}
 
 		if trans.PluralID == "" {
-			buf.WriteString("\nmsgstr \"" + trans.Trs[0] + "\"")
+			buf.WriteString("\nmsgstr \"" + EscapeSpecialCharacters(trans.Trs[0]) + "\"")
 		} else {
 			buf.WriteString("\nmsgid_plural \"" + trans.PluralID + "\"")
 			for i, tr := range trans.Trs {
-				buf.WriteString("\nmsgstr[" + strconv.Itoa(i) + "] \"" + tr + "\"")
+				buf.WriteString("\nmsgstr[" + EscapeSpecialCharacters(strconv.Itoa(i)) + "] \"" + tr + "\"")
 			}
 		}
 	}
@@ -607,6 +608,12 @@ func (do *Domain) MarshalText() ([]byte, error) {
 	return buf.Bytes(), nil
 }
 
+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 
+}
+
 // MarshalBinary implements encoding.BinaryMarshaler interface
 func (do *Domain) MarshalBinary() ([]byte, error) {
 	obj := new(TranslatorEncoding)
diff --git a/domain_test.go b/domain_test.go
index fa94e13..5df36e3 100644
--- a/domain_test.go
+++ b/domain_test.go
@@ -1,6 +1,8 @@
 package gotext
 
-import "testing"
+import (
+	"testing"
+)
 
 const (
 	enUSFixture = "fixtures/en_US/default.po"
@@ -69,3 +71,20 @@ func TestDomain_GetTranslations(t *testing.T) {
 		}
 	}
 }
+
+func TestDomain_CheckExportFormatting(t *testing.T) {
+	po := NewPo()
+	po.Set("myid", "test string\nwith \"newline\"")
+	poBytes, _ := po.MarshalText()
+
+	expectedOutput := `msgid ""
+msgstr ""
+
+msgid "myid"
+msgstr "test string"
+"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)
+	}
+}
diff --git a/go.mod b/go.mod
index 21f5da6..dd752e9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/leonelquinteros/gotext
 // go: no requirements found in Gopkg.lock
 
 require (
-	golang.org/x/text v0.3.0
-	golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
+	golang.org/x/text v0.3.8
+	golang.org/x/tools v0.1.12
 )
 
 go 1.13
diff --git a/go.sum b/go.sum
index 988c1f6..2d1aca1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,15 +1,28 @@
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs=
-golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/mo.go b/mo.go
index 5a7f8e8..278fbd5 100644
--- a/mo.go
+++ b/mo.go
@@ -35,14 +35,14 @@ Example:
 	)
 
 	func main() {
-		// Create po object
-		po := gotext.NewMoTranslator()
+		// Create mo object
+		mo := gotext.NewMo()
 
-		// Parse .po file
-		po.ParseFile("/path/to/po/file/translations.mo")
+		// Parse .mo file
+		mo.ParseFile("/path/to/po/file/translations.mo")
 
 		// Get Translation
-		fmt.Println(po.Get("Translate this"))
+		fmt.Println(mo.Get("Translate this"))
 	}
 
 */
diff --git a/plurals/compiler.go b/plurals/compiler.go
index ee82205..74cb4cd 100644
--- a/plurals/compiler.go
+++ b/plurals/compiler.go
@@ -1,10 +1,11 @@
-/*
- * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
+// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
+// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
+// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
+//
+// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
 
 /*
- Package plurals is the pluralform compiler to get the correct translation id of the plural string
+Package plurals is the pluralform compiler to get the correct translation id of the plural string
 */
 package plurals
 
diff --git a/plurals/compiler_test.go b/plurals/compiler_test.go
index ba8f58d..041a91e 100644
--- a/plurals/compiler_test.go
+++ b/plurals/compiler_test.go
@@ -1,7 +1,8 @@
-/*
- * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
+// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
+// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
+// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
+//
+// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
 
 package plurals
 
diff --git a/plurals/expression.go b/plurals/expression.go
index 3a2add5..a323da9 100644
--- a/plurals/expression.go
+++ b/plurals/expression.go
@@ -1,7 +1,8 @@
-/*
- * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
+// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
+// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
+// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
+//
+// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
 
 package plurals
 
diff --git a/plurals/genfixture.py b/plurals/genfixture.py
new file mode 100644
index 0000000..115a09b
--- /dev/null
+++ b/plurals/genfixture.py
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
+#
+# Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
+
+import json
+from gettext import c2py
+
+
+PLURAL_FORMS = [
+    "0",
+    "n!=1",
+    "n>1",
+    "n%10==1&&n%100!=11?0:n!=0?1:2",
+    "n==1?0:n==2?1:2",
+    "n==1?0:(n==0||(n%100>0&&n%100<20))?1:2",
+    "n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?1:2",
+    "n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2",
+    "(n==1)?0:(n>=2&&n<=4)?1:2",
+    "n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2",
+    "n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3",
+    "n==0?0:n==1?1:n==2?2:n%100>=3&&n%100<=10?3:n%100>=11?4:5",
+]
+
+NUM = 1000
+
+
+def gen():
+    tests = []
+    for plural_form in PLURAL_FORMS:
+        expr = c2py(plural_form)
+        tests.append({
+            'pluralform': plural_form,
+            'fixture': [expr(n) for n in range(NUM + 1)]
+        })
+    return json.dumps(tests)
+
+
+if __name__ == "__main__":
+    print(gen())
diff --git a/plurals/math.go b/plurals/math.go
index ceaeaaf..1a8c446 100644
--- a/plurals/math.go
+++ b/plurals/math.go
@@ -1,7 +1,8 @@
-/*
- * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
+// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
+// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
+// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
+//
+// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
 
 package plurals
 
diff --git a/plurals/tests.go b/plurals/tests.go
index b459610..39f892f 100644
--- a/plurals/tests.go
+++ b/plurals/tests.go
@@ -1,7 +1,8 @@
-/*
- * Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
+// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
+// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
+// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
+//
+// Licensed under the BSD License. See License.txt in the project root for license information.
 
 package plurals
 
diff --git a/po.go b/po.go
index 84baa44..89f83a3 100644
--- a/po.go
+++ b/po.go
@@ -126,7 +126,7 @@ func (po *Po) ParseFile(f string) {
 	po.Parse(data)
 }
 
-// Parse loads the translations specified in the provided string (str)
+// Parse loads the translations specified in the provided byte slice (buf)
 func (po *Po) Parse(buf []byte) {
 	if po.domain == nil {
 		panic("NewPo() was not used to instantiate this object")

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details