Initial import
This commit is contained in:
commit
9107e9b75a
9 changed files with 595 additions and 0 deletions
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Eclipse shit
|
||||
.project
|
||||
.settings
|
||||
.buildpath
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Leonel Quinteros
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
122
README.md
Normal file
122
README.md
Normal file
|
@ -0,0 +1,122 @@
|
|||
[![GoDoc](https://godoc.org/github.com/leonelquinteros/gotext?status.svg)](https://godoc.org/github.com/leonelquinteros/gotext)
|
||||
|
||||
# Gotext
|
||||
|
||||
GNU gettext utilities for Go
|
||||
|
||||
|
||||
# Examples
|
||||
|
||||
## Using package for single language/domain settings
|
||||
|
||||
For quick/simple translations on a single file, you can use the package level functions directly.
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
```go
|
||||
import "github.com/leonelquinteros/gotext"
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Set variables
|
||||
name := "John"
|
||||
|
||||
// Translate text with variables
|
||||
println(gotext.Get("Hi, my name is %s", name))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Using Locale object
|
||||
|
||||
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 "github.com/leonelquinteros/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
|
||||
println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain
|
||||
l.AddDomain("translations")
|
||||
|
||||
// Translate text from domain
|
||||
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 templace code would look like:
|
||||
|
||||
```
|
||||
{{ .Loc.Get "Translate this" }}
|
||||
```
|
||||
|
||||
|
||||
## Using the Po object to handle .po files and PO-formatted 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 "github.com/leonelquinteros/gotext"
|
||||
|
||||
func main() {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid "Translate this"
|
||||
msgstr "Translated text"
|
||||
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := new(Po)
|
||||
po.Parse(str)
|
||||
|
||||
println(po.Get("Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Contribute
|
||||
|
||||
- Please, contribute.
|
||||
- Use the package on your projects.
|
||||
- Report issues on Github.
|
||||
- Send pull requests for bugfixes and improvements.
|
||||
- Send proposals on Github issues.
|
93
gotext.go
Normal file
93
gotext.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Package gotext implements GNU gettext utilities.
|
||||
*/
|
||||
package gotext
|
||||
|
||||
// Global environment variables
|
||||
var (
|
||||
// Default domain to look at when no domain is specified. Used by package level functions.
|
||||
domain = "default"
|
||||
|
||||
// Language set.
|
||||
language = "en_US"
|
||||
|
||||
// Path to library directory where all locale directories and translation files are.
|
||||
library = "/tmp"
|
||||
|
||||
// Storage for package level methods
|
||||
storage *Locale
|
||||
)
|
||||
|
||||
// loadStorage creates a new Locale object at package level based on the Global variables settings.
|
||||
// It's called automatically when trying to use Get or GetD methods.
|
||||
func loadStorage(force bool) {
|
||||
if storage == nil || force {
|
||||
storage = NewLocale(library, language)
|
||||
}
|
||||
|
||||
if _, ok := storage.domains[domain]; !ok || force {
|
||||
storage.AddDomain(domain)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func GetDomain() string {
|
||||
return domain
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
func SetDomain(dom string) {
|
||||
domain = dom
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// GetLanguage is the language getter for the package configuration
|
||||
func GetLanguage() string {
|
||||
return language
|
||||
}
|
||||
|
||||
// SetLanguage sets the language code to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
func SetLanguage(lang string) {
|
||||
language = lang
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// GetLibrary is the library getter for the package configuration
|
||||
func GetLibrary() string {
|
||||
return library
|
||||
}
|
||||
|
||||
// SetLibrary sets the root path for the loale directories and files to be used at package level.
|
||||
// It reloads the corresponding translation file.
|
||||
func SetLibrary(lib string) {
|
||||
library = lib
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// Configure sets all configuration variables to be used at package level and reloads the corresponding translation file.
|
||||
// It receives the library path, language code and domain name.
|
||||
// This function is recommended to be used when changing more than one setting,
|
||||
// as using each setter will introduce a I/O overhead because the translation file will be loaded after each set.
|
||||
func Configure(lib, lang, dom string) {
|
||||
library = lib
|
||||
language = lang
|
||||
domain = dom
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// Get uses the default domain globally set to return the corresponding translation of a given string.
|
||||
func Get(str string, vars ...interface{}) string {
|
||||
return GetD(domain, str, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for a given string.
|
||||
func GetD(dom, str string, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
return storage.GetD(dom, str, vars...)
|
||||
}
|
56
gotext_test.go
Normal file
56
gotext_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPackageFunctions(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory on default location
|
||||
dirname := path.Clean(library + string(os.PathSeparator) + "en_US")
|
||||
err := os.MkdirAll(dirname, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||
}
|
||||
|
||||
// Write PO content to default domain file
|
||||
filename := path.Clean(dirname + string(os.PathSeparator) + domain + ".po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(str)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Test translations
|
||||
tr := Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
tr = Get("One with var: %s", v)
|
||||
if tr != "This one sets the var: Variable" {
|
||||
t.Errorf("Expected 'This one sets the var: Variable' but got '%s'", tr)
|
||||
}
|
||||
}
|
72
locale.go
Normal file
72
locale.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type Locale struct {
|
||||
// Path to locale files.
|
||||
path string
|
||||
|
||||
// Language for this Locale
|
||||
lang string
|
||||
|
||||
// List of available domains for this locale.
|
||||
domains map[string]*Po
|
||||
}
|
||||
|
||||
// NewLocale creates and initializes a new Locale object for a given language.
|
||||
// It receives a path for the i18n files directory (p) and a language code to use (l).
|
||||
func NewLocale(p, l string) *Locale {
|
||||
return &Locale{
|
||||
path: p,
|
||||
lang: l,
|
||||
domains: make(map[string]*Po),
|
||||
}
|
||||
}
|
||||
|
||||
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||
// If the domain exists, it gets reloaded.
|
||||
func (l *Locale) AddDomain(dom string) {
|
||||
po := new(Po)
|
||||
|
||||
// Check for file.
|
||||
filename := path.Clean(l.path + string(os.PathSeparator) + l.lang + string(os.PathSeparator) + dom + ".po")
|
||||
|
||||
// Try to use the generic language dir if the provided isn't available
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Clean(l.path + string(os.PathSeparator) + l.lang[:2] + string(os.PathSeparator) + dom + ".po")
|
||||
}
|
||||
}
|
||||
|
||||
// Parse file.
|
||||
po.ParseFile(filename)
|
||||
|
||||
// Save new domain
|
||||
if l.domains == nil {
|
||||
l.domains = make(map[string]*Po)
|
||||
}
|
||||
l.domains[dom] = po
|
||||
}
|
||||
|
||||
// Get uses a domain "default" to return the corresponding translation of a given string.
|
||||
func (l *Locale) Get(str string, vars ...interface{}) string {
|
||||
return l.GetD("default", str, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for a given string.
|
||||
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||
if l.domains != nil {
|
||||
if _, ok := l.domains[dom]; ok {
|
||||
if l.domains[dom] != nil {
|
||||
return l.domains[dom].Get(str, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
62
locale_test.go
Normal file
62
locale_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocale(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
|
||||
`
|
||||
|
||||
// Create Locales directory with simplified language code
|
||||
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en")
|
||||
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.Clean(dirname + string(os.PathSeparator) + "my_domain.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(str)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
|
||||
// Test translations
|
||||
tr := l.GetD("my_domain", "My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
tr = l.GetD("my_domain", "One with var: %s", v)
|
||||
if tr != "This one sets the var: Variable" {
|
||||
t.Errorf("Expected 'This one sets the var: Variable' but got '%s'", tr)
|
||||
}
|
||||
}
|
88
po.go
Normal file
88
po.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Po struct {
|
||||
translations map[string]string
|
||||
}
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (po *Po) ParseFile(f string) {
|
||||
// Check if file exists
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check that isn't a directory
|
||||
if info.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse file content
|
||||
data, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
po.Parse(string(data))
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (po *Po) Parse(str string) {
|
||||
if po.translations == nil {
|
||||
po.translations = make(map[string]string)
|
||||
}
|
||||
|
||||
lines := strings.Split(str, "\n")
|
||||
|
||||
var msgid, msgstr string
|
||||
|
||||
for _, l := range lines {
|
||||
// Trim spaces
|
||||
l = strings.TrimSpace(l)
|
||||
|
||||
// Skip empty lines
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip invalid lines
|
||||
if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgstr") {
|
||||
continue
|
||||
}
|
||||
|
||||
// Buffer msgid and continue
|
||||
if strings.HasPrefix(l, "msgid") {
|
||||
msgid = strings.TrimSpace(strings.TrimPrefix(l, "msgid"))
|
||||
msgid, _ = strconv.Unquote(msgid)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Save translation for buffered msgid
|
||||
if strings.HasPrefix(l, "msgstr") {
|
||||
msgstr = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
msgstr, _ = strconv.Unquote(msgstr)
|
||||
|
||||
po.translations[msgid] = msgstr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return fmt.Sprintf(po.translations[str], vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
52
po_test.go
Normal file
52
po_test.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPo(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
|
||||
`
|
||||
// Write PO content to file
|
||||
filename := path.Clean(os.TempDir() + string(os.PathSeparator) + "default.po")
|
||||
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't create test file: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(str)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||
}
|
||||
|
||||
// Parse po file
|
||||
po := new(Po)
|
||||
po.ParseFile(filename)
|
||||
|
||||
// Test translations
|
||||
tr := po.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
tr = po.Get("One with var: %s", v)
|
||||
if tr != "This one sets the var: Variable" {
|
||||
t.Errorf("Expected 'This one sets the var: Variable' but got '%s'", tr)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue