Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75a1fb9b04 | ||
|
|
3bb41424ff | ||
|
|
d791a97f01 | ||
|
|
192fa01364 | ||
|
|
362a1d6915 | ||
|
|
60d22914a5 | ||
|
|
f7464201e6 | ||
|
|
d26d707c73 | ||
|
|
b6df672e9a | ||
|
|
c40a6e9dd5 | ||
|
|
be1a13b346 | ||
|
|
36588ae653 | ||
|
|
20f50a5bba | ||
|
|
5b0ba55f37 | ||
|
|
df996c3ae1 | ||
|
|
f46758caed | ||
|
|
4aa838b8c3 | ||
|
|
478f4d29b7 | ||
|
|
9091772533 | ||
|
|
3b92b162e1 | ||
|
|
b8c9a57c39 | ||
|
|
1891633250 | ||
|
|
75a3d22c53 | ||
|
|
2b59b30398 | ||
|
|
208807f5ca | ||
|
|
823ca32c7a | ||
|
|
99a9166ded | ||
|
|
6431cb3aea | ||
|
|
0e382cfe26 | ||
|
|
ff3209d159 | ||
|
|
0216b71049 | ||
|
|
37bac2fe57 | ||
|
|
7b73c0d36b |
35
.github/workflows/build.yml
vendored
Normal file
35
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Gotext build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Set up Go 1.13
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.13
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run: |
|
||||||
|
go get -v -u -t -d ./...
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: go build -v .
|
||||||
|
|
||||||
|
- name: Install xgotext CLI
|
||||||
|
run: go install -v github.com/leonelquinteros/gotext/cli/xgotext
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -v -race ./...
|
||||||
13
.travis.yml
13
.travis.yml
@@ -1,19 +1,8 @@
|
|||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- "1.6"
|
|
||||||
- "1.7"
|
|
||||||
- "1.8"
|
|
||||||
- "1.9"
|
- "1.9"
|
||||||
- "1.10"
|
- "1.10"
|
||||||
- "1.11"
|
- "1.11"
|
||||||
|
- "1.12"
|
||||||
- "tip"
|
- "tip"
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go get -t -v ./...
|
|
||||||
|
|
||||||
script: go test -v -race -coverprofile=coverage.txt -covermode=atomic .
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
|
||||||
|
|
||||||
|
|||||||
9
Gopkg.lock
generated
9
Gopkg.lock
generated
@@ -1,9 +0,0 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "ab4fef131ee828e96ba67d31a7d690bd5f2f42040c6766b1b12fe856f87e0ff7"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
||||||
30
Gopkg.toml
30
Gopkg.toml
@@ -1,30 +0,0 @@
|
|||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
||||||
25
README.md
25
README.md
@@ -1,8 +1,8 @@
|
|||||||
[](https://github.com/leonelquinteros/gotext)
|
[](https://github.com/leonelquinteros/gotext)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
||||||
|

|
||||||
[](https://travis-ci.org/leonelquinteros/gotext)
|
[](https://travis-ci.org/leonelquinteros/gotext)
|
||||||
[](https://codecov.io/gh/leonelquinteros/gotext)
|
|
||||||
[](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
|
[](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
|
||||||
|
|
||||||
# Gotext
|
# Gotext
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
|
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
|
||||||
- Ready to use inside Go templates.
|
- Ready to use inside Go templates.
|
||||||
- Objects are serializable to []byte to store them in cache.
|
- Objects are serializable to []byte to store them in cache.
|
||||||
|
- Support for Go Modules.
|
||||||
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
@@ -48,19 +49,31 @@ go get github.com/leonelquinteros/gotext
|
|||||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
- No need for environment variables. Some naming conventions are applied but not needed.
|
||||||
|
|
||||||
|
|
||||||
### Version vendoring
|
## Version vendoring
|
||||||
|
|
||||||
Stable releases use [semantic versioning](http://semver.org/spec/v2.0.0.html) tagging on this repository.
|
Stable releases use [semantic versioning](http://semver.org/spec/v2.0.0.html) tagging on this repository.
|
||||||
|
|
||||||
You can rely on this to use your preferred vendoring tool or to manually retrieve the corresponding release tag from the GitHub repository.
|
You can rely on this to use your preferred vendoring tool or to manually retrieve the corresponding release tag from the GitHub repository.
|
||||||
|
|
||||||
|
|
||||||
#### Vendoring with [dep](https://golang.github.io/dep/)
|
### Vendoring with [Go Modules](https://github.com/golang/go/wiki/Modules) (Recommended)
|
||||||
|
|
||||||
To use last stable version (v1.3.1 at the moment of writing)
|
Add `github.com/leonelquinteros/gotext` inside the `require` section in your `go.mod` file.
|
||||||
|
|
||||||
|
i.e.
|
||||||
|
```
|
||||||
|
require (
|
||||||
|
github.com/leonelquinteros/gotext v1.4.0
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 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.3.1
|
dep ensure -add github.com/leonelquinteros/gotext@v1.4.0
|
||||||
```
|
```
|
||||||
|
|
||||||
Import as
|
Import as
|
||||||
@@ -70,7 +83,7 @@ import "github.com/leonelquinteros/gotext"
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
|
### Vendoring with [gopkg.in](http://labix.org/gopkg.in)
|
||||||
|
|
||||||
[http://gopkg.in/leonelquinteros/gotext.v1](http://gopkg.in/leonelquinteros/gotext.v1)
|
[http://gopkg.in/leonelquinteros/gotext.v1](http://gopkg.in/leonelquinteros/gotext.v1)
|
||||||
|
|
||||||
|
|||||||
48
cli/xgotext/README.md
Normal file
48
cli/xgotext/README.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# xgotext
|
||||||
|
|
||||||
|
CLI tool to extract translation strings from Go packages into .POT files.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
go install github.com/leonelquinteros/gotext/cli/xgotext
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage of xgotext:
|
||||||
|
-default string
|
||||||
|
Name of default domain (default "default")
|
||||||
|
-exclude string
|
||||||
|
Comma separated list of directories to exclude (default ".git")
|
||||||
|
-in string
|
||||||
|
input dir: /path/to/go/pkg
|
||||||
|
-out string
|
||||||
|
output dir: /path/to/i18n/files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
This is the first (naive) implementation for this tool.
|
||||||
|
|
||||||
|
It will scan the Go package provided for method calls that matches the method names from the gotext package and write the corresponding translation files to the output directory.
|
||||||
|
|
||||||
|
Isn't able to parse calls to translation functions using parameters inside variables, if the translation string is inside a variable and that variable is used to invoke the translation function, this tool won't be able to parse that string. See this example code:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// This line will be added to the .po file
|
||||||
|
gotext.Get("Translate this")
|
||||||
|
|
||||||
|
tr := "Translate this string"
|
||||||
|
// The following line will NOT be added to the .pot file
|
||||||
|
gotext.Get(tr)
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI tool traverse sub-directories based on the given input directory.
|
||||||
|
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
Please
|
||||||
|
|
||||||
28
cli/xgotext/fixtures/i18n/default.po
Normal file
28
cli/xgotext/fixtures/i18n/default.po
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"X-Generator: xgotext\n"
|
||||||
|
|
||||||
|
|
||||||
|
#: fixtures/main.go:23
|
||||||
|
#. gotext.Get
|
||||||
|
msgid "My text on 'domain-name' domain"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: fixtures/main.go:38
|
||||||
|
#. l.GetN
|
||||||
|
msgid "Singular"
|
||||||
|
msgid_plural "Plural"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: fixtures/main.go:40
|
||||||
|
#. l.GetN
|
||||||
|
msgid "SingularVar"
|
||||||
|
msgid_plural "PluralVar"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
15
cli/xgotext/fixtures/i18n/domain.po
Normal file
15
cli/xgotext/fixtures/i18n/domain.po
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"X-Generator: xgotext\n"
|
||||||
|
|
||||||
|
|
||||||
|
#: fixtures/main.go:42
|
||||||
|
#. l.GetDC
|
||||||
|
msgctxt "ctx"
|
||||||
|
msgid "string"
|
||||||
|
msgstr ""
|
||||||
14
cli/xgotext/fixtures/i18n/domain2.po
Normal file
14
cli/xgotext/fixtures/i18n/domain2.po
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"X-Generator: xgotext\n"
|
||||||
|
|
||||||
|
|
||||||
|
#: fixtures/main.go:26
|
||||||
|
#. gotext.GetD
|
||||||
|
msgid "Another text on a different domain"
|
||||||
|
msgstr ""
|
||||||
22
cli/xgotext/fixtures/i18n/translations.po
Normal file
22
cli/xgotext/fixtures/i18n/translations.po
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"X-Generator: xgotext\n"
|
||||||
|
|
||||||
|
|
||||||
|
#: fixtures/main.go:35
|
||||||
|
#. l.GetD
|
||||||
|
msgid "Translate this"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: fixtures/main.go:43
|
||||||
|
#. l.GetNDC
|
||||||
|
msgctxt "NDC-CTX"
|
||||||
|
msgid "ndc"
|
||||||
|
msgid_plural "ndcs"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
90
cli/xgotext/fixtures/main.go
Normal file
90
cli/xgotext/fixtures/main.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/leonelquinteros/gotext"
|
||||||
|
alias "github.com/leonelquinteros/gotext"
|
||||||
|
"github.com/leonelquinteros/gotext/cli/xgotext/fixtures/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fake object with methods similar to gotext
|
||||||
|
type Fake struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get by id
|
||||||
|
func (f Fake) Get(id int) int {
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fake object with same methods as gotext
|
||||||
|
type Fake2 struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get by str
|
||||||
|
func (f Fake2) Get(s string) string {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Configure package
|
||||||
|
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||||
|
|
||||||
|
// Translate text from default domain
|
||||||
|
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||||
|
// same as before
|
||||||
|
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||||
|
|
||||||
|
// unsupported function call
|
||||||
|
trStr := "some string to translate"
|
||||||
|
fmt.Println(gotext.Get(trStr))
|
||||||
|
|
||||||
|
// same with alias package name
|
||||||
|
fmt.Println(alias.Get("alias call"))
|
||||||
|
|
||||||
|
// Translate text from a different domain without reconfigure
|
||||||
|
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||||
|
|
||||||
|
// Create Locale with library path and language code
|
||||||
|
l := gotext.NewLocale("/path/to/locales/root/dir", "es_UY")
|
||||||
|
|
||||||
|
// Load domain '/path/to/locales/root/dir/es_UY/default.po'
|
||||||
|
l.AddDomain("translations")
|
||||||
|
l.SetDomain("translations")
|
||||||
|
|
||||||
|
// Translate text from domain
|
||||||
|
fmt.Println(l.GetD("translations", "Translate this"))
|
||||||
|
|
||||||
|
// Get plural translations
|
||||||
|
l.GetN("Singular", "Plural", 4)
|
||||||
|
num := 17
|
||||||
|
l.GetN("SingularVar", "PluralVar", num)
|
||||||
|
|
||||||
|
l.GetDC("domain2", "string", "ctx")
|
||||||
|
l.GetNDC("translations", "ndc", "ndcs", 7, "NDC-CTX")
|
||||||
|
|
||||||
|
// try fake structs
|
||||||
|
f := Fake{}
|
||||||
|
f.Get(3)
|
||||||
|
|
||||||
|
f2 := Fake2{}
|
||||||
|
f2.Get("3")
|
||||||
|
|
||||||
|
// use translator of sub object
|
||||||
|
t := pkg.Translate{}
|
||||||
|
t.L.Get("translate package")
|
||||||
|
t.S.L.Get("translate sub package")
|
||||||
|
|
||||||
|
// redefine alias with fake struct
|
||||||
|
alias := Fake2{}
|
||||||
|
alias.Get("3")
|
||||||
|
|
||||||
|
err := errors.New("test")
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// dummy function
|
||||||
|
func dummy(locale *gotext.Locale) {
|
||||||
|
locale.Get("inside dummy")
|
||||||
|
}
|
||||||
16
cli/xgotext/fixtures/pkg/pkg.go
Normal file
16
cli/xgotext/fixtures/pkg/pkg.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
import "github.com/leonelquinteros/gotext"
|
||||||
|
|
||||||
|
type SubTranslate struct {
|
||||||
|
L gotext.Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
type Translate struct {
|
||||||
|
L gotext.Locale
|
||||||
|
S SubTranslate
|
||||||
|
}
|
||||||
|
|
||||||
|
func test() {
|
||||||
|
gotext.Get("inside sub package")
|
||||||
|
}
|
||||||
45
cli/xgotext/main.go
Normal file
45
cli/xgotext/main.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/leonelquinteros/gotext/cli/xgotext/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
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")
|
||||||
|
excludeDirs = flag.String("exclude", ".git", "Comma separated list of directories to exclude")
|
||||||
|
verbose = flag.Bool("v", false, "print currently handled directory")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Init logger
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
if *dirName == "" {
|
||||||
|
log.Fatal("No input directory given")
|
||||||
|
}
|
||||||
|
if *outputDir == "" {
|
||||||
|
log.Fatal("No output directory given")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &parser.DomainMap{
|
||||||
|
Default: *defaultDomain,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parser.ParseDirRec(*dirName, strings.Split(*excludeDirs, ","), data, *verbose)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = data.Save(*outputDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
195
cli/xgotext/parser/domain.go
Normal file
195
cli/xgotext/parser/domain.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Translation for a text to translate
|
||||||
|
type Translation struct {
|
||||||
|
MsgId string
|
||||||
|
MsgIdPlural string
|
||||||
|
Context string
|
||||||
|
SourceLocations []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddLocations to translation
|
||||||
|
func (t *Translation) AddLocations(locations []string) {
|
||||||
|
if t.SourceLocations == nil {
|
||||||
|
t.SourceLocations = locations
|
||||||
|
} else {
|
||||||
|
t.SourceLocations = append(t.SourceLocations, locations...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump translation as string
|
||||||
|
func (t *Translation) Dump() string {
|
||||||
|
data := make([]string, 0, len(t.SourceLocations)+5)
|
||||||
|
|
||||||
|
for _, location := range t.SourceLocations {
|
||||||
|
data = append(data, "#: "+location)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Context != "" {
|
||||||
|
data = append(data, "msgctxt "+t.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = append(data, "msgid "+t.MsgId)
|
||||||
|
|
||||||
|
if t.MsgIdPlural == "" {
|
||||||
|
data = append(data, "msgstr \"\"")
|
||||||
|
} else {
|
||||||
|
data = append(data,
|
||||||
|
"msgid_plural "+t.MsgIdPlural,
|
||||||
|
"msgstr[0] \"\"",
|
||||||
|
"msgstr[1] \"\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(data, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TranslationMap contains a map of translations with the ID as key
|
||||||
|
type TranslationMap map[string]*Translation
|
||||||
|
|
||||||
|
// Dump the translation map as string
|
||||||
|
func (m TranslationMap) Dump() string {
|
||||||
|
// sort by translation id for consistence output
|
||||||
|
keys := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
data := make([]string, 0, len(m))
|
||||||
|
for _, key := range keys {
|
||||||
|
data = append(data, (m)[key].Dump())
|
||||||
|
}
|
||||||
|
return strings.Join(data, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain holds all translations of one domain
|
||||||
|
type Domain struct {
|
||||||
|
Translations TranslationMap
|
||||||
|
ContextTranslations map[string]TranslationMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTranslation to the domain
|
||||||
|
func (d *Domain) AddTranslation(translation *Translation) {
|
||||||
|
if d.Translations == nil {
|
||||||
|
d.Translations = make(TranslationMap)
|
||||||
|
d.ContextTranslations = make(map[string]TranslationMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if translation.Context == "" {
|
||||||
|
if t, ok := d.Translations[translation.MsgId]; ok {
|
||||||
|
t.AddLocations(translation.SourceLocations)
|
||||||
|
} else {
|
||||||
|
d.Translations[translation.MsgId] = translation
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := d.ContextTranslations[translation.Context]; !ok {
|
||||||
|
d.ContextTranslations[translation.Context] = make(TranslationMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := d.ContextTranslations[translation.Context][translation.MsgId]; ok {
|
||||||
|
t.AddLocations(translation.SourceLocations)
|
||||||
|
} else {
|
||||||
|
d.ContextTranslations[translation.Context][translation.MsgId] = translation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump the domain as string
|
||||||
|
func (d *Domain) Dump() string {
|
||||||
|
data := make([]string, 0, len(d.ContextTranslations)+1)
|
||||||
|
data = append(data, d.Translations.Dump())
|
||||||
|
|
||||||
|
// sort context translations by context for consistence output
|
||||||
|
keys := make([]string, 0, len(d.ContextTranslations))
|
||||||
|
for k := range d.ContextTranslations {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
data = append(data, d.ContextTranslations[key].Dump())
|
||||||
|
}
|
||||||
|
return strings.Join(data, "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save domain to file
|
||||||
|
func (d *Domain) Save(path string) error {
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to domain: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// write header
|
||||||
|
_, err = file.WriteString(`msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"X-Generator: xgotext\n"
|
||||||
|
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// write domain content
|
||||||
|
_, err = file.WriteString(d.Dump())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainMap contains multiple domains as map with name as key
|
||||||
|
type DomainMap struct {
|
||||||
|
Domains map[string]*Domain
|
||||||
|
Default string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTranslation to domain map
|
||||||
|
func (m *DomainMap) AddTranslation(domain string, translation *Translation) {
|
||||||
|
if m.Domains == nil {
|
||||||
|
m.Domains = make(map[string]*Domain, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// use "default" as default domain if not set
|
||||||
|
if m.Default == "" {
|
||||||
|
m.Default = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
// no domain given -> use default
|
||||||
|
if domain == "" {
|
||||||
|
domain = m.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := m.Domains[domain]; !ok {
|
||||||
|
m.Domains[domain] = new(Domain)
|
||||||
|
}
|
||||||
|
m.Domains[domain].AddTranslation(translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save domains to directory
|
||||||
|
func (m *DomainMap) Save(directory string) error {
|
||||||
|
// ensure output directory exist
|
||||||
|
err := os.MkdirAll(directory, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create output dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save each domain in a separate po file
|
||||||
|
for name, domain := range m.Domains {
|
||||||
|
err := domain.Save(filepath.Join(directory, name+".pot"))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save domain %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
271
cli/xgotext/parser/golang.go
Normal file
271
cli/xgotext/parser/golang.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetterDef describes a getter
|
||||||
|
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},
|
||||||
|
}
|
||||||
|
|
||||||
|
// register go parser
|
||||||
|
func init() {
|
||||||
|
AddParser(goParser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse go package
|
||||||
|
func goParser(dirPath, basePath string, data *DomainMap) error {
|
||||||
|
fileSet := token.NewFileSet()
|
||||||
|
|
||||||
|
conf := packages.Config{
|
||||||
|
Mode: packages.NeedName |
|
||||||
|
packages.NeedFiles |
|
||||||
|
packages.NeedSyntax |
|
||||||
|
packages.NeedTypes |
|
||||||
|
packages.NeedTypesInfo,
|
||||||
|
Fset: fileSet,
|
||||||
|
Dir: basePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// load package from path
|
||||||
|
pkgs, err := packages.Load(&packages.Config{
|
||||||
|
Mode: conf.Mode,
|
||||||
|
Fset: fileSet,
|
||||||
|
Dir: dirPath,
|
||||||
|
})
|
||||||
|
if err != nil || len(pkgs) == 0 {
|
||||||
|
// not a go package
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle each file
|
||||||
|
for _, node := range pkgs[0].Syntax {
|
||||||
|
file := GoFile{
|
||||||
|
pkgConf: &conf,
|
||||||
|
filePath: fileSet.Position(node.Package).Filename,
|
||||||
|
basePath: basePath,
|
||||||
|
data: data,
|
||||||
|
fileSet: fileSet,
|
||||||
|
|
||||||
|
importedPackages: map[string]*packages.Package{
|
||||||
|
pkgs[0].Name: pkgs[0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.Inspect(node, file.inspectFile)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GoFile handles the parsing of one go file
|
||||||
|
type GoFile struct {
|
||||||
|
filePath string
|
||||||
|
basePath string
|
||||||
|
data *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) {
|
||||||
|
pkgs, err := packages.Load(g.pkgConf, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return pkgs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getType from ident object
|
||||||
|
func (g *GoFile) getType(ident *ast.Ident) types.Object {
|
||||||
|
for _, pkg := range g.importedPackages {
|
||||||
|
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() != "github.com/leonelquinteros/gotext" {
|
||||||
|
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 {
|
||||||
|
pkg, ok := g.importedPackages[e.Name]
|
||||||
|
if !ok || pkg.PkgPath != "github.com/leonelquinteros/gotext" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 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 := 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)
|
||||||
|
}
|
||||||
67
cli/xgotext/parser/parser.go
Normal file
67
cli/xgotext/parser/parser.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseDirFunc parses one directory
|
||||||
|
type ParseDirFunc func(filePath, basePath string, data *DomainMap) error
|
||||||
|
|
||||||
|
var knownParser []ParseDirFunc
|
||||||
|
|
||||||
|
// AddParser to the known parser list
|
||||||
|
func AddParser(parser ParseDirFunc) {
|
||||||
|
if knownParser == nil {
|
||||||
|
knownParser = []ParseDirFunc{parser}
|
||||||
|
} else {
|
||||||
|
knownParser = append(knownParser, parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDir calls all known parser for each directory
|
||||||
|
func ParseDir(dirPath, basePath string, data *DomainMap) error {
|
||||||
|
dirPath, _ = filepath.Abs(dirPath)
|
||||||
|
basePath, _ = filepath.Abs(basePath)
|
||||||
|
|
||||||
|
for _, parser := range knownParser {
|
||||||
|
err := parser(dirPath, basePath, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDirRec calls all known parser for each directory
|
||||||
|
func ParseDirRec(dirPath string, exclude []string, data *DomainMap, verbose bool) error {
|
||||||
|
dirPath, _ = filepath.Abs(dirPath)
|
||||||
|
|
||||||
|
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsDir() {
|
||||||
|
// skip directory if in exclude list
|
||||||
|
subDir, _ := filepath.Rel(dirPath, path)
|
||||||
|
for _, d := range exclude {
|
||||||
|
if strings.HasPrefix(subDir, d) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
log.Print(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ParseDir(path, dirPath, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
||||||
|
|
||||||
msgid "Alcohol & Tobacco"
|
msgid "Alcohol & Tobacco"
|
||||||
msgstr "الكحول والتبغ"
|
msgstr "الكحول والتبغ"
|
||||||
|
|
||||||
|
# this test data is purposely missing msgstr
|
||||||
|
msgid "%d selected"
|
||||||
|
msgid_plural "%d selected"
|
||||||
|
|
||||||
|
msgid "Load %d more document"
|
||||||
|
msgid_plural "Load %d more documents"
|
||||||
|
msgstr[0] "حمّل %d مستندات إضافيّة"
|
||||||
|
msgstr[1] "حمّل مستند واحد إضافي"
|
||||||
|
msgstr[2] "حمّل مستندين إضافيين"
|
||||||
|
msgstr[3] "حمّل %d مستندات إضافيّة"
|
||||||
|
msgstr[4] "حمّل %d مستندا إضافيّا"
|
||||||
|
msgstr[5] "حمّل %d مستند إضافي"
|
||||||
2
fixtures/ar/no_plural_header.po
Normal file
2
fixtures/ar/no_plural_header.po
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
msgid "Alcohol & Tobacco"
|
||||||
|
msgstr "الكحول والتبغ"
|
||||||
4
go.mod
4
go.mod
@@ -1,3 +1,7 @@
|
|||||||
module github.com/leonelquinteros/gotext
|
module github.com/leonelquinteros/gotext
|
||||||
|
|
||||||
// go: no requirements found in Gopkg.lock
|
// go: no requirements found in Gopkg.lock
|
||||||
|
|
||||||
|
require golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|||||||
14
go.sum
Normal file
14
go.sum
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/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=
|
||||||
30
gotext.go
30
gotext.go
@@ -171,7 +171,20 @@ func GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
// GetD returns the corresponding Translation in the given domain for a given string.
|
// GetD returns the corresponding Translation in the given domain for a given string.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetD(dom, str string, vars ...interface{}) string {
|
func GetD(dom, str string, vars ...interface{}) string {
|
||||||
return GetND(dom, str, str, 1, vars...)
|
// Try to load default package Locale storage
|
||||||
|
loadStorage(false)
|
||||||
|
|
||||||
|
// Return Translation
|
||||||
|
globalConfig.RLock()
|
||||||
|
|
||||||
|
if _, ok := globalConfig.storage.Domains[dom]; !ok {
|
||||||
|
globalConfig.storage.AddDomain(dom)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := globalConfig.storage.GetD(dom, str, vars...)
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetND retrieves the (N)th plural form of Translation in the given domain for a given string.
|
// GetND retrieves the (N)th plural form of Translation in the given domain for a given string.
|
||||||
@@ -182,6 +195,11 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
|||||||
|
|
||||||
// Return Translation
|
// Return Translation
|
||||||
globalConfig.RLock()
|
globalConfig.RLock()
|
||||||
|
|
||||||
|
if _, ok := globalConfig.storage.Domains[dom]; !ok {
|
||||||
|
globalConfig.storage.AddDomain(dom)
|
||||||
|
}
|
||||||
|
|
||||||
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||||
globalConfig.RUnlock()
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
@@ -203,7 +221,15 @@ func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
|||||||
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||||
return GetNDC(dom, str, str, 1, ctx, vars...)
|
// Try to load default package Locale storage
|
||||||
|
loadStorage(false)
|
||||||
|
|
||||||
|
// Return Translation
|
||||||
|
globalConfig.RLock()
|
||||||
|
tr := globalConfig.storage.GetDC(dom, str, ctx, vars...)
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNDC retrieves the (N)th plural form of Translation in the given domain for a given string.
|
// GetNDC retrieves the (N)th plural form of Translation in the given domain for a given string.
|
||||||
|
|||||||
@@ -82,6 +82,22 @@ msgstr[1] ""
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
anotherStr := `
|
||||||
|
msgid ""
|
||||||
|
msgstr "Project-Id-Version: %s\n"
|
||||||
|
"Report-Msgid-Bugs-To: %s\n"
|
||||||
|
|
||||||
|
# Initial comment
|
||||||
|
# More 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 "Another text on a different domain"
|
||||||
|
msgstr "Another text on another domain"
|
||||||
|
`
|
||||||
|
|
||||||
// Create Locales directory on default location
|
// Create Locales directory on default location
|
||||||
dirname := path.Join("/tmp", "en_US")
|
dirname := path.Join("/tmp", "en_US")
|
||||||
err := os.MkdirAll(dirname, os.ModePerm)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
@@ -102,8 +118,21 @@ msgstr[1] ""
|
|||||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anotherFilename := path.Join(dirname, "another.po")
|
||||||
|
|
||||||
|
af, err := os.Create(anotherFilename)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't create test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = af.WriteString(anotherStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// Move file close to write the file, so we can use it in the next step
|
// Move file close to write the file, so we can use it in the next step
|
||||||
f.Close()
|
f.Close()
|
||||||
|
af.Close()
|
||||||
|
|
||||||
// Set package configuration
|
// Set package configuration
|
||||||
Configure("/tmp", "en_US", "default")
|
Configure("/tmp", "en_US", "default")
|
||||||
@@ -142,6 +171,11 @@ msgstr[1] ""
|
|||||||
if tr != "This one is the plural in a Ctx context: Variable" {
|
if tr != "This one is the plural in a Ctx context: Variable" {
|
||||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Variable' but got '%s'", tr)
|
t.Errorf("Expected 'This one is the plural in a Ctx context: Variable' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr = GetD("another", "Another text on a different domain")
|
||||||
|
if tr != "Another text on another domain" {
|
||||||
|
t.Errorf("Expected 'Another text on another domain' but got '%s'", tr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUntranslated(t *testing.T) {
|
func TestUntranslated(t *testing.T) {
|
||||||
@@ -418,3 +452,60 @@ msgstr "Some random Translation in a context"
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPackageArabicTranslation(t *testing.T) {
|
||||||
|
Configure("fixtures/", "ar", "categories")
|
||||||
|
|
||||||
|
// Plurals formula missing + Plural translation string missing
|
||||||
|
tr := GetD("categories", "Alcohol & Tobacco")
|
||||||
|
if tr != "الكحول والتبغ" {
|
||||||
|
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plural translation string present without translations, should get the msgid_plural
|
||||||
|
tr = GetND("categories", "%d selected", "%d selected", 10)
|
||||||
|
if tr != "%d selected" {
|
||||||
|
t.Errorf("Expected to get '%%d selected', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Plurals formula present + Plural translation string present and complete
|
||||||
|
tr = GetND("categories", "Load %d more document", "Load %d more documents", 0)
|
||||||
|
if tr != "حمّل %d مستندات إضافيّة" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[0]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetND("categories", "Load %d more document", "Load %d more documents", 1)
|
||||||
|
if tr != "حمّل مستند واحد إضافي" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[1]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetND("categories", "Load %d more document", "Load %d more documents", 2)
|
||||||
|
if tr != "حمّل مستندين إضافيين" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[2]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetND("categories", "Load %d more document", "Load %d more documents", 6)
|
||||||
|
if tr != "حمّل %d مستندات إضافيّة" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[3]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetND("categories", "Load %d more document", "Load %d more documents", 116)
|
||||||
|
if tr != "حمّل %d مستندا إضافيّا" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[4]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = GetND("categories", "Load %d more document", "Load %d more documents", 102)
|
||||||
|
if tr != "حمّل %d مستند إضافي" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[5]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackageArabicMissingPluralForm(t *testing.T) {
|
||||||
|
Configure("fixtures/", "ar", "no_plural_header")
|
||||||
|
|
||||||
|
// Get translation
|
||||||
|
tr := GetD("no_plural_header", "Alcohol & Tobacco")
|
||||||
|
if tr != "الكحول والتبغ" {
|
||||||
|
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
145
locale.go
145
locale.go
@@ -152,7 +152,7 @@ func (l *Locale) AddTranslator(dom string, tr Translator) {
|
|||||||
l.Unlock()
|
l.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDomain is the domain getter for the package configuration
|
// GetDomain is the domain getter for Locale configuration
|
||||||
func (l *Locale) GetDomain() string {
|
func (l *Locale) GetDomain() string {
|
||||||
l.RLock()
|
l.RLock()
|
||||||
dom := l.defaultDomain
|
dom := l.defaultDomain
|
||||||
@@ -182,7 +182,19 @@ func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
// GetD returns the corresponding Translation in the given domain for the given string.
|
// GetD returns the corresponding Translation in the given domain for the given string.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||||
return l.GetND(dom, str, str, 1, vars...)
|
// Sync read
|
||||||
|
l.RLock()
|
||||||
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
if l.Domains != nil {
|
||||||
|
if _, ok := l.Domains[dom]; ok {
|
||||||
|
if l.Domains[dom] != nil {
|
||||||
|
return l.Domains[dom].Get(str, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Printf(str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetND retrieves the (N)th plural form of Translation in the given domain for the given string.
|
// GetND retrieves the (N)th plural form of Translation in the given domain for the given string.
|
||||||
@@ -200,7 +212,10 @@ func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the same we received by default
|
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||||
|
if n == 1 {
|
||||||
|
return Printf(str, vars...)
|
||||||
|
}
|
||||||
return Printf(plural, vars...)
|
return Printf(plural, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +234,19 @@ func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{
|
|||||||
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||||
return l.GetNDC(dom, str, str, 1, ctx, vars...)
|
// Sync read
|
||||||
|
l.RLock()
|
||||||
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
if l.Domains != nil {
|
||||||
|
if _, ok := l.Domains[dom]; ok {
|
||||||
|
if l.Domains[dom] != nil {
|
||||||
|
return l.Domains[dom].GetC(str, ctx, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Printf(str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNDC retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
|
// GetNDC retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
|
||||||
@@ -237,10 +264,118 @@ func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...inte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the same we received by default
|
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||||
|
if n == 1 {
|
||||||
|
return Printf(str, vars...)
|
||||||
|
}
|
||||||
return Printf(plural, vars...)
|
return Printf(plural, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Euses a domain "default" to return the corresponding Translation of a given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetE(str string, vars ...interface{}) (string, bool) {
|
||||||
|
return l.GetDE(l.GetDomain(), str, vars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNE retrieves the (N)th plural form of Translation for the given string in the "default" domain.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||||
|
return l.GetNDE(l.GetDomain(), str, plural, n, vars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDE returns the corresponding Translation in the given domain for the given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetDE(dom, str string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
l.RLock()
|
||||||
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
if l.Domains != nil {
|
||||||
|
if _, ok := l.Domains[dom]; ok {
|
||||||
|
if l.Domains[dom] != nil {
|
||||||
|
return l.Domains[dom].GetE(str, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNDE retrieves the (N)th plural form of Translation in the given domain for the given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetNDE(dom, str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
l.RLock()
|
||||||
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
if l.Domains != nil {
|
||||||
|
if _, ok := l.Domains[dom]; ok {
|
||||||
|
if l.Domains[dom] != nil {
|
||||||
|
return l.Domains[dom].GetNE(str, plural, n, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetC uses a domain "default" to return the corresponding Translation of the given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
return l.GetDCE(l.GetDomain(), str, ctx, vars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
return l.GetNDCE(l.GetDomain(), str, plural, n, ctx, vars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDCE returns the corresponding Translation in the given domain for the given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
func (l *Locale) GetDCE(dom, str, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
l.RLock()
|
||||||
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
if l.Domains != nil {
|
||||||
|
if _, ok := l.Domains[dom]; ok {
|
||||||
|
if l.Domains[dom] != nil {
|
||||||
|
return l.Domains[dom].GetCE(str, ctx, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNDCE retrieves the (N)th plural form of Translation in the given domain for the given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (l *Locale) GetNDCE(dom, str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
l.RLock()
|
||||||
|
defer l.RUnlock()
|
||||||
|
|
||||||
|
if l.Domains != nil {
|
||||||
|
if _, ok := l.Domains[dom]; ok {
|
||||||
|
if l.Domains[dom] != nil {
|
||||||
|
return l.Domains[dom].GetNCE(str, plural, n, ctx, vars...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
|
// LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
|
||||||
type LocaleEncoding struct {
|
type LocaleEncoding struct {
|
||||||
Path string
|
Path string
|
||||||
|
|||||||
@@ -255,6 +255,11 @@ msgstr "More Translation"
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr = l.GetN("This is a test", "This are tests", 1)
|
tr = l.GetN("This is a test", "This are tests", 1)
|
||||||
|
if tr != "This is a test" {
|
||||||
|
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = l.GetN("This is a test", "This are tests", 7)
|
||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
@@ -266,8 +271,12 @@ msgstr "More Translation"
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||||
|
if tr != "This one has invalid syntax translations" {
|
||||||
|
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 2)
|
||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Locale with full language code
|
// Create Locale with full language code
|
||||||
@@ -292,8 +301,12 @@ msgstr "More Translation"
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||||
|
if tr != "This one has invalid syntax translations" {
|
||||||
|
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 111)
|
||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Locale with full language code
|
// Create Locale with full language code
|
||||||
@@ -318,8 +331,12 @@ msgstr "More Translation"
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||||
|
if tr != "This one has invalid syntax translations" {
|
||||||
|
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 21)
|
||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Locale with full language code
|
// Create Locale with full language code
|
||||||
@@ -344,8 +361,12 @@ msgstr "More Translation"
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||||
|
if tr != "This one has invalid syntax translations" {
|
||||||
|
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 2)
|
||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Locale with full language code
|
// Create Locale with full language code
|
||||||
@@ -376,8 +397,12 @@ msgstr "More Translation"
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||||
|
if tr != "This one has invalid syntax translations" {
|
||||||
|
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = l.GetN("This one has invalid syntax translations", "This are tests", 14)
|
||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,11 +504,63 @@ func TestArabicTranslation(t *testing.T) {
|
|||||||
// Add domain
|
// Add domain
|
||||||
l.AddDomain("categories")
|
l.AddDomain("categories")
|
||||||
|
|
||||||
// Get translation
|
// Plurals formula missing + Plural translation string missing
|
||||||
tr := l.GetD("categories", "Alcohol & Tobacco")
|
tr := l.GetD("categories", "Alcohol & Tobacco")
|
||||||
if tr != "الكحول والتبغ" {
|
if tr != "الكحول والتبغ" {
|
||||||
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plural translation string present without translations, should get the msgid_plural
|
||||||
|
tr = l.GetND("categories", "%d selected", "%d selected", 10)
|
||||||
|
if tr != "%d selected" {
|
||||||
|
t.Errorf("Expected to get '%%d selected', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Plurals formula present + Plural translation string present and complete
|
||||||
|
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 0)
|
||||||
|
if tr != "حمّل %d مستندات إضافيّة" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[0]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 1)
|
||||||
|
if tr != "حمّل مستند واحد إضافي" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[1]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 2)
|
||||||
|
if tr != "حمّل مستندين إضافيين" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[2]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 6)
|
||||||
|
if tr != "حمّل %d مستندات إضافيّة" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[3]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 116)
|
||||||
|
if tr != "حمّل %d مستندا إضافيّا" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[4]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = l.GetND("categories", "Load %d more document", "Load %d more documents", 102)
|
||||||
|
if tr != "حمّل %d مستند إضافي" {
|
||||||
|
t.Errorf("Expected to get 'msgstr[5]', but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArabicMissingPluralForm(t *testing.T) {
|
||||||
|
// Create Locale
|
||||||
|
l := NewLocale("fixtures/", "ar")
|
||||||
|
|
||||||
|
// Add domain
|
||||||
|
l.AddDomain("no_plural_header")
|
||||||
|
|
||||||
|
// Get translation
|
||||||
|
tr := l.GetD("no_plural_header", "Alcohol & Tobacco")
|
||||||
|
if tr != "الكحول والتبغ" {
|
||||||
|
t.Errorf("Expected to get 'الكحول والتبغ', but got '%s'", tr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLocaleBinaryEncoding(t *testing.T) {
|
func TestLocaleBinaryEncoding(t *testing.T) {
|
||||||
|
|||||||
84
mo.go
84
mo.go
@@ -427,6 +427,90 @@ func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
|||||||
return Printf(plural, vars...)
|
return Printf(plural, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetE retrieves the corresponding Translation for the given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (mo *Mo) GetE(str string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
mo.RLock()
|
||||||
|
defer mo.RUnlock()
|
||||||
|
|
||||||
|
if mo.translations != nil {
|
||||||
|
if _, ok := mo.translations[str]; ok {
|
||||||
|
if fmt, ok := mo.translations[str].GetE(); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNE retrieves the (N)th plural form of Translation for the given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (mo *Mo) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
mo.RLock()
|
||||||
|
defer mo.RUnlock()
|
||||||
|
|
||||||
|
if mo.translations != nil {
|
||||||
|
if _, ok := mo.translations[str]; ok {
|
||||||
|
if fmt, ok := mo.translations[str].GetNE(mo.pluralForm(n)); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCE retrieves the corresponding Translation for a given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (mo *Mo) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
mo.RLock()
|
||||||
|
defer mo.RUnlock()
|
||||||
|
|
||||||
|
if mo.contexts != nil {
|
||||||
|
if _, ok := mo.contexts[ctx]; ok {
|
||||||
|
if mo.contexts[ctx] != nil {
|
||||||
|
if _, ok := mo.contexts[ctx][str]; ok {
|
||||||
|
if fmt, ok := mo.contexts[ctx][str].GetE(); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNCE retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (mo *Mo) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
mo.RLock()
|
||||||
|
defer mo.RUnlock()
|
||||||
|
|
||||||
|
if mo.contexts != nil {
|
||||||
|
if _, ok := mo.contexts[ctx]; ok {
|
||||||
|
if mo.contexts[ctx] != nil {
|
||||||
|
if _, ok := mo.contexts[ctx][str]; ok {
|
||||||
|
if fmt, ok := mo.contexts[ctx][str].GetNE(mo.pluralForm(n)); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||||
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
||||||
obj := new(TranslatorEncoding)
|
obj := new(TranslatorEncoding)
|
||||||
|
|||||||
93
po.go
93
po.go
@@ -365,7 +365,7 @@ func (po *Po) pluralForm(n int) int {
|
|||||||
|
|
||||||
// Failure fallback
|
// Failure fallback
|
||||||
if po.pluralforms == nil {
|
if po.pluralforms == nil {
|
||||||
/* Use the Germanic plural rule. */
|
/* Use Western plural rule. */
|
||||||
if n == 1 {
|
if n == 1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -404,7 +404,8 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == 1 {
|
// Parse plural forms to distinguish between plural and singular
|
||||||
|
if po.pluralForm(n) == 0 {
|
||||||
return Printf(str, vars...)
|
return Printf(str, vars...)
|
||||||
}
|
}
|
||||||
return Printf(plural, vars...)
|
return Printf(plural, vars...)
|
||||||
@@ -448,12 +449,98 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == 1 {
|
// Parse plural forms to distinguish between plural and singular
|
||||||
|
if po.pluralForm(n) == 0 {
|
||||||
return Printf(str, vars...)
|
return Printf(str, vars...)
|
||||||
}
|
}
|
||||||
return Printf(plural, vars...)
|
return Printf(plural, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Eretrieves the corresponding Translation for the given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (po *Po) GetE(str string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
po.RLock()
|
||||||
|
defer po.RUnlock()
|
||||||
|
|
||||||
|
if po.translations != nil {
|
||||||
|
if _, ok := po.translations[str]; ok {
|
||||||
|
if fmt, ok := po.translations[str].GetE(); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetN Eretrieves the (N)th plural form of Translation for the given string.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (po *Po) GetNE(str, plural string, n int, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
po.RLock()
|
||||||
|
defer po.RUnlock()
|
||||||
|
|
||||||
|
if po.translations != nil {
|
||||||
|
if _, ok := po.translations[str]; ok {
|
||||||
|
if fmt, ok := po.translations[str].GetNE(po.pluralForm(n)); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCE retrieves the corresponding Translation for a given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (po *Po) GetCE(str, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
po.RLock()
|
||||||
|
defer po.RUnlock()
|
||||||
|
|
||||||
|
if po.contexts != nil {
|
||||||
|
if _, ok := po.contexts[ctx]; ok {
|
||||||
|
if po.contexts[ctx] != nil {
|
||||||
|
if _, ok := po.contexts[ctx][str]; ok {
|
||||||
|
if fmt, ok := po.contexts[ctx][str].GetE(); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNCE retrieves the (N)th plural form of Translation for the given string in the given context.
|
||||||
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
|
// The second return value is true iff the string was found.
|
||||||
|
func (po *Po) GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool) {
|
||||||
|
// Sync read
|
||||||
|
po.RLock()
|
||||||
|
defer po.RUnlock()
|
||||||
|
|
||||||
|
if po.contexts != nil {
|
||||||
|
if _, ok := po.contexts[ctx]; ok {
|
||||||
|
if po.contexts[ctx] != nil {
|
||||||
|
if _, ok := po.contexts[ctx][str]; ok {
|
||||||
|
if fmt, ok := po.contexts[ctx][str].GetNE(po.pluralForm(n)); ok {
|
||||||
|
return Printf(fmt, vars...), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse plural forms to distinguish between plural and singular
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalBinary implements encoding.BinaryMarshaler interface
|
// MarshalBinary implements encoding.BinaryMarshaler interface
|
||||||
func (po *Po) MarshalBinary() ([]byte, error) {
|
func (po *Po) MarshalBinary() ([]byte, error) {
|
||||||
obj := new(TranslatorEncoding)
|
obj := new(TranslatorEncoding)
|
||||||
|
|||||||
16
po_test.go
16
po_test.go
@@ -177,6 +177,22 @@ msgstr "More Translation"
|
|||||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test translations with existence check
|
||||||
|
tr, exists := po.GetE("My text")
|
||||||
|
if (tr != "Translated text") || (!exists) {
|
||||||
|
t.Errorf("Expected 'Translated text', true but got '%s', %v", tr, exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr, exists = po.GetE("I don't exist")
|
||||||
|
if exists {
|
||||||
|
t.Errorf("Expected 'I don't exist' not to exist but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr = po.GetN("I don't exist", "We don't exist", 100)
|
||||||
|
if exists {
|
||||||
|
t.Errorf("Expected 'I/We don't exist' not to exist but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
// Test context translations
|
// Test context translations
|
||||||
v = "Test"
|
v = "Test"
|
||||||
tr = po.GetC("One with var: %s", "Ctx", v)
|
tr = po.GetC("One with var: %s", "Ctx", v)
|
||||||
|
|||||||
@@ -50,3 +50,29 @@ func (t *Translation) GetN(n int) string {
|
|||||||
// Return untranslated plural by default
|
// Return untranslated plural by default
|
||||||
return t.PluralID
|
return t.PluralID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get returns the string of the translation. The second return value is true
|
||||||
|
// iff the string was found.
|
||||||
|
func (t *Translation) GetE() (string, bool) {
|
||||||
|
// Look for Translation index 0
|
||||||
|
if _, ok := t.Trs[0]; ok {
|
||||||
|
if t.Trs[0] != "" {
|
||||||
|
return t.Trs[0], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetN returns the string of the plural translation. The second return value
|
||||||
|
// is true iff the string was found.
|
||||||
|
func (t *Translation) GetNE(n int) (string, bool) {
|
||||||
|
// Look for Translation index
|
||||||
|
if _, ok := t.Trs[n]; ok {
|
||||||
|
if t.Trs[n] != "" {
|
||||||
|
return t.Trs[n], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,11 +13,17 @@ import "net/textproto"
|
|||||||
type Translator interface {
|
type Translator interface {
|
||||||
ParseFile(f string)
|
ParseFile(f string)
|
||||||
Parse(buf []byte)
|
Parse(buf []byte)
|
||||||
|
|
||||||
Get(str string, vars ...interface{}) string
|
Get(str string, vars ...interface{}) string
|
||||||
GetN(str, plural string, n int, vars ...interface{}) string
|
GetN(str, plural string, n int, vars ...interface{}) string
|
||||||
GetC(str, ctx string, vars ...interface{}) string
|
GetC(str, ctx string, vars ...interface{}) string
|
||||||
GetNC(str, plural string, n int, ctx string, vars ...interface{}) string
|
GetNC(str, plural string, n int, ctx string, vars ...interface{}) string
|
||||||
|
|
||||||
|
GetE(str string, vars ...interface{}) (string, bool)
|
||||||
|
GetNE(str, plural string, n int, vars ...interface{}) (string, bool)
|
||||||
|
GetCE(str, ctx string, vars ...interface{}) (string, bool)
|
||||||
|
GetNCE(str, plural string, n int, ctx string, vars ...interface{}) (string, bool)
|
||||||
|
|
||||||
MarshalBinary() ([]byte, error)
|
MarshalBinary() ([]byte, error)
|
||||||
UnmarshalBinary([]byte) error
|
UnmarshalBinary([]byte) error
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user