Fix GetN and GetNC to honor package domain. Refactor global package functions to make them all concurrent safe. Fixes #14
This commit is contained in:
@@ -1,9 +1,15 @@
|
|||||||
# CONTRIBUTING
|
# CONTRIBUTING
|
||||||
|
|
||||||
|
This open source project welcomes everybody that wants to contribute to it by implementing new features, fixing bugs, testing, creating documentation or simply talk about it.
|
||||||
|
|
||||||
|
Most contributions will start by creating a new Issue to discuss what is the contribution about and to agree on the steps to move forward.
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
All issues reports are welcome. Open a new Issue whenever you want to report a bug, request a change or make a proposal.
|
All issues reports are welcome. Open a new Issue whenever you want to report a bug, request a change or make a proposal.
|
||||||
|
|
||||||
|
This should be your start point of contribution.
|
||||||
|
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
|
|||||||
108
gotext.go
108
gotext.go
@@ -22,68 +22,110 @@ For quick/simple translations you can use the package level functions directly.
|
|||||||
*/
|
*/
|
||||||
package gotext
|
package gotext
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
// Global environment variables
|
// Global environment variables
|
||||||
var (
|
type config struct {
|
||||||
|
sync.RWMutex
|
||||||
|
|
||||||
// Default domain to look at when no domain is specified. Used by package level functions.
|
// Default domain to look at when no domain is specified. Used by package level functions.
|
||||||
domain = "default"
|
domain string
|
||||||
|
|
||||||
// Language set.
|
// Language set.
|
||||||
language = "en_US"
|
language string
|
||||||
|
|
||||||
// Path to library directory where all locale directories and translation files are.
|
// Path to library directory where all locale directories and translation files are.
|
||||||
library = "/usr/local/share/locale"
|
library string
|
||||||
|
|
||||||
// Storage for package level methods
|
// Storage for package level methods
|
||||||
storage *Locale
|
storage *Locale
|
||||||
)
|
}
|
||||||
|
|
||||||
|
var globalConfig *config
|
||||||
|
|
||||||
|
// Init default configuration
|
||||||
|
func init() {
|
||||||
|
globalConfig = &config{
|
||||||
|
domain: "default",
|
||||||
|
language: "en_US",
|
||||||
|
library: "/usr/local/share/locale",
|
||||||
|
storage: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// loadStorage creates a new Locale object at package level based on the Global variables settings.
|
// 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.
|
// It's called automatically when trying to use Get or GetD methods.
|
||||||
func loadStorage(force bool) {
|
func loadStorage(force bool) {
|
||||||
if storage == nil || force {
|
globalConfig.Lock()
|
||||||
storage = NewLocale(library, language)
|
|
||||||
|
if globalConfig.storage == nil || force {
|
||||||
|
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := storage.domains[domain]; !ok || force {
|
if _, ok := globalConfig.storage.domains[globalConfig.domain]; !ok || force {
|
||||||
storage.AddDomain(domain)
|
globalConfig.storage.AddDomain(globalConfig.domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
globalConfig.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDomain is the domain getter for the package configuration
|
// GetDomain is the domain getter for the package configuration
|
||||||
func GetDomain() string {
|
func GetDomain() string {
|
||||||
return domain
|
globalConfig.RLock()
|
||||||
|
dom := globalConfig.domain
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return dom
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDomain sets the name for the domain to be used at package level.
|
// SetDomain sets the name for the domain to be used at package level.
|
||||||
// It reloads the corresponding translation file.
|
// It reloads the corresponding translation file.
|
||||||
func SetDomain(dom string) {
|
func SetDomain(dom string) {
|
||||||
domain = dom
|
globalConfig.Lock()
|
||||||
|
globalConfig.domain = dom
|
||||||
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
loadStorage(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLanguage is the language getter for the package configuration
|
// GetLanguage is the language getter for the package configuration
|
||||||
func GetLanguage() string {
|
func GetLanguage() string {
|
||||||
return language
|
globalConfig.RLock()
|
||||||
|
lang := globalConfig.language
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return lang
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLanguage sets the language code to be used at package level.
|
// SetLanguage sets the language code to be used at package level.
|
||||||
// It reloads the corresponding translation file.
|
// It reloads the corresponding translation file.
|
||||||
func SetLanguage(lang string) {
|
func SetLanguage(lang string) {
|
||||||
language = lang
|
globalConfig.Lock()
|
||||||
|
globalConfig.language = lang
|
||||||
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
loadStorage(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLibrary is the library getter for the package configuration
|
// GetLibrary is the library getter for the package configuration
|
||||||
func GetLibrary() string {
|
func GetLibrary() string {
|
||||||
return library
|
globalConfig.RLock()
|
||||||
|
lib := globalConfig.library
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return lib
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLibrary sets the root path for the loale directories and files to be used at package level.
|
// SetLibrary sets the root path for the loale directories and files to be used at package level.
|
||||||
// It reloads the corresponding translation file.
|
// It reloads the corresponding translation file.
|
||||||
func SetLibrary(lib string) {
|
func SetLibrary(lib string) {
|
||||||
library = lib
|
globalConfig.Lock()
|
||||||
|
globalConfig.library = lib
|
||||||
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
loadStorage(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +134,13 @@ func SetLibrary(lib string) {
|
|||||||
// This function is recommended to be used when changing more than one setting,
|
// 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.
|
// 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) {
|
func Configure(lib, lang, dom string) {
|
||||||
library = lib
|
globalConfig.Lock()
|
||||||
language = lang
|
|
||||||
domain = dom
|
globalConfig.library = lib
|
||||||
|
globalConfig.language = lang
|
||||||
|
globalConfig.domain = dom
|
||||||
|
|
||||||
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
loadStorage(true)
|
||||||
}
|
}
|
||||||
@@ -102,13 +148,13 @@ func Configure(lib, lang, dom string) {
|
|||||||
// Get uses the default domain globally set to return the corresponding translation of a given string.
|
// Get uses the default domain globally set 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.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func Get(str string, vars ...interface{}) string {
|
func Get(str string, vars ...interface{}) string {
|
||||||
return GetD(domain, str, vars...)
|
return GetD(GetDomain(), str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain.
|
// GetN 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.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
return GetND("default", str, plural, n, vars...)
|
return GetND(GetDomain(), str, plural, n, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetD returns the corresponding translation in the given domain for a given string.
|
// GetD returns the corresponding translation in the given domain for a given string.
|
||||||
@@ -124,19 +170,23 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
|||||||
loadStorage(false)
|
loadStorage(false)
|
||||||
|
|
||||||
// Return translation
|
// Return translation
|
||||||
return storage.GetND(dom, str, plural, n, vars...)
|
globalConfig.RLock()
|
||||||
|
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetC uses the default domain globally set to return the corresponding translation of the given string in the given context.
|
// GetC uses the default domain globally set 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.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetC(str, ctx string, vars ...interface{}) string {
|
func GetC(str, ctx string, vars ...interface{}) string {
|
||||||
return GetDC(domain, str, ctx, vars...)
|
return GetDC(GetDomain(), str, ctx, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the "default" domain.
|
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the default domain.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||||
return GetNDC("default", str, plural, n, ctx, vars...)
|
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
||||||
@@ -152,7 +202,11 @@ func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) str
|
|||||||
loadStorage(false)
|
loadStorage(false)
|
||||||
|
|
||||||
// Return translation
|
// Return translation
|
||||||
return storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
globalConfig.RLock()
|
||||||
|
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||||
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
// printf applies text formatting only when needed to parse variables.
|
// printf applies text formatting only when needed to parse variables.
|
||||||
|
|||||||
140
gotext_test.go
140
gotext_test.go
@@ -82,14 +82,14 @@ msgstr[1] ""
|
|||||||
`
|
`
|
||||||
|
|
||||||
// Create Locales directory on default location
|
// Create Locales directory on default location
|
||||||
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US")
|
dirname := path.Join("/tmp", "en_US")
|
||||||
err := os.MkdirAll(dirname, os.ModePerm)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PO content to default domain file
|
// Write PO content to default domain file
|
||||||
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po")
|
filename := path.Join(dirname, "default.po")
|
||||||
|
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -161,14 +161,14 @@ msgstr[1] ""
|
|||||||
`
|
`
|
||||||
|
|
||||||
// Create Locales directory on default location
|
// Create Locales directory on default location
|
||||||
dirname := path.Clean("/tmp" + string(os.PathSeparator) + "en_US")
|
dirname := path.Join("/tmp", "en_US")
|
||||||
err := os.MkdirAll(dirname, os.ModePerm)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PO content to default domain file
|
// Write PO content to default domain file
|
||||||
filename := path.Clean(dirname + string(os.PathSeparator) + "default.po")
|
filename := path.Join(dirname, "default.po")
|
||||||
|
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -214,6 +214,108 @@ msgstr[1] ""
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDomains(t *testing.T) {
|
||||||
|
// Set PO content
|
||||||
|
strDefault := `
|
||||||
|
msgid ""
|
||||||
|
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgid "Default text"
|
||||||
|
msgid_plural "Default texts"
|
||||||
|
msgstr[0] "Default translation"
|
||||||
|
msgstr[1] "Default translations"
|
||||||
|
|
||||||
|
msgctxt "Ctx"
|
||||||
|
msgid "Default context"
|
||||||
|
msgid_plural "Default contexts"
|
||||||
|
msgstr[0] "Default ctx translation"
|
||||||
|
msgstr[1] "Default ctx translations"
|
||||||
|
`
|
||||||
|
|
||||||
|
strCustom := `
|
||||||
|
msgid ""
|
||||||
|
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
msgid "Custom text"
|
||||||
|
msgid_plural "Custom texts"
|
||||||
|
msgstr[0] "Custom translation"
|
||||||
|
msgstr[1] "Custom translations"
|
||||||
|
|
||||||
|
msgctxt "Ctx"
|
||||||
|
msgid "Custom context"
|
||||||
|
msgid_plural "Custom contexts"
|
||||||
|
msgstr[0] "Custom ctx translation"
|
||||||
|
msgstr[1] "Custom ctx translations"
|
||||||
|
`
|
||||||
|
|
||||||
|
// Create Locales directory and files on temp location
|
||||||
|
dirname := path.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"))
|
||||||
|
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"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't create test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer fCustom.Close()
|
||||||
|
|
||||||
|
_, err = fDefault.WriteString(strDefault)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
_, err = fCustom.WriteString(strCustom)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Can't write to test file: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
Configure("/tmp", "en_US", "default")
|
||||||
|
|
||||||
|
// Check default domain translation
|
||||||
|
SetDomain("default")
|
||||||
|
tr := Get("Default text")
|
||||||
|
if tr != "Default translation" {
|
||||||
|
t.Errorf("Expected 'Default translation'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetN("Default text", "Default texts", 23)
|
||||||
|
if tr != "Default translations" {
|
||||||
|
t.Errorf("Expected 'Default translations'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetC("Default context", "Ctx")
|
||||||
|
if tr != "Default ctx translation" {
|
||||||
|
t.Errorf("Expected 'Default ctx translation'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetNC("Default context", "Default contexts", 23, "Ctx")
|
||||||
|
if tr != "Default ctx translations" {
|
||||||
|
t.Errorf("Expected 'Default ctx translations'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
SetDomain("custom")
|
||||||
|
tr = Get("Custom text")
|
||||||
|
if tr != "Custom translation" {
|
||||||
|
t.Errorf("Expected 'Custom translation'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetN("Custom text", "Custom texts", 23)
|
||||||
|
if tr != "Custom translations" {
|
||||||
|
t.Errorf("Expected 'Custom translations'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetC("Custom context", "Ctx")
|
||||||
|
if tr != "Custom ctx translation" {
|
||||||
|
t.Errorf("Expected 'Custom ctx translation'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
tr = GetNC("Custom context", "Custom contexts", 23, "Ctx")
|
||||||
|
if tr != "Custom ctx translations" {
|
||||||
|
t.Errorf("Expected 'Custom ctx translations'. Got '%s'", tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPackageRace(t *testing.T) {
|
func TestPackageRace(t *testing.T) {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
str := `# Some comment
|
str := `# Some comment
|
||||||
@@ -230,17 +332,21 @@ msgstr[0] "This one is the singular: %s"
|
|||||||
msgstr[1] "This one is the plural: %s"
|
msgstr[1] "This one is the plural: %s"
|
||||||
msgstr[2] "And this is the second plural form: %s"
|
msgstr[2] "And this is the second plural form: %s"
|
||||||
|
|
||||||
|
msgctxt "Ctx"
|
||||||
|
msgid "Some random in a context"
|
||||||
|
msgstr "Some random translation in a context"
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// Create Locales directory on default location
|
// Create Locales directory on default location
|
||||||
dirname := path.Clean(library + string(os.PathSeparator) + "en_US")
|
dirname := path.Join("/tmp", "en_US")
|
||||||
err := os.MkdirAll(dirname, os.ModePerm)
|
err := os.MkdirAll(dirname, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test directory: %s", err.Error())
|
t.Fatalf("Can't create test directory: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write PO content to default domain file
|
// Write PO content to default domain file
|
||||||
filename := path.Clean(dirname + string(os.PathSeparator) + domain + ".po")
|
filename := path.Join("/tmp", GetDomain()+".po")
|
||||||
|
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -255,26 +361,24 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// Test translations
|
// Test translations
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
GetLibrary()
|
||||||
|
SetLibrary(path.Join("/tmp", "gotextlib"))
|
||||||
|
GetDomain()
|
||||||
|
SetDomain("default")
|
||||||
|
GetLanguage()
|
||||||
|
SetLanguage("en_US")
|
||||||
|
Configure("/tmp", "en_US", "default")
|
||||||
|
|
||||||
Get("My text")
|
Get("My text")
|
||||||
GetN("One with var: %s", "Several with vars: %s", 0, "test")
|
GetN("One with var: %s", "Several with vars: %s", 0, "test")
|
||||||
|
GetC("Some random in a context", "Ctx")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
Get("My text")
|
|
||||||
GetN("One with var: %s", "Several with vars: %s", 1, "test")
|
|
||||||
}()
|
|
||||||
|
|
||||||
Get("My text")
|
|
||||||
GetN("One with var: %s", "Several with vars: %s", 2, "test")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|||||||
Reference in New Issue
Block a user