Merge pull request #19 from DeineAgenturUG/leonelquinteros-master
Create MO parser
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@
|
||||
.settings
|
||||
.buildpath
|
||||
|
||||
# golang jetbrains shit
|
||||
.idea
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
|
||||
BIN
fixtures/de/default.mo
Normal file
BIN
fixtures/de/default.mo
Normal file
Binary file not shown.
77
fixtures/de/default.po
Normal file
77
fixtures/de/default.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: de\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "de"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"plural\n"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
"Multi \n"
|
||||
"line"
|
||||
|
||||
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"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
BIN
fixtures/de_DE/LC_MESSAGES/default.mo
Normal file
BIN
fixtures/de_DE/LC_MESSAGES/default.mo
Normal file
Binary file not shown.
77
fixtures/de_DE/LC_MESSAGES/default.po
Normal file
77
fixtures/de_DE/LC_MESSAGES/default.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: de_DE\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "de_DE"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"plural\n"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
"Multi \n"
|
||||
"line"
|
||||
|
||||
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"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
68
fixtures/en_AU/default.po
Normal file
68
fixtures/en_AU/default.po
Normal file
@@ -0,0 +1,68 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en_US\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "en_AU"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multilineid"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multilinepluralid"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi line"
|
||||
|
||||
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"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
BIN
fixtures/en_GB/default.mo
Normal file
BIN
fixtures/en_GB/default.mo
Normal file
Binary file not shown.
BIN
fixtures/en_US/default.mo
Normal file
BIN
fixtures/en_US/default.mo
Normal file
Binary file not shown.
68
fixtures/en_US/default.po
Normal file
68
fixtures/en_US/default.po
Normal file
@@ -0,0 +1,68 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: en_US\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "en_US"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multilineid"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multilinepluralid"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi line"
|
||||
|
||||
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"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
BIN
fixtures/fr/LC_MESSAGES/default.mo
Normal file
BIN
fixtures/fr/LC_MESSAGES/default.mo
Normal file
Binary file not shown.
77
fixtures/fr/LC_MESSAGES/default.po
Normal file
77
fixtures/fr/LC_MESSAGES/default.po
Normal file
@@ -0,0 +1,77 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: Josef Fröhle <froehle@b1-systems.de>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
"X-Poedit-SourceCharset: UTF-8\n"
|
||||
|
||||
# Initial comment
|
||||
# Headers below
|
||||
msgid "language"
|
||||
msgstr "fr"
|
||||
|
||||
# Some comment
|
||||
msgid "My text"
|
||||
msgstr "Translated text"
|
||||
|
||||
# More comments
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid ""
|
||||
"multi\n"
|
||||
"line\n"
|
||||
"plural\n"
|
||||
"id"
|
||||
msgstr "plural id with multiline content"
|
||||
|
||||
# Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr ""
|
||||
"Multi \n"
|
||||
"line"
|
||||
|
||||
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"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
msgid_plural "Empty plural form"
|
||||
msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
60
gotext.go
60
gotext.go
@@ -23,7 +23,6 @@ For quick/simple translations you can use the package level functions directly.
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -37,7 +36,7 @@ type config struct {
|
||||
// Language set.
|
||||
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 string
|
||||
|
||||
// Storage for package level methods
|
||||
@@ -65,7 +64,7 @@ func loadStorage(force bool) {
|
||||
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
|
||||
}
|
||||
|
||||
if _, ok := globalConfig.storage.domains[globalConfig.domain]; !ok || force {
|
||||
if _, ok := globalConfig.storage.Domains[globalConfig.domain]; !ok || force {
|
||||
globalConfig.storage.AddDomain(globalConfig.domain)
|
||||
}
|
||||
|
||||
@@ -74,18 +73,27 @@ func loadStorage(force bool) {
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func GetDomain() string {
|
||||
var dom string
|
||||
globalConfig.RLock()
|
||||
dom := globalConfig.domain
|
||||
if globalConfig.storage != nil {
|
||||
dom = globalConfig.storage.GetDomain()
|
||||
}
|
||||
if dom == "" {
|
||||
dom = globalConfig.domain
|
||||
}
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return dom
|
||||
}
|
||||
|
||||
// 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) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.domain = dom
|
||||
if globalConfig.storage != nil {
|
||||
globalConfig.storage.SetDomain(dom)
|
||||
}
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
@@ -101,10 +109,10 @@ func GetLanguage() string {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.language = lang
|
||||
globalConfig.language = SimplifiedLocale(lang)
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
@@ -120,7 +128,7 @@ func GetLibrary() string {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.library = lib
|
||||
@@ -129,47 +137,48 @@ func SetLibrary(lib string) {
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// Configure sets all configuration variables to be used at package level and reloads the corresponding translation file.
|
||||
// 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.
|
||||
// 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) {
|
||||
globalConfig.Lock()
|
||||
|
||||
globalConfig.library = lib
|
||||
globalConfig.language = lang
|
||||
globalConfig.language = SimplifiedLocale(lang)
|
||||
globalConfig.domain = dom
|
||||
globalConfig.storage.SetDomain(dom)
|
||||
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadStorage(true)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Get(str string, vars ...interface{}) string {
|
||||
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.
|
||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
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.
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of translation in the given domain for a given string.
|
||||
// GetND retrieves the (N)th plural form of 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 GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||
globalConfig.RUnlock()
|
||||
@@ -177,43 +186,34 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
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.
|
||||
func GetC(str, ctx string, vars ...interface{}) string {
|
||||
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.
|
||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
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.
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of translation in the given domain for a given string.
|
||||
// GetNDC retrieves the (N)th plural form of 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 GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Try to load default package Locale storage
|
||||
loadStorage(false)
|
||||
|
||||
// Return translation
|
||||
// Return Translation
|
||||
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.
|
||||
func printf(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gotext
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
@@ -65,14 +66,14 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
msgid "Untranslated"
|
||||
msgid_plural "Several untranslated"
|
||||
@@ -95,13 +96,15 @@ msgstr[1] ""
|
||||
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())
|
||||
}
|
||||
|
||||
// Move file close to write the file, so we can use it in the next step
|
||||
f.Close()
|
||||
|
||||
// Set package configuration
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
@@ -125,8 +128,8 @@ msgstr[1] ""
|
||||
|
||||
// Test context translations
|
||||
tr = GetC("Some random in a context", "Ctx")
|
||||
if tr != "Some random translation in a context" {
|
||||
t.Errorf("Expected 'Some random translation in a context' but got '%s'", tr)
|
||||
if tr != "Some random Translation in a context" {
|
||||
t.Errorf("Expected 'Some random Translation in a context' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v = "Variable"
|
||||
@@ -214,6 +217,38 @@ msgstr[1] ""
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoAndPoTranslator(t *testing.T) {
|
||||
|
||||
fixPath, _ := filepath.Abs("./fixtures/")
|
||||
|
||||
Configure(fixPath, "en_GB", "default")
|
||||
|
||||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr := Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
|
||||
}
|
||||
tr = Get("language")
|
||||
if tr != "en_GB" {
|
||||
t.Errorf("Expected 'en_GB'. Got '%s'", tr)
|
||||
}
|
||||
|
||||
// Change Language (locale)
|
||||
SetLanguage("en_AU")
|
||||
|
||||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr = Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text'. Got '%s'", tr)
|
||||
}
|
||||
tr = Get("language")
|
||||
if tr != "en_AU" {
|
||||
t.Errorf("Expected 'en_AU'. Got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomains(t *testing.T) {
|
||||
// Set PO content
|
||||
strDefault := `
|
||||
@@ -222,13 +257,13 @@ msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Default text"
|
||||
msgid_plural "Default texts"
|
||||
msgstr[0] "Default translation"
|
||||
msgstr[0] "Default Translation"
|
||||
msgstr[1] "Default translations"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Default context"
|
||||
msgid_plural "Default contexts"
|
||||
msgstr[0] "Default ctx translation"
|
||||
msgstr[0] "Default ctx Translation"
|
||||
msgstr[1] "Default ctx translations"
|
||||
`
|
||||
|
||||
@@ -238,13 +273,13 @@ msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Custom text"
|
||||
msgid_plural "Custom texts"
|
||||
msgstr[0] "Custom translation"
|
||||
msgstr[0] "Custom Translation"
|
||||
msgstr[1] "Custom translations"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Custom context"
|
||||
msgid_plural "Custom contexts"
|
||||
msgstr[0] "Custom ctx translation"
|
||||
msgstr[0] "Custom ctx Translation"
|
||||
msgstr[1] "Custom ctx translations"
|
||||
`
|
||||
|
||||
@@ -278,19 +313,19 @@ msgstr[1] "Custom ctx translations"
|
||||
|
||||
Configure("/tmp", "en_US", "default")
|
||||
|
||||
// Check default domain translation
|
||||
// Check default domain Translation
|
||||
SetDomain("default")
|
||||
tr := Get("Default text")
|
||||
if tr != "Default translation" {
|
||||
t.Errorf("Expected 'Default translation'. Got '%s'", tr)
|
||||
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)
|
||||
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" {
|
||||
@@ -299,16 +334,16 @@ msgstr[1] "Custom ctx translations"
|
||||
|
||||
SetDomain("custom")
|
||||
tr = Get("Custom text")
|
||||
if tr != "Custom translation" {
|
||||
t.Errorf("Expected 'Custom translation'. Got '%s'", tr)
|
||||
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)
|
||||
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" {
|
||||
@@ -334,7 +369,7 @@ 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"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
`
|
||||
|
||||
|
||||
86
helper.go
Normal file
86
helper.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile(`%\(([a-zA-Z0-9_]+)\)[.0-9]*[svTtbcdoqXxUeEfFgGp]`)
|
||||
|
||||
// SimplifiedLocale simplified locale like " en_US"/"de_DE "/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/... to en_US, de_DE, zh_CN, el_GR...
|
||||
func SimplifiedLocale(lang string) string {
|
||||
// en_US/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/...
|
||||
if idx := strings.Index(lang, ":"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "@"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "."); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
return strings.TrimSpace(lang)
|
||||
}
|
||||
|
||||
// Printf applies text formatting only when needed to parse variables.
|
||||
func Printf(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// NPrintf support named format
|
||||
// NPrintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func NPrintf(format string, params map[string]interface{}) {
|
||||
f, p := parseSprintf(format, params)
|
||||
fmt.Printf(f, p...)
|
||||
}
|
||||
|
||||
// Sprintf support named format
|
||||
// Sprintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func Sprintf(format string, params map[string]interface{}) string {
|
||||
f, p := parseSprintf(format, params)
|
||||
return fmt.Sprintf(f, p...)
|
||||
}
|
||||
|
||||
func parseSprintf(format string, params map[string]interface{}) (string, []interface{}) {
|
||||
f, n := reformatSprintf(format)
|
||||
var p []interface{}
|
||||
for _, v := range n {
|
||||
p = append(p, params[v])
|
||||
}
|
||||
return f, p
|
||||
}
|
||||
|
||||
func reformatSprintf(f string) (string, []string) {
|
||||
m := re.FindAllStringSubmatch(f, -1)
|
||||
i := re.FindAllStringSubmatchIndex(f, -1)
|
||||
|
||||
ord := []string{}
|
||||
for _, v := range m {
|
||||
ord = append(ord, v[1])
|
||||
}
|
||||
|
||||
pair := []int{0}
|
||||
for _, v := range i {
|
||||
pair = append(pair, v[2]-1)
|
||||
pair = append(pair, v[3]+1)
|
||||
}
|
||||
pair = append(pair, len(f))
|
||||
plen := len(pair)
|
||||
|
||||
out := ""
|
||||
for n := 0; n < plen; n += 2 {
|
||||
out += f[pair[n]:pair[n+1]]
|
||||
}
|
||||
|
||||
return out, ord
|
||||
}
|
||||
112
helper_test.go
Normal file
112
helper_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimplifiedLocale(t *testing.T) {
|
||||
tr :=SimplifiedLocale("de_DE@euro")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr =SimplifiedLocale("de_DE.UTF-8")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr =SimplifiedLocale("de_DE:latin1")
|
||||
if tr != "de_DE" {
|
||||
t.Errorf("Expected 'de_DE' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReformattingSingleNamedPattern(t *testing.T) {
|
||||
pat := "%(name_me)x"
|
||||
|
||||
f, n := reformatSprintf(pat)
|
||||
|
||||
if f != "%x" {
|
||||
t.Errorf("pattern should be %%x but %v", f)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(n, []string{"name_me"}) {
|
||||
t.Errorf("named var should be {name_me} but %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReformattingMultipleNamedPattern(t *testing.T) {
|
||||
pat := "%(name_me)x and %(another_name)v"
|
||||
|
||||
f, n := reformatSprintf(pat)
|
||||
|
||||
if f != "%x and %v" {
|
||||
t.Errorf("pattern should be %%x and %%v but %v", f)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(n, []string{"name_me", "another_name"}) {
|
||||
t.Errorf("named var should be {name_me, another_name} but %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReformattingRepeatedNamedPattern(t *testing.T) {
|
||||
pat := "%(name_me)x and %(another_name)v and %(name_me)v"
|
||||
|
||||
f, n := reformatSprintf(pat)
|
||||
|
||||
if f != "%x and %v and %v" {
|
||||
t.Errorf("pattern should be %%x and %%v and %%v but %v", f)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(n, []string{"name_me", "another_name", "name_me"}) {
|
||||
t.Errorf("named var should be {name_me, another_name, name_me} but %v", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSprintf(t *testing.T) {
|
||||
pat := "%(brother)s loves %(sister)s. %(sister)s also loves %(brother)s."
|
||||
params := map[string]interface{}{
|
||||
"sister": "Susan",
|
||||
"brother": "Louis",
|
||||
}
|
||||
|
||||
s := Sprintf(pat, params)
|
||||
|
||||
if s != "Louis loves Susan. Susan also loves Louis." {
|
||||
t.Errorf("result should be Louis loves Susan. Susan also love Louis. but %v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNPrintf(t *testing.T) {
|
||||
pat := "%(brother)s loves %(sister)s. %(sister)s also loves %(brother)s.\n"
|
||||
params := map[string]interface{}{
|
||||
"sister": "Susan",
|
||||
"brother": "Louis",
|
||||
}
|
||||
|
||||
NPrintf(pat, params)
|
||||
|
||||
}
|
||||
|
||||
func TestSprintfFloatsWithPrecision(t *testing.T) {
|
||||
pat := "%(float)f / %(floatprecision).1f / %(long)g / %(longprecision).3g"
|
||||
params := map[string]interface{}{
|
||||
"float": 5.034560,
|
||||
"floatprecision": 5.03456,
|
||||
"long": 5.03456,
|
||||
"longprecision": 5.03456,
|
||||
}
|
||||
|
||||
s := Sprintf(pat, params)
|
||||
|
||||
expectedresult := "5.034560 / 5.0 / 5.03456 / 5.03"
|
||||
if s != expectedresult {
|
||||
t.Errorf("result should be (%v) but is (%v)", expectedresult, s)
|
||||
}
|
||||
}
|
||||
126
locale.go
126
locale.go
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
@@ -22,13 +27,13 @@ Example:
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
|
||||
|
||||
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.po'
|
||||
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.{po,mo}'
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.po')
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.{po,mo}')
|
||||
l.AddDomain("extras")
|
||||
|
||||
// Translate text from domain
|
||||
@@ -43,8 +48,11 @@ type Locale struct {
|
||||
// Language for this Locale
|
||||
lang string
|
||||
|
||||
// List of available domains for this locale.
|
||||
domains map[string]*Po
|
||||
// List of available Domains for this locale.
|
||||
Domains map[string]Translator
|
||||
|
||||
// First AddDomain is default Domain
|
||||
defaultDomain string
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
@@ -55,124 +63,162 @@ type Locale struct {
|
||||
func NewLocale(p, l string) *Locale {
|
||||
return &Locale{
|
||||
path: p,
|
||||
lang: l,
|
||||
domains: make(map[string]*Po),
|
||||
lang: SimplifiedLocale(l),
|
||||
Domains: make(map[string]Translator),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Locale) findPO(dom string) string {
|
||||
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+".po")
|
||||
func (l *Locale) findExt(dom, ext string) string {
|
||||
filename := path.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+".po")
|
||||
filename = path.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+".po")
|
||||
filename = path.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+".po")
|
||||
filename = path.Join(l.path, l.lang[:2], dom+"."+ext)
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
return filename
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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)
|
||||
var poObj Translator
|
||||
|
||||
file := l.findExt(dom, "po")
|
||||
if file != "" {
|
||||
poObj = new(Po)
|
||||
// Parse file.
|
||||
po.ParseFile(l.findPO(dom))
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
file = l.findExt(dom, "mo")
|
||||
if file != "" {
|
||||
poObj = new(Mo)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
// fallback return if no file found with
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Save new domain
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if l.domains == nil {
|
||||
l.domains = make(map[string]*Po)
|
||||
if l.Domains == nil {
|
||||
l.Domains = make(map[string]Translator)
|
||||
}
|
||||
l.domains[dom] = po
|
||||
if l.defaultDomain == "" {
|
||||
l.defaultDomain = dom
|
||||
}
|
||||
l.Domains[dom] = poObj
|
||||
|
||||
// Unlock "Save new domain"
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// Get uses a domain "default" to return the corresponding translation of a given string.
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func (l *Locale) GetDomain() string {
|
||||
l.RLock()
|
||||
dom := l.defaultDomain
|
||||
l.RUnlock()
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used at package level.
|
||||
// It reloads the corresponding Translation file.
|
||||
func (l *Locale) SetDomain(dom string) {
|
||||
l.Lock()
|
||||
l.defaultDomain = dom
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// Get 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.
|
||||
func (l *Locale) Get(str string, vars ...interface{}) string {
|
||||
return l.GetD(GetDomain(), str, vars...)
|
||||
return l.GetD(l.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.
|
||||
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return l.GetND(GetDomain(), str, plural, n, vars...)
|
||||
return l.GetND(l.GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding translation in the given domain for the given 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...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of translation in the given domain for the given string.
|
||||
// GetND 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.
|
||||
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 {
|
||||
return l.domains[dom].GetN(str, plural, n, vars...)
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetN(str, plural, n, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(plural, vars...)
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC uses a domain "default" to return the corresponding translation of the given string in the given context.
|
||||
// GetC 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.
|
||||
func (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return l.GetDC(GetDomain(), str, ctx, vars...)
|
||||
return l.GetDC(l.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.
|
||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||
return l.GetNDC(l.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.
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of translation in the given domain for the given string in the given context.
|
||||
// GetNDC 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.
|
||||
func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, 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 {
|
||||
return l.domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(plural, vars...)
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
169
locale_test.go
169
locale_test.go
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
@@ -45,14 +50,14 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
`
|
||||
|
||||
@@ -81,14 +86,11 @@ msgstr "More translation"
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Force nil domain storage
|
||||
l.domains = nil
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
|
||||
// Set global domain
|
||||
SetDomain("my_domain")
|
||||
|
||||
// Test translations
|
||||
tr := l.GetD("my_domain", "My text")
|
||||
if tr != "Translated text" {
|
||||
@@ -109,8 +111,8 @@ msgstr "More translation"
|
||||
|
||||
// Test context translations
|
||||
tr = l.GetC("Some random in a context", "Ctx")
|
||||
if tr != "Some random translation in a context" {
|
||||
t.Errorf("Expected 'Some random translation in a context'. Got '%s'", tr)
|
||||
if tr != "Some random Translation in a context" {
|
||||
t.Errorf("Expected 'Some random Translation in a context'. Got '%s'", tr)
|
||||
}
|
||||
|
||||
v = "Test"
|
||||
@@ -130,10 +132,10 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last translation
|
||||
// Test last Translation
|
||||
tr = l.GetD("my_domain", "More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
if tr != "More Translation" {
|
||||
t.Errorf("Expected 'More Translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,14 +180,14 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
`
|
||||
|
||||
@@ -214,16 +216,28 @@ msgstr "More translation"
|
||||
l := NewLocale("/tmp", "en_US")
|
||||
|
||||
// Force nil domain storage
|
||||
l.domains = nil
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.AddDomain("my_domain")
|
||||
|
||||
// Set default domain to make it fail
|
||||
SetDomain("default")
|
||||
// Test non-existent "default" domain responses
|
||||
tr := l.GetDomain()
|
||||
if tr != "my_domain" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test non-existent "deafult" domain responses
|
||||
tr := l.Get("My text")
|
||||
// Set default domain to make it fail
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'default' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.Get("My text")
|
||||
if tr != "My text" {
|
||||
t.Errorf("Expected 'My text' but got '%s'", tr)
|
||||
}
|
||||
@@ -255,6 +269,121 @@ msgstr "More translation"
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("/tmp", "golem")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("my_domain")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "my_domain" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
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", 1)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("fixtures/", "fr_FR")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
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", 1)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("fixtures/", "de_DE")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
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", 1)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
}
|
||||
|
||||
|
||||
// Create Locale with full language code
|
||||
l = NewLocale("fixtures/", "de_AT")
|
||||
|
||||
// Force nil domain storage
|
||||
l.Domains = nil
|
||||
|
||||
// Add domain
|
||||
l.SetDomain("default")
|
||||
|
||||
// Test non-existent "default" domain responses
|
||||
tr = l.GetDomain()
|
||||
if tr != "default" {
|
||||
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.Get("This one has invalid syntax translations")
|
||||
if tr != "This one has invalid syntax translations" {
|
||||
t.Errorf("Expected 'This one has invalid syntax translations' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test syntax error parsed translations
|
||||
tr = l.GetNDC("mega", "This one has invalid syntax translations","plural",2,"ctx")
|
||||
if tr != "plural" {
|
||||
t.Errorf("Expected 'plural' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = l.GetN("This one has invalid syntax translations", "This are tests", 1)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocaleRace(t *testing.T) {
|
||||
|
||||
423
mo.go
Normal file
423
mo.go
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
const (
|
||||
MoMagicLittleEndian = 0x950412de // MoMagicLittleEndian encoding
|
||||
MoMagicBigEndian = 0xde120495 // MoMagicBigEndian encoding
|
||||
|
||||
EotSeparator = "\x04" // msgctxt and msgid separator
|
||||
NulSeparator = "\x00" // msgid and msgstr separator
|
||||
)
|
||||
|
||||
/*
|
||||
Mo parses the content of any MO file and provides all the Translation functions needed.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create po object
|
||||
po := gotext.NewMoTranslator()
|
||||
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.mo")
|
||||
|
||||
// Get Translation
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
|
||||
*/
|
||||
type Mo struct {
|
||||
// Headers storage
|
||||
Headers textproto.MIMEHeader
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
}
|
||||
|
||||
// NewMoTranslator creates a new Mo object with the Translator interface
|
||||
func NewMoTranslator() Translator {
|
||||
return new(Mo)
|
||||
}
|
||||
|
||||
// ParseFile tries to read the file by its provided path (f) and parse its content as a .po file.
|
||||
func (mo *Mo) 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
|
||||
}
|
||||
|
||||
mo.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (mo *Mo) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
mo.Lock()
|
||||
|
||||
// Init storage
|
||||
if mo.translations == nil {
|
||||
mo.translations = make(map[string]*Translation)
|
||||
mo.contexts = make(map[string]map[string]*Translation)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
var magicNumber uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
var bo binary.ByteOrder
|
||||
switch magicNumber {
|
||||
case MoMagicLittleEndian:
|
||||
bo = binary.LittleEndian
|
||||
case MoMagicBigEndian:
|
||||
bo = binary.BigEndian
|
||||
default:
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid magic number")
|
||||
}
|
||||
|
||||
var header struct {
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
MsgIDCount uint32
|
||||
MsgIDOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
}
|
||||
if err := binary.Read(r, bo, &header); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if v := header.MajorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
if v := header.MinorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
|
||||
msgIDStart := make([]uint32, header.MsgIDCount)
|
||||
msgIDLen := make([]uint32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgIDOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgIDLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgIDStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
msgStrStart := make([]int32, header.MsgIDCount)
|
||||
msgStrLen := make([]int32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if _, err := r.Seek(int64(msgIDStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgIdData := make([]byte, msgIDLen[i])
|
||||
if _, err := r.Read(msgIdData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgStrData := make([]byte, msgStrLen[i])
|
||||
if _, err := r.Read(msgStrData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if len(msgIdData) == 0 {
|
||||
mo.addTranslation(msgIdData, msgStrData)
|
||||
} else {
|
||||
mo.addTranslation(msgIdData, msgStrData)
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock to parse headers
|
||||
mo.Unlock()
|
||||
|
||||
// Parse headers
|
||||
mo.parseHeaders()
|
||||
return
|
||||
// return nil
|
||||
}
|
||||
|
||||
func (mo *Mo) addTranslation(msgid, msgstr []byte) {
|
||||
translation := NewTranslation()
|
||||
var msgctxt []byte
|
||||
var msgidPlural []byte
|
||||
|
||||
d := bytes.Split(msgid, []byte(EotSeparator))
|
||||
if len(d) == 1 {
|
||||
msgid = d[0]
|
||||
} else {
|
||||
msgid, msgctxt = d[1], d[0]
|
||||
}
|
||||
|
||||
dd := bytes.Split(msgid, []byte(NulSeparator))
|
||||
if len(dd) > 1 {
|
||||
msgid = dd[0]
|
||||
dd = dd[1:]
|
||||
}
|
||||
|
||||
translation.ID = string(msgid)
|
||||
|
||||
msgidPlural = bytes.Join(dd, []byte(NulSeparator))
|
||||
if len(msgidPlural) > 0 {
|
||||
translation.PluralID = string(msgidPlural)
|
||||
}
|
||||
|
||||
ddd := bytes.Split(msgstr, []byte(NulSeparator))
|
||||
if len(ddd) > 0 {
|
||||
for i, s := range ddd {
|
||||
translation.Trs[i] = string(s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(msgctxt) > 0 {
|
||||
// With context...
|
||||
if _, ok := mo.contexts[string(msgctxt)]; !ok {
|
||||
mo.contexts[string(msgctxt)] = make(map[string]*Translation)
|
||||
}
|
||||
mo.contexts[string(msgctxt)][translation.ID] = translation
|
||||
} else {
|
||||
mo.translations[translation.ID] = translation
|
||||
}
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers
|
||||
func (mo *Mo) parseHeaders() {
|
||||
// Make sure we end with 2 carriage returns.
|
||||
raw := mo.Get("") + "\n\n"
|
||||
|
||||
// Read
|
||||
reader := bufio.NewReader(strings.NewReader(raw))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
var err error
|
||||
|
||||
// Sync Headers write.
|
||||
mo.Lock()
|
||||
defer mo.Unlock()
|
||||
|
||||
mo.Headers, err = tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
mo.Language = mo.Headers.Get("Language")
|
||||
mo.PluralForms = mo.Headers.Get("Plural-Forms")
|
||||
|
||||
// Parse Plural-Forms formula
|
||||
if mo.PluralForms == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(mo.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
mo.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
mo.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(mo.plural); err == nil {
|
||||
mo.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pluralForm calculates the plural form index corresponding to n.
|
||||
// Returns 0 on error
|
||||
func (mo *Mo) pluralForm(n int) int {
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if mo.pluralforms == nil {
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
|
||||
}
|
||||
return mo.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// 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 (mo *Mo) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
return Printf(mo.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetN 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.
|
||||
func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
mo.RLock()
|
||||
defer mo.RUnlock()
|
||||
|
||||
if mo.translations != nil {
|
||||
if _, ok := mo.translations[str]; ok {
|
||||
return Printf(mo.translations[str].GetN(mo.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC 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.
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// 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 {
|
||||
return Printf(mo.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetNC 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.
|
||||
func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// 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 {
|
||||
return Printf(mo.contexts[ctx][str].GetN(mo.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
204
mo_test.go
Normal file
204
mo_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMo_Get(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
mo := new(Mo)
|
||||
|
||||
// Try to parse a directory
|
||||
mo.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
||||
// Test translations
|
||||
tr := mo.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = mo.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMo(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
mo := new(Mo)
|
||||
|
||||
// Try to parse a directory
|
||||
mo.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
|
||||
// Test translations
|
||||
tr := mo.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
|
||||
v := "Variable"
|
||||
tr = mo.Get("One with var: %s", v)
|
||||
if tr != "This one is the singular: Variable" {
|
||||
t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line id
|
||||
tr = mo.Get("multilineid")
|
||||
if tr != "id with multiline content" {
|
||||
t.Errorf("Expected 'id with multiline content' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line plural id
|
||||
tr = mo.Get("multilinepluralid")
|
||||
if tr != "plural id with multiline content" {
|
||||
t.Errorf("Expected 'plural id with multiline content' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test multi-line
|
||||
tr = mo.Get("Multi-line")
|
||||
if tr != "Multi line" {
|
||||
t.Errorf("Expected 'Multi line' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = mo.GetN("One with var: %s", "Several with vars: %s", 2, v)
|
||||
if tr != "This one is the plural: Variable" {
|
||||
t.Errorf("Expected 'This one is the plural: Variable' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test not existent translations
|
||||
tr = mo.Get("This is a test")
|
||||
if tr != "This is a test" {
|
||||
t.Errorf("Expected 'This is a test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.GetN("This is a test", "This are tests", 100)
|
||||
if tr != "This are tests" {
|
||||
t.Errorf("Expected 'This are tests' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test context translations
|
||||
v = "Test"
|
||||
tr = mo.GetC("One with var: %s", "Ctx", v)
|
||||
if tr != "This one is the singular in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the singular in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test plural
|
||||
tr = mo.GetNC("One with var: %s", "Several with vars: %s", 17, "Ctx", v)
|
||||
if tr != "This one is the plural in a Ctx context: Test" {
|
||||
t.Errorf("Expected 'This one is the plural in a Ctx context: Test' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test default plural vs singular return responses
|
||||
tr = mo.GetN("Original", "Original plural", 4)
|
||||
if tr != "Original plural" {
|
||||
t.Errorf("Expected 'Original plural' but got '%s'", tr)
|
||||
}
|
||||
tr = mo.GetN("Original", "Original plural", 1)
|
||||
if tr != "Original" {
|
||||
t.Errorf("Expected 'Original' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test empty Translation strings
|
||||
tr = mo.Get("Empty Translation")
|
||||
if tr != "Empty Translation" {
|
||||
t.Errorf("Expected 'Empty Translation' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.Get("Empty plural form singular")
|
||||
if tr != "Singular translated" {
|
||||
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.GetN("Empty plural form singular", "Empty plural form", 1)
|
||||
if tr != "Singular translated" {
|
||||
t.Errorf("Expected 'Singular translated' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = mo.GetN("Empty plural form singular", "Empty plural form", 2)
|
||||
if tr != "Empty plural form" {
|
||||
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last Translation
|
||||
tr = mo.Get("More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := new(Mo)
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
rc := make(chan bool)
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(mo *Mo, done chan bool) {
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
done <- true
|
||||
}(mo, pc)
|
||||
|
||||
// Read some Translation on a goroutine
|
||||
go func(mo *Mo, done chan bool) {
|
||||
mo.Get("My text")
|
||||
done <- true
|
||||
}(mo, rc)
|
||||
|
||||
// Read something at top level
|
||||
mo.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestNewMoTranslatorRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := NewMoTranslator()
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
rc := make(chan bool)
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.mo")
|
||||
done <- true
|
||||
}(mo, pc)
|
||||
|
||||
// Read some Translation on a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
mo.Get("My text")
|
||||
done <- true
|
||||
}(mo, rc)
|
||||
|
||||
// Read something at top level
|
||||
mo.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package plurals is the pluralform compiler to get the correct translation id of the plural string
|
||||
*/
|
||||
package plurals
|
||||
|
||||
import (
|
||||
@@ -19,10 +23,6 @@ type match struct {
|
||||
|
||||
var pat = regexp.MustCompile(`(\?|:|\|\||&&|==|!=|>=|>|<=|<|%|\d+|n)`)
|
||||
|
||||
type exprToken interface {
|
||||
compile(tokens []string) (expr Expression, err error)
|
||||
}
|
||||
|
||||
type testToken interface {
|
||||
compile(tokens []string) (test test, err error)
|
||||
}
|
||||
@@ -47,18 +47,18 @@ func (ternaryStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
true_action, err := compileExpression(strings.Join(actions.Left, ""))
|
||||
trueAction, err := compileExpression(strings.Join(actions.Left, ""))
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
false_action, err := compileExpression(strings.Join(actions.Right, ""))
|
||||
falseAction, err := compileExpression(strings.Join(actions.Right, ""))
|
||||
if err != nil {
|
||||
return expr, nil
|
||||
}
|
||||
return ternary{
|
||||
test: test,
|
||||
trueExpr: true_action,
|
||||
falseExpr: false_action,
|
||||
trueExpr: trueAction,
|
||||
falseExpr: falseAction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -180,9 +180,9 @@ func compileEquality(tokens []string, sep string, builder cmpTestBuilder) (test
|
||||
return builder(i, true), nil
|
||||
} else if contains(split.Left, "n") && contains(split.Left, "%") {
|
||||
return subPipe(split.Left, split.Right, builder, false)
|
||||
} else {
|
||||
return test, errors.New("equality test must have 'n' as one of the two tests")
|
||||
}
|
||||
return test, errors.New("equality test must have 'n' as one of the two tests")
|
||||
|
||||
}
|
||||
|
||||
var eqToken eqStruct
|
||||
@@ -405,9 +405,8 @@ func compileExpression(s string) (expr Expression, err error) {
|
||||
tokens := tokenize(s)
|
||||
if contains(tokens, "?") {
|
||||
return ternaryToken.compile(tokens)
|
||||
} else {
|
||||
return constToken.compile(tokens)
|
||||
}
|
||||
return constToken.compile(tokens)
|
||||
}
|
||||
|
||||
// Compiles a test (comparison)
|
||||
@@ -425,7 +424,6 @@ func parseUint32(s string) (ui uint32, err error) {
|
||||
i, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return ui, err
|
||||
} else {
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,9 @@ func (t ternary) Eval(n uint32) int {
|
||||
return -1
|
||||
}
|
||||
return t.trueExpr.Eval(n)
|
||||
} else {
|
||||
}
|
||||
if t.falseExpr == nil {
|
||||
return -1
|
||||
}
|
||||
return t.falseExpr.Eval(n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,8 @@ type lt struct {
|
||||
func (e lt) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value < n
|
||||
} else {
|
||||
return n < e.value
|
||||
}
|
||||
return n < e.value
|
||||
}
|
||||
|
||||
type gte struct {
|
||||
@@ -55,9 +54,8 @@ type gte struct {
|
||||
func (e gte) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value >= n
|
||||
} else {
|
||||
return n >= e.value
|
||||
}
|
||||
return n >= e.value
|
||||
}
|
||||
|
||||
type lte struct {
|
||||
@@ -68,9 +66,8 @@ type lte struct {
|
||||
func (e lte) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value <= n
|
||||
} else {
|
||||
return n <= e.value
|
||||
}
|
||||
return n <= e.value
|
||||
}
|
||||
|
||||
type and struct {
|
||||
@@ -81,9 +78,8 @@ type and struct {
|
||||
func (e and) test(n uint32) bool {
|
||||
if !e.left.test(n) {
|
||||
return false
|
||||
} else {
|
||||
return e.right.test(n)
|
||||
}
|
||||
return e.right.test(n)
|
||||
}
|
||||
|
||||
type or struct {
|
||||
@@ -94,9 +90,8 @@ type or struct {
|
||||
func (e or) test(n uint32) bool {
|
||||
if e.left.test(n) {
|
||||
return true
|
||||
} else {
|
||||
return e.right.test(n)
|
||||
}
|
||||
return e.right.test(n)
|
||||
}
|
||||
|
||||
type pipe struct {
|
||||
|
||||
152
po.go
152
po.go
@@ -1,3 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
@@ -12,50 +17,8 @@ import (
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
type translation struct {
|
||||
id string
|
||||
pluralID string
|
||||
trs map[int]string
|
||||
}
|
||||
|
||||
func newTranslation() *translation {
|
||||
tr := new(translation)
|
||||
tr.trs = make(map[int]string)
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
func (t *translation) get() string {
|
||||
// Look for translation index 0
|
||||
if _, ok := t.trs[0]; ok {
|
||||
if t.trs[0] != "" {
|
||||
return t.trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated id by default
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *translation) getN(n int) string {
|
||||
// Look for translation index
|
||||
if _, ok := t.trs[n]; ok {
|
||||
if t.trs[n] != "" {
|
||||
return t.trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated singular if corresponding
|
||||
if n == 0 {
|
||||
return t.id
|
||||
}
|
||||
|
||||
// Return untranslated plural by default
|
||||
return t.pluralID
|
||||
}
|
||||
|
||||
/*
|
||||
Po parses the content of any PO file and provides all the translation functions needed.
|
||||
Po parses the content of any PO file and provides all the Translation functions needed.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
@@ -68,12 +31,12 @@ Example:
|
||||
|
||||
func main() {
|
||||
// Create po object
|
||||
po := new(gotext.Po)
|
||||
po := gotext.NewPoTranslator()
|
||||
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.po")
|
||||
|
||||
// Get translation
|
||||
// Get Translation
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
|
||||
@@ -94,14 +57,14 @@ type Po struct {
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*translation
|
||||
contexts map[string]map[string]*translation
|
||||
translations map[string]*Translation
|
||||
contexts map[string]map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *translation
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
}
|
||||
|
||||
@@ -115,6 +78,11 @@ const (
|
||||
msgStr
|
||||
)
|
||||
|
||||
// NewPoTranslator creates a new Po object with the Translator interface
|
||||
func NewPoTranslator() Translator {
|
||||
return new(Po)
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -134,25 +102,25 @@ func (po *Po) ParseFile(f string) {
|
||||
return
|
||||
}
|
||||
|
||||
po.Parse(string(data))
|
||||
po.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided string (str)
|
||||
func (po *Po) Parse(str string) {
|
||||
func (po *Po) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
po.Lock()
|
||||
|
||||
// Init storage
|
||||
if po.translations == nil {
|
||||
po.translations = make(map[string]*translation)
|
||||
po.contexts = make(map[string]map[string]*translation)
|
||||
po.translations = make(map[string]*Translation)
|
||||
po.contexts = make(map[string]map[string]*Translation)
|
||||
}
|
||||
|
||||
// Get lines
|
||||
lines := strings.Split(str, "\n")
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
|
||||
// Init buffer
|
||||
po.trBuffer = newTranslation()
|
||||
po.trBuffer = NewTranslation()
|
||||
po.ctxBuffer = ""
|
||||
|
||||
state := head
|
||||
@@ -186,7 +154,7 @@ func (po *Po) Parse(str string) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Save translation
|
||||
// Save Translation
|
||||
if strings.HasPrefix(l, "msgstr") {
|
||||
po.parseMessage(l)
|
||||
state = msgStr
|
||||
@@ -200,7 +168,7 @@ func (po *Po) Parse(str string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Save last translation buffer.
|
||||
// Save last Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Unlock to parse headers
|
||||
@@ -210,33 +178,33 @@ func (po *Po) Parse(str string) {
|
||||
po.parseHeaders()
|
||||
}
|
||||
|
||||
// saveBuffer takes the context and translation buffers
|
||||
// saveBuffer takes the context and Translation buffers
|
||||
// and saves it on the translations collection
|
||||
func (po *Po) saveBuffer() {
|
||||
// With no context...
|
||||
if po.ctxBuffer == "" {
|
||||
po.translations[po.trBuffer.id] = po.trBuffer
|
||||
po.translations[po.trBuffer.ID] = po.trBuffer
|
||||
} else {
|
||||
// With context...
|
||||
if _, ok := po.contexts[po.ctxBuffer]; !ok {
|
||||
po.contexts[po.ctxBuffer] = make(map[string]*translation)
|
||||
po.contexts[po.ctxBuffer] = make(map[string]*Translation)
|
||||
}
|
||||
po.contexts[po.ctxBuffer][po.trBuffer.id] = po.trBuffer
|
||||
po.contexts[po.ctxBuffer][po.trBuffer.ID] = po.trBuffer
|
||||
|
||||
// Cleanup current context buffer if needed
|
||||
if po.trBuffer.id != "" {
|
||||
if po.trBuffer.ID != "" {
|
||||
po.ctxBuffer = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Flush translation buffer
|
||||
po.trBuffer = newTranslation()
|
||||
// Flush Translation buffer
|
||||
po.trBuffer = NewTranslation()
|
||||
}
|
||||
|
||||
// parseContext takes a line starting with "msgctxt",
|
||||
// saves the current translation buffer and creates a new context.
|
||||
// saves the current Translation buffer and creates a new context.
|
||||
func (po *Po) parseContext(l string) {
|
||||
// Save current translation buffer.
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Buffer context
|
||||
@@ -244,25 +212,25 @@ func (po *Po) parseContext(l string) {
|
||||
}
|
||||
|
||||
// parseID takes a line starting with "msgid",
|
||||
// saves the current translation and creates a new msgid buffer.
|
||||
// saves the current Translation and creates a new msgid buffer.
|
||||
func (po *Po) parseID(l string) {
|
||||
// Save current translation buffer.
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Set id
|
||||
po.trBuffer.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
po.trBuffer.ID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
}
|
||||
|
||||
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||
func (po *Po) parsePluralID(l string) {
|
||||
po.trBuffer.pluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
po.trBuffer.PluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
}
|
||||
|
||||
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||
func (po *Po) parseMessage(l string) {
|
||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
|
||||
// Check for indexed translation forms
|
||||
// Check for indexed Translation forms
|
||||
if strings.HasPrefix(l, "[") {
|
||||
idx := strings.Index(l, "]")
|
||||
if idx == -1 {
|
||||
@@ -277,15 +245,15 @@ func (po *Po) parseMessage(l string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse translation string
|
||||
po.trBuffer.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
// Parse Translation string
|
||||
po.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
|
||||
// Loop
|
||||
return
|
||||
}
|
||||
|
||||
// Save single translation form under 0 index
|
||||
po.trBuffer.trs[0], _ = strconv.Unquote(l)
|
||||
// Save single Translation form under 0 index
|
||||
po.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||
}
|
||||
|
||||
// parseString takes a well formatted string without prefix
|
||||
@@ -295,16 +263,16 @@ func (po *Po) parseString(l string, state parseState) {
|
||||
|
||||
switch state {
|
||||
case msgStr:
|
||||
// Append to last translation found
|
||||
po.trBuffer.trs[len(po.trBuffer.trs)-1] += clean
|
||||
// Append to last Translation found
|
||||
po.trBuffer.Trs[len(po.trBuffer.Trs)-1] += clean
|
||||
|
||||
case msgID:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.id += clean
|
||||
po.trBuffer.ID += clean
|
||||
|
||||
case msgIDPlural:
|
||||
// Multiline msgid - Append to current id
|
||||
po.trBuffer.pluralID += clean
|
||||
po.trBuffer.PluralID += clean
|
||||
|
||||
case msgCtxt:
|
||||
// Multiline context - Append to current context
|
||||
@@ -405,7 +373,7 @@ func (po *Po) pluralForm(n int) int {
|
||||
return po.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// Get retrieves the corresponding translation for the given 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
|
||||
@@ -414,15 +382,15 @@ func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return printf(po.translations[str].get(), vars...)
|
||||
return Printf(po.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return printf(str, vars...)
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of translation for the given string.
|
||||
// GetN 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.
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -431,17 +399,17 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
|
||||
if po.translations != nil {
|
||||
if _, ok := po.translations[str]; ok {
|
||||
return printf(po.translations[str].getN(po.pluralForm(n)), vars...)
|
||||
return Printf(po.translations[str].GetN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return printf(str, vars...)
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return printf(plural, vars...)
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding translation for a given string in the given context.
|
||||
// GetC 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.
|
||||
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -452,17 +420,17 @@ func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return printf(po.contexts[ctx][str].get(), vars...)
|
||||
return Printf(po.contexts[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return printf(str, vars...)
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context.
|
||||
// GetNC 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.
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
@@ -473,14 +441,14 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
||||
if _, ok := po.contexts[ctx]; ok {
|
||||
if po.contexts[ctx] != nil {
|
||||
if _, ok := po.contexts[ctx][str]; ok {
|
||||
return printf(po.contexts[ctx][str].getN(po.pluralForm(n)), vars...)
|
||||
return Printf(po.contexts[ctx][str].GetN(po.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return printf(str, vars...)
|
||||
return Printf(str, vars...)
|
||||
}
|
||||
return printf(plural, vars...)
|
||||
return Printf(plural, vars...)
|
||||
}
|
||||
|
||||
122
po_test.go
122
po_test.go
@@ -1,11 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
)
|
||||
|
||||
func TestPo_Get(t *testing.T) {
|
||||
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
|
||||
// Try to parse a directory
|
||||
po.ParseFile(path.Clean(os.TempDir()))
|
||||
|
||||
// Parse file
|
||||
po.ParseFile("fixtures/en_US/default.po")
|
||||
|
||||
// Test translations
|
||||
tr := po.Get("My text")
|
||||
if tr != "Translated text" {
|
||||
t.Errorf("Expected 'Translated text' but got '%s'", tr)
|
||||
}
|
||||
// Test translations
|
||||
tr = po.Get("language")
|
||||
if tr != "en_US" {
|
||||
t.Errorf("Expected 'en_US' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPo(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
@@ -28,13 +57,15 @@ msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
# Multi-line msgid
|
||||
msgid "multi"
|
||||
msgid ""
|
||||
"multi"
|
||||
"line"
|
||||
"id"
|
||||
msgstr "id with multiline content"
|
||||
|
||||
# Multi-line msgid_plural
|
||||
msgid "multi"
|
||||
msgid ""
|
||||
"multi"
|
||||
"line"
|
||||
"plural"
|
||||
"id"
|
||||
@@ -42,7 +73,8 @@ msgstr "plural id with multiline content"
|
||||
|
||||
#Multi-line string
|
||||
msgid "Multi-line"
|
||||
msgstr "Multi "
|
||||
msgstr ""
|
||||
"Multi "
|
||||
"line"
|
||||
|
||||
msgid "One with var: %s"
|
||||
@@ -58,13 +90,13 @@ msgstr[0] "This one is the singular in a Ctx context: %s"
|
||||
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||
|
||||
msgid "Some random"
|
||||
msgstr "Some random translation"
|
||||
msgstr "Some random Translation"
|
||||
|
||||
msgctxt "Ctx"
|
||||
msgid "Some random in a context"
|
||||
msgstr "Some random translation in a context"
|
||||
msgstr "Some random Translation in a context"
|
||||
|
||||
msgid "Empty translation"
|
||||
msgid "Empty Translation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Empty plural form singular"
|
||||
@@ -73,7 +105,7 @@ msgstr[0] "Singular translated"
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "More"
|
||||
msgstr "More translation"
|
||||
msgstr "More Translation"
|
||||
|
||||
`
|
||||
|
||||
@@ -170,10 +202,10 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'Original' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test empty translation strings
|
||||
tr = po.Get("Empty translation")
|
||||
if tr != "Empty translation" {
|
||||
t.Errorf("Expected 'Empty translation' but got '%s'", tr)
|
||||
// Test empty Translation strings
|
||||
tr = po.Get("Empty Translation")
|
||||
if tr != "Empty Translation" {
|
||||
t.Errorf("Expected 'Empty Translation' but got '%s'", tr)
|
||||
}
|
||||
|
||||
tr = po.Get("Empty plural form singular")
|
||||
@@ -191,10 +223,10 @@ msgstr "More translation"
|
||||
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
||||
}
|
||||
|
||||
// Test last translation
|
||||
// Test last Translation
|
||||
tr = po.Get("More")
|
||||
if tr != "More translation" {
|
||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
||||
if tr != "More Translation" {
|
||||
t.Errorf("Expected 'More Translation' but got '%s'", tr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +247,7 @@ msgstr[2] "TR Plural 2: %s"
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
v := "Var"
|
||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||
@@ -229,7 +261,6 @@ msgstr[2] "TR Plural 2: %s"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestPluralNoHeaderInformation(t *testing.T) {
|
||||
// Set PO content
|
||||
str := `
|
||||
@@ -246,7 +277,7 @@ msgstr[2] "TR Plural 2: %s"
|
||||
`
|
||||
// Create po object
|
||||
po := new(Po)
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
v := "Var"
|
||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||
@@ -281,7 +312,7 @@ msgstr "Translated example"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check headers expected
|
||||
if po.Language != "en" {
|
||||
@@ -305,9 +336,9 @@ msgstr "Translated example"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check translation expected
|
||||
// Check Translation expected
|
||||
if po.Get("Example") != "Translated example" {
|
||||
t.Errorf("Expected 'Translated example' but got '%s'", po.Get("Example"))
|
||||
}
|
||||
@@ -333,7 +364,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
@@ -378,7 +409,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
@@ -419,7 +450,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(0)
|
||||
@@ -469,7 +500,7 @@ msgstr[3] "Plural form 3"
|
||||
po := new(Po)
|
||||
|
||||
// Parse
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
|
||||
// Check plural form
|
||||
n := po.pluralForm(1)
|
||||
@@ -495,19 +526,18 @@ msgstr[3] "Plural form 3"
|
||||
}
|
||||
|
||||
func TestTranslationObject(t *testing.T) {
|
||||
tr := newTranslation()
|
||||
str := tr.get()
|
||||
tr := NewTranslation()
|
||||
str := tr.Get()
|
||||
|
||||
if str != "" {
|
||||
t.Errorf("Expected '' but got '%s'", str)
|
||||
}
|
||||
|
||||
// Set id
|
||||
tr.id = "Text"
|
||||
tr.ID = "Text"
|
||||
str = tr.Get()
|
||||
|
||||
// Get again
|
||||
str = tr.get()
|
||||
|
||||
if str != "Text" {
|
||||
t.Errorf("Expected 'Text' but got '%s'", str)
|
||||
}
|
||||
@@ -540,11 +570,11 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(po *Po, done chan bool) {
|
||||
po.Parse(str)
|
||||
po.Parse([]byte(str))
|
||||
done <- true
|
||||
}(po, pc)
|
||||
|
||||
// Read some translation on a goroutine
|
||||
// Read some Translation on a goroutine
|
||||
go func(po *Po, done chan bool) {
|
||||
po.Get("My text")
|
||||
done <- true
|
||||
@@ -557,3 +587,33 @@ msgstr[2] "And this is the second plural form: %s"
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
func TestNewPoTranslatorRace(t *testing.T) {
|
||||
|
||||
// Create Po object
|
||||
mo := NewPoTranslator()
|
||||
|
||||
// Create sync channels
|
||||
pc := make(chan bool)
|
||||
rc := make(chan bool)
|
||||
|
||||
// Parse po content in a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
// Parse file
|
||||
mo.ParseFile("fixtures/en_US/default.po")
|
||||
done <- true
|
||||
}(mo, pc)
|
||||
|
||||
// Read some Translation on a goroutine
|
||||
go func(mo Translator, done chan bool) {
|
||||
mo.Get("My text")
|
||||
done <- true
|
||||
}(mo, rc)
|
||||
|
||||
// Read something at top level
|
||||
mo.Get("My text")
|
||||
|
||||
// Wait for goroutines to finish
|
||||
<-pc
|
||||
<-rc
|
||||
}
|
||||
|
||||
52
translation.go
Normal file
52
translation.go
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
// Translation is the struct for the Translations parsed via Po or Mo files and all coming parsers
|
||||
type Translation struct {
|
||||
ID string
|
||||
PluralID string
|
||||
Trs map[int]string
|
||||
}
|
||||
|
||||
// NewTranslation returns the Translation object and initialized it.
|
||||
func NewTranslation() *Translation {
|
||||
tr := new(Translation)
|
||||
tr.Trs = make(map[int]string)
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// Get returns the string of the translation
|
||||
func (t *Translation) Get() string {
|
||||
// Look for Translation index 0
|
||||
if _, ok := t.Trs[0]; ok {
|
||||
if t.Trs[0] != "" {
|
||||
return t.Trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated id by default
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// Get returns the string of the plural translation
|
||||
func (t *Translation) GetN(n int) string {
|
||||
// Look for Translation index
|
||||
if _, ok := t.Trs[n]; ok {
|
||||
if t.Trs[n] != "" {
|
||||
return t.Trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated singular if corresponding
|
||||
if n == 0 {
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// Return untranslated plural by default
|
||||
return t.PluralID
|
||||
}
|
||||
15
translator.go
Normal file
15
translator.go
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user