38 Commits

Author SHA1 Message Date
c8ac87a60c update module to own version 2020-07-29 18:59:14 +02:00
67d7b9ca60 update module to own version 2020-07-29 18:09:14 +02:00
bb9b8c2585 fix path on different OS 2020-07-29 16:47:49 +02:00
b4ab6497b5 update module to own version 2020-07-27 18:17:50 +02:00
2441e55972 „go.mod“ ändern 2020-07-27 16:06:38 +00:00
Ben Sarah Golightly
75a1fb9b04 Add variants of Get functions with existence checks
Currently, a translation that doesn't exist just defaults to the
passed message ID.

It can be helpful to be able to catch these missing cases e.g. to
save to a log.

This PR implements variants ending in 'E' like GetE, GetNE.

This wasn't implemented at the package-level scope, because for
quick translations like that the extra flexibility probably isn't
needed.
2020-07-23 00:28:56 +01:00
Leonel Quinteros
3bb41424ff Merge pull request #40 from chrisvaughn/cv/ar_bug
call Translation.Get & GetC directly
2020-04-22 12:11:35 -03:00
Chris Vaughn
d791a97f01 call Get & GetC directly instead of using plural=1 for package methods 2020-04-19 14:50:56 -05:00
Chris Vaughn
192fa01364 call Translation.Get & GetC directly instead of calling Translation.GetN with default of plural=1
* add correct Arabic pluralform rules to test
2020-04-16 17:29:45 -05:00
Leonel Quinteros Carbano
362a1d6915 Add xgotext CLI tool installation to build process 2020-04-15 22:16:10 -03:00
Leonel Quinteros Carbano
60d22914a5 Remove old Build configuration. 2020-04-15 22:12:20 -03:00
Leonel Quinteros Carbano
f7464201e6 Add Github Build action badge 2020-04-15 22:08:55 -03:00
Leonel Quinteros
d26d707c73 Create build.yml 2020-04-15 22:03:51 -03:00
Leonel Quinteros
b6df672e9a Merge pull request #37 from bboehmke/xgotext_verbose
verbose flag for xgotext
2020-03-06 10:52:42 -03:00
Benjamin Böhmke
c40a6e9dd5 xgotext: added verbose flag 2020-03-05 19:40:31 +01:00
Leonel Quinteros
be1a13b346 Merge pull request #36 from bboehmke/xgotext_fixes
xgotext fixes
2020-02-29 16:38:15 -03:00
Benjamin Böhmke
36588ae653 xgotext: updated README.md 2020-02-29 11:42:35 +01:00
Benjamin Böhmke
20f50a5bba xgotext: name generated files .pot 2020-02-28 16:56:38 +01:00
Benjamin Böhmke
5b0ba55f37 xgotext: added option to exclude directories 2020-02-27 19:59:03 +01:00
Benjamin Böhmke
df996c3ae1 xgotext: catch unknown variable type 2020-02-26 21:43:19 +01:00
Benjamin Böhmke
f46758caed xgotext: fixed parsing of error types 2020-02-26 21:08:59 +01:00
Leonel Quinteros
4aa838b8c3 Merge pull request #35 from bboehmke/xgotext_rework
rework of xgotext
2020-02-25 11:15:17 -03:00
Benjamin Böhmke
478f4d29b7 added option to set default domain 2020-02-24 17:52:39 +01:00
Benjamin Böhmke
9091772533 fixed support for older go versions 2020-02-24 15:33:30 +01:00
Benjamin Böhmke
3b92b162e1 moved PO file writing to domain object 2020-02-23 10:08:48 +01:00
Benjamin Böhmke
b8c9a57c39 catch unsupported function calls 2020-02-23 10:00:43 +01:00
Benjamin Böhmke
1891633250 updated CLI README.md 2020-02-23 09:51:48 +01:00
Benjamin Böhmke
75a3d22c53 initial rework of xgotext 2020-02-22 22:37:17 +01:00
Leonel Quinteros
2b59b30398 Update README.md 2019-11-13 14:59:02 -03:00
Leonel Quinteros
208807f5ca Create go.yml 2019-11-13 14:46:21 -03:00
Leonel Quinteros
823ca32c7a Fallback Po's missing plural translation using plural forms when available. Use western rule n==1 convention only on Locale object without Domain. Fixes #34 2019-10-21 14:43:48 -03:00
Leonel Quinteros
99a9166ded Remove code coverage tool 2019-07-10 18:23:45 -03:00
Leonel Quinteros
6431cb3aea Parse SetDomain methods 2019-02-18 18:09:32 -03:00
Leonel Quinteros
0e382cfe26 First implementation of CLI tool 2019-02-15 15:20:42 -03:00
Leonel Quinteros
ff3209d159 Remove dep files 2018-12-21 14:53:49 -03:00
Leonel Quinteros
0216b71049 Merge pull request #30 from draven-archive/master
Fix gotext.GetD with unloaded domain
2018-12-17 10:02:03 -03:00
draveness
37bac2fe57 Fix gotext.GetD with unloaded domain 2018-12-07 12:32:39 +08:00
Leonel Quinteros
7b73c0d36b Update README 2018-09-12 15:31:27 -03:00
29 changed files with 1576 additions and 173 deletions

35
.github/workflows/build.yml vendored Normal file
View 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 ./...

View File

@@ -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
View File

@@ -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

View File

@@ -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
View File

@@ -1,13 +1,13 @@
[![GitHub release](https://img.shields.io/github/release/leonelquinteros/gotext.svg)](https://github.com/leonelquinteros/gotext)
[![GitHub release](https://img.shields.io/github/release/leonelquinteros/gotext.svg)](https://git.deineagentur.com/DeineAgenturUG/gotext)
[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![GoDoc](https://godoc.org/github.com/leonelquinteros/gotext?status.svg)](https://godoc.org/github.com/leonelquinteros/gotext)
[![GoDoc](https://godoc.org/git.deineagentur.com/DeineAgenturUG/gotext?status.svg)](https://godoc.org/git.deineagentur.com/DeineAgenturUG/gotext)
![Gotext build](https://git.deineagentur.com/DeineAgenturUG/gotext/workflows/Gotext%20build/badge.svg?branch=master)
[![Build Status](https://travis-ci.org/leonelquinteros/gotext.svg?branch=master)](https://travis-ci.org/leonelquinteros/gotext)
[![codecov](https://codecov.io/gh/leonelquinteros/gotext/branch/master/graph/badge.svg)](https://codecov.io/gh/leonelquinteros/gotext)
[![Go Report Card](https://goreportcard.com/badge/github.com/leonelquinteros/gotext)](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
[![Go Report Card](https://goreportcard.com/badge/git.deineagentur.com/DeineAgenturUG/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
View 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

View 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] ""

View 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 ""

View 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 ""

View 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] ""

View 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")
}

View 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
View 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)
}
}

View 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
}

View 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)
}

View 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
}

View File

@@ -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 مستند إضافي"

View File

@@ -0,0 +1,2 @@
msgid "Alcohol & Tobacco"
msgstr "الكحول والتبغ"

6
go.mod
View File

@@ -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
View 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=

View File

@@ -1,23 +1,23 @@
/*
Package gotext implements GNU gettext utilities.
Package gotext implements GNU gettext utilities.
For quick/simple translations you can use the package level functions directly.
For quick/simple translations you can use the package level functions directly.
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
import (
"fmt"
"git.deineagentur.com/DeineAgenturUG/gotext"
)
func main() {
// Configure package
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
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 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"))
}
// Translate text from a different domain without reconfigure
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
}
*/
package gotext
@@ -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.

View File

@@ -2,7 +2,6 @@ package gotext
import (
"os"
"path"
"path/filepath"
"sync"
"testing"
@@ -82,15 +81,31 @@ 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")
dirname := filepath.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to default domain file
filename := path.Join(dirname, "default.po")
filename := filepath.Join(dirname, "default.po")
f, err := os.Create(filename)
if err != nil {
@@ -102,8 +117,21 @@ msgstr[1] ""
t.Fatalf("Can't write to test file: %s", err.Error())
}
anotherFilename := filepath.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 +170,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) {
@@ -164,14 +197,14 @@ msgstr[1] ""
`
// Create Locales directory on default location
dirname := path.Join("/tmp", "en_US")
dirname := filepath.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to default domain file
filename := path.Join(dirname, "default.po")
filename := filepath.Join(dirname, "default.po")
f, err := os.Create(filename)
if err != nil {
@@ -284,19 +317,19 @@ msgstr[1] "Custom ctx translations"
`
// Create Locales directory and files on temp location
dirname := path.Join("/tmp", "en_US")
dirname := filepath.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
fDefault, err := os.Create(path.Join(dirname, "default.po"))
fDefault, err := os.Create(filepath.Join(dirname, "default.po"))
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
defer fDefault.Close()
fCustom, err := os.Create(path.Join(dirname, "custom.po"))
fCustom, err := os.Create(filepath.Join(dirname, "custom.po"))
if err != nil {
t.Fatalf("Can't create test file: %s", err.Error())
}
@@ -374,14 +407,14 @@ msgstr "Some random Translation in a context"
`
// Create Locales directory on default location
dirname := path.Join("/tmp", "en_US")
dirname := filepath.Join("/tmp", "en_US")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to default domain file
filename := path.Join("/tmp", GetDomain()+".po")
filename := filepath.Join("/tmp", GetDomain()+".po")
f, err := os.Create(filename)
if err != nil {
@@ -403,7 +436,7 @@ msgstr "Some random Translation in a context"
defer wg.Done()
GetLibrary()
SetLibrary(path.Join("/tmp", "gotextlib"))
SetLibrary(filepath.Join("/tmp", "gotextlib"))
GetDomain()
SetDomain("default")
GetLanguage()
@@ -418,3 +451,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)
}
}

183
locale.go
View File

@@ -9,7 +9,7 @@ import (
"bytes"
"encoding/gob"
"os"
"path"
"path/filepath"
"sync"
)
@@ -21,10 +21,10 @@ multiple languages at the same time by working with this object.
Example:
import (
"encoding/gob"
"bytes"
"fmt"
"github.com/leonelquinteros/gotext"
"encoding/gob"
"bytes"
"fmt"
"git.deineagentur.com/DeineAgenturUG/gotext"
)
func main() {
@@ -41,9 +41,8 @@ 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.
@@ -72,26 +71,41 @@ func NewLocale(p, l string) *Locale {
}
}
//SetLang ...
func (l *Locale) SetLang(lang string) {
l.lang = lang
}
//GetLang ...
func (l *Locale) GetLang() string {
return l.lang
}
//SetPath ...
func (l *Locale) SetPath(path string) {
l.path = path
}
func (l *Locale) findExt(dom, ext string) string {
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
filename := filepath.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
if _, err := os.Stat(filename); err == nil {
return filename
}
if len(l.lang) > 2 {
filename = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
filename = filepath.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
if _, err := os.Stat(filename); err == nil {
return filename
}
}
filename = path.Join(l.path, l.lang, dom+"."+ext)
filename = filepath.Join(l.path, l.lang, dom+"."+ext)
if _, err := os.Stat(filename); err == nil {
return filename
}
if len(l.lang) > 2 {
filename = path.Join(l.path, l.lang[:2], dom+"."+ext)
filename = filepath.Join(l.path, l.lang[:2], dom+"."+ext)
if _, err := os.Stat(filename); err == nil {
return filename
}
@@ -152,7 +166,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 +196,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 +226,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 +248,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 +278,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

View File

@@ -7,7 +7,7 @@ package gotext
import (
"os"
"path"
"path/filepath"
"testing"
)
@@ -62,14 +62,14 @@ msgstr "More Translation"
`
// Create Locales directory with simplified language code
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
dirname := filepath.Join("/tmp", "en", "LC_MESSAGES")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to file
filename := path.Join(dirname, "my_domain.po")
filename := filepath.Join(dirname, "my_domain.po")
f, err := os.Create(filename)
if err != nil {
@@ -192,14 +192,14 @@ msgstr "More Translation"
`
// Create Locales directory with simplified language code
dirname := path.Join("/tmp", "en", "LC_MESSAGES")
dirname := filepath.Join("/tmp", "en", "LC_MESSAGES")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to file
filename := path.Join(dirname, "my_domain.po")
filename := filepath.Join(dirname, "my_domain.po")
f, err := os.Create(filename)
if err != nil {
@@ -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)
}
}
@@ -400,14 +425,14 @@ msgstr[2] "And this is the second plural form: %s"
`
// Create Locales directory with simplified language code
dirname := path.Join("/tmp", "es")
dirname := filepath.Join("/tmp", "es")
err := os.MkdirAll(dirname, os.ModePerm)
if err != nil {
t.Fatalf("Can't create test directory: %s", err.Error())
}
// Write PO content to file
filename := path.Join(dirname, "race.po")
filename := filepath.Join(dirname, "race.po")
f, err := os.Create(filename)
if err != nil {
@@ -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
View File

@@ -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
View File

@@ -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...)
}
// 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 (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
}
// 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 (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)

View File

@@ -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)

View File

@@ -50,3 +50,29 @@ func (t *Translation) GetN(n int) string {
// Return untranslated plural by default
return t.PluralID
}
// GetE 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
}
// GetNE 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
}

View File

@@ -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
}