Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4ab6497b5 | |||
| 2441e55972 | |||
|
|
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 git.deineagentur.com/DeineAgenturUG/gotext/cli/xgotext
|
||||
|
||||
- name: Test
|
||||
run: go test -v -race ./...
|
||||
13
.travis.yml
13
.travis.yml
@@ -1,19 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.6"
|
||||
- "1.7"
|
||||
- "1.8"
|
||||
- "1.9"
|
||||
- "1.10"
|
||||
- "1.11"
|
||||
- "1.12"
|
||||
- "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
|
||||
117
README.md
117
README.md
@@ -1,13 +1,13 @@
|
||||
[](https://github.com/leonelquinteros/gotext)
|
||||
[](https://git.deineagentur.com/DeineAgenturUG/gotext)
|
||||
[](LICENSE)
|
||||
[](https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
[](https://godoc.org/git.deineagentur.com/DeineAgenturUG/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/git.deineagentur.com/DeineAgenturUG/gotext)
|
||||
|
||||
# Gotext
|
||||
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
|
||||
|
||||
# Features
|
||||
@@ -18,13 +18,14 @@
|
||||
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
|
||||
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
|
||||
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
|
||||
- Support for MO files.
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- Support for MO files.
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- It works with UTF-8 encoding as it's the default for Go language.
|
||||
- Unit tests 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.
|
||||
- Objects are serializable to []byte to store them in cache.
|
||||
- Support for Go Modules.
|
||||
|
||||
|
||||
# License
|
||||
@@ -34,43 +35,55 @@
|
||||
|
||||
# Documentation
|
||||
|
||||
Refer to the Godoc package documentation at (https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
Refer to the Godoc package documentation at (https://godoc.org/git.deineagentur.com/DeineAgenturUG/gotext)
|
||||
|
||||
|
||||
# Installation
|
||||
# Installation
|
||||
|
||||
```
|
||||
go get github.com/leonelquinteros/gotext
|
||||
go get git.deineagentur.com/DeineAgenturUG/gotext
|
||||
```
|
||||
|
||||
- There are no requirements or dependencies to use this package.
|
||||
- There are no requirements or dependencies to use this package.
|
||||
- No need to install GNU gettext utilities (unless specific needs of CLI tools).
|
||||
- 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.
|
||||
|
||||
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 `git.deineagentur.com/DeineAgenturUG/gotext` inside the `require` section in your `go.mod` file.
|
||||
|
||||
i.e.
|
||||
```
|
||||
require (
|
||||
git.deineagentur.com/DeineAgenturUG/gotext v1.5.0
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
### Vendoring with [dep](https://golang.github.io/dep/)
|
||||
|
||||
To use last stable version (v1.5.0 at the moment of writing)
|
||||
|
||||
```
|
||||
dep ensure -add github.com/leonelquinteros/gotext@v1.3.1
|
||||
dep ensure -add git.deineagentur.com/DeineAgenturUG/gotext@v1.5.0
|
||||
```
|
||||
|
||||
Import as
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
import "git.deineagentur.com/DeineAgenturUG/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)
|
||||
|
||||
@@ -91,19 +104,19 @@ Refer to it as gotext.
|
||||
|
||||
# Locales directories structure
|
||||
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
|
||||
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
|
||||
```
|
||||
/path/to/locales
|
||||
@@ -128,7 +141,7 @@ A library directory structure can look like:
|
||||
/path/to/locales/fr
|
||||
/path/to/locales/fr/default.po
|
||||
/path/to/locales/fr/extras.po
|
||||
```
|
||||
```
|
||||
|
||||
And so on...
|
||||
|
||||
@@ -142,16 +155,16 @@ For quick/simple translations you can use the package level functions directly.
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
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"))
|
||||
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
@@ -160,22 +173,22 @@ func main() {
|
||||
|
||||
## Using dynamic variables on translations
|
||||
|
||||
All translation strings support dynamic variables to be inserted without translate.
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
All translation strings support dynamic variables to be inserted without translate.
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
|
||||
// Set variables
|
||||
name := "John"
|
||||
|
||||
|
||||
// Translate text with variables
|
||||
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||
}
|
||||
@@ -185,35 +198,35 @@ func main() {
|
||||
|
||||
## Using Locale object
|
||||
|
||||
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
|
||||
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
|
||||
so you can handle each settings on their own.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 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("default")
|
||||
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
|
||||
// Load different domain
|
||||
l.AddDomain("translations")
|
||||
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("translations", "Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
|
||||
```
|
||||
{{ .Loc.Get "Translate this" }}
|
||||
@@ -222,13 +235,13 @@ If you set the Locale object as "Loc" in the template, then the template code wo
|
||||
|
||||
## Using the Po object to handle .po files and PO-formatted strings
|
||||
|
||||
For when you need to work with PO files and strings,
|
||||
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.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -243,11 +256,11 @@ msgstr ""
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
`
|
||||
|
||||
|
||||
// Create Po object
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
```
|
||||
@@ -264,7 +277,7 @@ Plural formulas are parsed and evaluated using [Kinako](https://github.com/mattn
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -287,21 +300,21 @@ msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
`
|
||||
|
||||
|
||||
// Create Po object
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
|
||||
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||
// "This one is the plural: Variable"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Contribute
|
||||
# Contribute
|
||||
|
||||
- Please, contribute.
|
||||
- Use the package on your projects.
|
||||
- Report issues on Github.
|
||||
- Report issues on Github.
|
||||
- Send pull requests for bugfixes and improvements.
|
||||
- Send proposals on Github issues.
|
||||
|
||||
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 git.deineagentur.com/DeineAgenturUG/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"
|
||||
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
alias "git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/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 "git.deineagentur.com/DeineAgenturUG/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"
|
||||
|
||||
"git.deineagentur.com/DeineAgenturUG/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() != "git.deineagentur.com/DeineAgenturUG/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 != "git.deineagentur.com/DeineAgenturUG/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 ""
|
||||
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"
|
||||
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 "الكحول والتبغ"
|
||||
6
go.mod
6
go.mod
@@ -1,3 +1,7 @@
|
||||
module github.com/leonelquinteros/gotext
|
||||
module git.deineagentur.com/DeineAgenturUG/gotext
|
||||
|
||||
// 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=
|
||||
72
gotext.go
72
gotext.go
@@ -1,25 +1,25 @@
|
||||
/*
|
||||
Package gotext implements GNU gettext utilities.
|
||||
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
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"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
*/
|
||||
* Package gotext implements GNU gettext utilities.
|
||||
*
|
||||
* For quick/simple translations you can use the package level functions directly.
|
||||
*
|
||||
* import (
|
||||
* "fmt"
|
||||
* "git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
* )
|
||||
*
|
||||
* 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"))
|
||||
*
|
||||
* // Translate text from a different domain without reconfigure
|
||||
* fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
* }
|
||||
*
|
||||
*/
|
||||
package gotext
|
||||
|
||||
import (
|
||||
@@ -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.
|
||||
// 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 {
|
||||
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.
|
||||
@@ -182,6 +195,11 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
|
||||
if _, ok := globalConfig.storage.Domains[dom]; !ok {
|
||||
globalConfig.storage.AddDomain(dom)
|
||||
}
|
||||
|
||||
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||
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.
|
||||
// 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 {
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
dirname := path.Join("/tmp", "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
@@ -102,8 +118,21 @@ msgstr[1] ""
|
||||
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
|
||||
f.Close()
|
||||
af.Close()
|
||||
|
||||
// Set package configuration
|
||||
Configure("/tmp", "en_US", "default")
|
||||
@@ -142,6 +171,11 @@ msgstr[1] ""
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -418,3 +452,60 @@ msgstr "Some random Translation in a context"
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
153
locale.go
153
locale.go
@@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
/**
|
||||
Locale wraps the entire i18n collection for a single language (locale)
|
||||
It's used by the package functions, but it can also be used independently to handle
|
||||
multiple languages at the same time by working with this object.
|
||||
@@ -24,7 +24,7 @@ Example:
|
||||
"encoding/gob"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -41,10 +41,10 @@ Example:
|
||||
l.AddDomain("extras")
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("extras", "Translate this"))
|
||||
fmt.Println(l.GetD("extras", "Translate this"))
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
type Locale struct {
|
||||
// Path to locale files.
|
||||
path string
|
||||
@@ -152,7 +152,7 @@ func (l *Locale) AddTranslator(dom string, tr Translator) {
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
// GetDomain is the domain getter for Locale configuration
|
||||
func (l *Locale) GetDomain() string {
|
||||
l.RLock()
|
||||
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.
|
||||
// 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 {
|
||||
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.
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
// 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 {
|
||||
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.
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
// GetE uses 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
|
||||
}
|
||||
|
||||
// GetCE 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...)
|
||||
}
|
||||
|
||||
// GetNCE 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.
|
||||
type LocaleEncoding struct {
|
||||
Path string
|
||||
|
||||
@@ -255,6 +255,11 @@ msgstr "More Translation"
|
||||
}
|
||||
|
||||
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" {
|
||||
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)
|
||||
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" {
|
||||
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
|
||||
@@ -292,8 +301,12 @@ msgstr "More Translation"
|
||||
}
|
||||
|
||||
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" {
|
||||
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
|
||||
@@ -318,8 +331,12 @@ msgstr "More Translation"
|
||||
}
|
||||
|
||||
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" {
|
||||
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
|
||||
@@ -344,8 +361,12 @@ msgstr "More Translation"
|
||||
}
|
||||
|
||||
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" {
|
||||
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
|
||||
@@ -376,8 +397,12 @@ msgstr "More Translation"
|
||||
}
|
||||
|
||||
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" {
|
||||
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
|
||||
l.AddDomain("categories")
|
||||
|
||||
// Get translation
|
||||
// Plurals formula missing + Plural translation string missing
|
||||
tr := l.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 = 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) {
|
||||
|
||||
88
mo.go
88
mo.go
@@ -17,7 +17,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext/plurals"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -41,7 +41,7 @@ Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -427,6 +427,90 @@ func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
||||
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
|
||||
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
97
po.go
97
po.go
@@ -16,7 +16,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext/plurals"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -28,7 +28,7 @@ Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"git.deineagentur.com/DeineAgenturUG/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -365,7 +365,7 @@ func (po *Po) pluralForm(n int) int {
|
||||
|
||||
// Failure fallback
|
||||
if po.pluralforms == nil {
|
||||
/* Use the Germanic plural rule. */
|
||||
/* Use Western plural rule. */
|
||||
if n == 1 {
|
||||
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(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(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
|
||||
func (po *Po) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
24
po_test.go
24
po_test.go
@@ -62,7 +62,7 @@ msgid ""
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
msgid ""
|
||||
"multi"
|
||||
"line"
|
||||
"plural"
|
||||
@@ -71,7 +71,7 @@ msgstr "plural id with multiline content"
|
||||
|
||||
#Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
msgstr ""
|
||||
"Multi "
|
||||
"line"
|
||||
|
||||
@@ -177,6 +177,22 @@ msgstr "More Translation"
|
||||
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
|
||||
v = "Test"
|
||||
tr = po.GetC("One with var: %s", "Ctx", v)
|
||||
@@ -241,7 +257,7 @@ msgstr[0] "TR Singular: %s"
|
||||
msgstr[1] "TR Plural: %s"
|
||||
msgstr[2] "TR Plural 2: %s"
|
||||
|
||||
|
||||
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
@@ -271,7 +287,7 @@ msgstr[0] "TR Singular: %s"
|
||||
msgstr[1] "TR Plural: %s"
|
||||
msgstr[2] "TR Plural 2: %s"
|
||||
|
||||
|
||||
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
@@ -50,3 +50,29 @@ func (t *Translation) GetN(n int) string {
|
||||
// Return untranslated plural by default
|
||||
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 {
|
||||
ParseFile(f string)
|
||||
Parse(buf []byte)
|
||||
|
||||
Get(str string, vars ...interface{}) string
|
||||
GetN(str, plural string, n int, vars ...interface{}) string
|
||||
GetC(str, 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)
|
||||
UnmarshalBinary([]byte) error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user