Properly handle singular vs plural defaults for untranslated strings. Fixes #9
This commit is contained in:
39
README.md
39
README.md
@@ -156,17 +156,20 @@ This is a normal Go compiler behavior.
|
|||||||
For quick/simple translations you can use the package level functions directly.
|
For quick/simple translations you can use the package level functions directly.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Configure package
|
// Configure package
|
||||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||||
|
|
||||||
// Translate text from default domain
|
// Translate text from default domain
|
||||||
println(gotext.Get("My text on 'domain-name' domain"))
|
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||||
|
|
||||||
// Translate text from a different domain without reconfigure
|
// Translate text from a different domain without reconfigure
|
||||||
println(gotext.GetD("domain2", "Another text on a different domain"))
|
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -177,7 +180,10 @@ All translation strings support dynamic variables to be inserted without transla
|
|||||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Configure package
|
// Configure package
|
||||||
@@ -187,7 +193,7 @@ func main() {
|
|||||||
name := "John"
|
name := "John"
|
||||||
|
|
||||||
// Translate text with variables
|
// Translate text with variables
|
||||||
println(gotext.Get("Hi, my name is %s", name))
|
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -199,7 +205,10 @@ When having multiple languages/domains/libraries at the same time, you can creat
|
|||||||
so you can handle each settings on their own.
|
so you can handle each settings on their own.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create Locale with library path and language code
|
// Create Locale with library path and language code
|
||||||
@@ -209,13 +218,13 @@ func main() {
|
|||||||
l.AddDomain("default")
|
l.AddDomain("default")
|
||||||
|
|
||||||
// Translate text from default domain
|
// Translate text from default domain
|
||||||
println(l.Get("Translate this"))
|
fmt.Println(l.Get("Translate this"))
|
||||||
|
|
||||||
// Load different domain
|
// Load different domain
|
||||||
l.AddDomain("translations")
|
l.AddDomain("translations")
|
||||||
|
|
||||||
// Translate text from domain
|
// Translate text from domain
|
||||||
println(l.GetD("translations", "Translate this"))
|
fmt.Println(l.GetD("translations", "Translate this"))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -233,7 +242,10 @@ For when you need to work with PO files and strings,
|
|||||||
you can directly use the Po object to parse it and access the translations in there in the same way.
|
you can directly use the Po object to parse it and access the translations in there in the same way.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
@@ -252,7 +264,7 @@ msgstr "This one sets the var: %s"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
po.Parse(str)
|
po.Parse(str)
|
||||||
|
|
||||||
println(po.Get("Translate this"))
|
fmt.Println(po.Get("Translate this"))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -266,7 +278,10 @@ as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_no
|
|||||||
Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn/kinako)
|
Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn/kinako)
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
@@ -293,7 +308,7 @@ msgstr[1] "This one is the plural: %s"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
po.Parse(str)
|
po.Parse(str)
|
||||||
|
|
||||||
println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||||
// "This one is the plural: Variable"
|
// "This one is the plural: Variable"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,17 +3,20 @@ Package gotext implements GNU gettext utilities.
|
|||||||
|
|
||||||
For quick/simple translations you can use the package level functions directly.
|
For quick/simple translations you can use the package level functions directly.
|
||||||
|
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Configure package
|
// Configure package
|
||||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||||
|
|
||||||
// Translate text from default domain
|
// Translate text from default domain
|
||||||
println(gotext.Get("My text on 'domain-name' domain"))
|
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||||
|
|
||||||
// Translate text from a different domain without reconfigure
|
// Translate text from a different domain without reconfigure
|
||||||
println(gotext.GetD("domain2", "Another text on a different domain"))
|
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ msgstr "Some random translation in a context"
|
|||||||
msgid "More"
|
msgid "More"
|
||||||
msgstr "More translation"
|
msgstr "More translation"
|
||||||
|
|
||||||
|
msgid "Untranslated"
|
||||||
|
msgid_plural "Several untranslated"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// Create Locales directory on default location
|
// Create Locales directory on default location
|
||||||
@@ -141,6 +146,79 @@ msgstr "More translation"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUntranslated(t *testing.T) {
|
||||||
|
// Set PO content
|
||||||
|
str := `
|
||||||
|
# msgid ""
|
||||||
|
# msgstr ""
|
||||||
|
# Initial comment
|
||||||
|
# Headers below
|
||||||
|
"Language: en\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgid "Untranslated"
|
||||||
|
msgid_plural "Several untranslated"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
// Create Locales directory on default location
|
||||||
|
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US")
|
||||||
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write PO content to default domain file
|
||||||
|
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po")
|
||||||
|
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't create test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.WriteString(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set package configuration
|
||||||
|
Configure("/tmp", "en_US", "default")
|
||||||
|
|
||||||
|
// Test untranslated
|
||||||
|
tr := Get("Untranslated")
|
||||||
|
if tr != "Untranslated" {
|
||||||
|
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetN("Untranslated", "Several untranslated", 1)
|
||||||
|
if tr != "Untranslated" {
|
||||||
|
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetN("Untranslated", "Several untranslated", 2)
|
||||||
|
if tr != "Several untranslated" {
|
||||||
|
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetD("default", "Untranslated")
|
||||||
|
if tr != "Untranslated" {
|
||||||
|
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetND("default", "Untranslated", "Several untranslated", 1)
|
||||||
|
if tr != "Untranslated" {
|
||||||
|
t.Errorf("Expected 'Untranslated' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetND("default", "Untranslated", "Several untranslated", 2)
|
||||||
|
if tr != "Several untranslated" {
|
||||||
|
t.Errorf("Expected 'Several untranslated' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPackageRace(t *testing.T) {
|
func TestPackageRace(t *testing.T) {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
str := `# Some comment
|
str := `# Some comment
|
||||||
@@ -184,6 +262,7 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
c1 := make(chan bool)
|
c1 := make(chan bool)
|
||||||
c2 := make(chan bool)
|
c2 := make(chan bool)
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
// Test translations
|
// Test translations
|
||||||
go func(done chan bool) {
|
go func(done chan bool) {
|
||||||
Get("My text")
|
Get("My text")
|
||||||
@@ -196,4 +275,5 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
}(c2)
|
}(c2)
|
||||||
|
|
||||||
Get("My text")
|
Get("My text")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ multiple languages at the same time by working with this object.
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create Locale with library path and language code
|
// Create Locale with library path and language code
|
||||||
@@ -24,13 +27,13 @@ Example:
|
|||||||
l.AddDomain("default")
|
l.AddDomain("default")
|
||||||
|
|
||||||
// Translate text from default domain
|
// Translate text from default domain
|
||||||
println(l.Get("Translate this"))
|
fmt.Println(l.Get("Translate this"))
|
||||||
|
|
||||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
|
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
|
||||||
l.AddDomain("extras")
|
l.AddDomain("extras")
|
||||||
|
|
||||||
// Translate text from domain
|
// Translate text from domain
|
||||||
println(l.GetD("extras", "Translate this"))
|
fmt.Println(l.GetD("extras", "Translate this"))
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|||||||
18
po.go
18
po.go
@@ -3,14 +3,13 @@ package gotext
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/mattn/kinako/vm"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/mattn/kinako/vm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type translation struct {
|
type translation struct {
|
||||||
@@ -46,7 +45,12 @@ func (t *translation) getN(n int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return unstranlated plural by default
|
// Return unstranlated singular if corresponding
|
||||||
|
if n == 0 {
|
||||||
|
return t.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return untranslated plural by default
|
||||||
return t.pluralID
|
return t.pluralID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +61,10 @@ And it's safe for concurrent use by multiple goroutines by using the sync packag
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
import "github.com/leonelquinteros/gotext"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create po object
|
// Create po object
|
||||||
@@ -67,7 +74,7 @@ Example:
|
|||||||
po.ParseFile("/path/to/po/file/translations.po")
|
po.ParseFile("/path/to/po/file/translations.po")
|
||||||
|
|
||||||
// Get translation
|
// Get translation
|
||||||
println(po.Get("Translate this"))
|
fmt.Println(po.Get("Translate this"))
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
@@ -439,6 +446,7 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
if n == 1 {
|
if n == 1 {
|
||||||
return fmt.Sprintf(str, vars...)
|
return fmt.Sprintf(str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(plural, vars...)
|
return fmt.Sprintf(plural, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user