diff --git a/gotext_test.go b/gotext_test.go index 35dcfc7..d67df36 100644 --- a/gotext_test.go +++ b/gotext_test.go @@ -63,3 +63,60 @@ msgstr[2] "And this is the second plural form: %s" t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr) } } + +func TestPackageRace(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" +msgid_plural "Several with vars: %s" +msgstr[0] "This one is the singular: %s" +msgstr[1] "This one is the plural: %s" +msgstr[2] "And this is the second plural form: %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()) + } + + // Init sync channels + c1 := make(chan bool) + c2 := make(chan bool) + + // Test translations + go func(done chan bool) { + println(Get("My text")) + done <- true + }(c1) + + go func(done chan bool) { + println(Get("My text")) + done <- true + }(c2) + + println(Get("My text")) +} diff --git a/locale.go b/locale.go index 26a1a17..f3f9fb5 100644 --- a/locale.go +++ b/locale.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path" + "sync" ) /* @@ -42,6 +43,9 @@ type Locale struct { // List of available domains for this locale. domains map[string]*Po + + // Sync Mutex + sync.RWMutex } // NewLocale creates and initializes a new Locale object for a given language. @@ -73,6 +77,9 @@ func (l *Locale) AddDomain(dom string) { po.ParseFile(filename) // Save new domain + l.Lock() + defer l.Unlock() + if l.domains == nil { l.domains = make(map[string]*Po) } @@ -102,6 +109,10 @@ func (l *Locale) GetD(dom, str string, vars ...interface{}) string { // If n == 0, usually the singular form of the string is returned as defined in the PO file. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string { + // Sync read + l.RLock() + defer l.RUnlock() + if l.domains != nil { if _, ok := l.domains[dom]; ok { if l.domains[dom] != nil { diff --git a/locale_test.go b/locale_test.go index 0aebb1f..53a0897 100644 --- a/locale_test.go +++ b/locale_test.go @@ -69,3 +69,69 @@ msgstr[2] "And this is the second plural form: %s" t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr) } } + +func TestLocaleRace(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" +msgid_plural "Several with vars: %s" +msgstr[0] "This one is the singular: %s" +msgstr[1] "This one is the plural: %s" +msgstr[2] "And this is the second plural form: %s" + + ` + + // Create Locales directory with simplified language code + dirname := path.Clean("/tmp" + string(os.PathSeparator) + "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.Clean(dirname + string(os.PathSeparator) + "race.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", "es") + + // Init sync channels + ac := make(chan bool) + rc := make(chan bool) + + // Add domain in goroutine + go func(l *Locale, done chan bool) { + l.AddDomain("race") + done <- true + }(l, ac) + + // Get translations in goroutine + go func(l *Locale, done chan bool) { + println(l.GetD("race", "My text")) + done <- true + }(l, rc) + + // Get translations at top level + println(l.GetD("race", "My text")) + + // Wait for goroutines to finish + <-ac + <-rc +} diff --git a/po.go b/po.go index 4a26008..2f649f9 100644 --- a/po.go +++ b/po.go @@ -186,6 +186,10 @@ func (po *Po) Parse(str string) { // Get 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. func (po *Po) Get(str string, vars ...interface{}) string { + // Sync read + po.RLock() + defer po.RUnlock() + if po.translations != nil { if _, ok := po.translations[str]; ok { return fmt.Sprintf(po.translations[str].get(), vars...) @@ -200,6 +204,10 @@ func (po *Po) Get(str string, vars ...interface{}) string { // If n == 0, usually the singular form of the string is returned as defined in the PO file. // Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string { + // Sync read + po.RLock() + defer po.RUnlock() + if po.translations != nil { if _, ok := po.translations[str]; ok { return fmt.Sprintf(po.translations[str].getN(n), vars...) diff --git a/po_test.go b/po_test.go index b92b4d8..02ab8c4 100644 --- a/po_test.go +++ b/po_test.go @@ -59,3 +59,48 @@ msgstr[2] "And this is the second plural form: %s" t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr) } } + +func TestPoRace(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" +msgid_plural "Several with vars: %s" +msgstr[0] "This one is the singular: %s" +msgstr[1] "This one is the plural: %s" +msgstr[2] "And this is the second plural form: %s" + + ` + + // Create Po object + po := new(Po) + + // Create sync channels + pc := make(chan bool) + rc := make(chan bool) + + // Parse po content in a goroutine + go func(po *Po, done chan bool) { + po.Parse(str) + done <- true + }(po, pc) + + // Read some translation on a goroutine + go func(po *Po, done chan bool) { + println(po.Get("My text")) + done <- true + }(po, rc) + + // Read something at top level + println(po.Get("My text")) + + // Wait for goroutines to finish + <-pc + <-rc +}