Create MO parser
Refactored a bit too, so we can use interfaces to take Mo and Po files added fixtures found that the parser for Po files have a bug... but it works... so not touched
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,9 @@
|
|||||||
.settings
|
.settings
|
||||||
.buildpath
|
.buildpath
|
||||||
|
|
||||||
|
# golang jetbrains shit
|
||||||
|
.idea
|
||||||
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.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
|
package gotext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ type config struct {
|
|||||||
// Language set.
|
// Language set.
|
||||||
language string
|
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
|
library string
|
||||||
|
|
||||||
// Storage for package level methods
|
// Storage for package level methods
|
||||||
@@ -65,7 +64,7 @@ func loadStorage(force bool) {
|
|||||||
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
|
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)
|
globalConfig.storage.AddDomain(globalConfig.domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,18 +73,27 @@ func loadStorage(force bool) {
|
|||||||
|
|
||||||
// GetDomain is the domain getter for the package configuration
|
// GetDomain is the domain getter for the package configuration
|
||||||
func GetDomain() string {
|
func GetDomain() string {
|
||||||
|
var dom string
|
||||||
globalConfig.RLock()
|
globalConfig.RLock()
|
||||||
dom := globalConfig.domain
|
if globalConfig.storage != nil {
|
||||||
|
dom = globalConfig.storage.GetDomain()
|
||||||
|
}
|
||||||
|
if dom == "" {
|
||||||
|
dom = globalConfig.domain
|
||||||
|
}
|
||||||
globalConfig.RUnlock()
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
return dom
|
return dom
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDomain sets the name for the domain to be used at package level.
|
// SetDomain sets the name for the domain to be used at package level.
|
||||||
// It reloads the corresponding translation file.
|
// It reloads the corresponding Translation file.
|
||||||
func SetDomain(dom string) {
|
func SetDomain(dom string) {
|
||||||
globalConfig.Lock()
|
globalConfig.Lock()
|
||||||
globalConfig.domain = dom
|
globalConfig.domain = dom
|
||||||
|
if globalConfig.storage != nil {
|
||||||
|
globalConfig.storage.SetDomain(dom)
|
||||||
|
}
|
||||||
globalConfig.Unlock()
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
loadStorage(true)
|
||||||
@@ -101,10 +109,10 @@ func GetLanguage() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetLanguage sets the language code to be used at package level.
|
// SetLanguage sets the language code to be used at package level.
|
||||||
// It reloads the corresponding translation file.
|
// It reloads the corresponding Translation file.
|
||||||
func SetLanguage(lang string) {
|
func SetLanguage(lang string) {
|
||||||
globalConfig.Lock()
|
globalConfig.Lock()
|
||||||
globalConfig.language = lang
|
globalConfig.language = SimplifiedLocale(lang)
|
||||||
globalConfig.Unlock()
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
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.
|
// SetLibrary sets the root path for the loale directories and files to be used at package level.
|
||||||
// It reloads the corresponding translation file.
|
// It reloads the corresponding Translation file.
|
||||||
func SetLibrary(lib string) {
|
func SetLibrary(lib string) {
|
||||||
globalConfig.Lock()
|
globalConfig.Lock()
|
||||||
globalConfig.library = lib
|
globalConfig.library = lib
|
||||||
@@ -129,47 +137,48 @@ func SetLibrary(lib string) {
|
|||||||
loadStorage(true)
|
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.
|
// It receives the library path, language code and domain name.
|
||||||
// This function is recommended to be used when changing more than one setting,
|
// This function is recommended to be used when changing more than one setting,
|
||||||
// as using each setter will introduce a I/O overhead because the translation file will be loaded after each set.
|
// as using each setter will introduce a I/O overhead because the Translation file will be loaded after each set.
|
||||||
func Configure(lib, lang, dom string) {
|
func Configure(lib, lang, dom string) {
|
||||||
globalConfig.Lock()
|
globalConfig.Lock()
|
||||||
|
|
||||||
globalConfig.library = lib
|
globalConfig.library = lib
|
||||||
globalConfig.language = lang
|
globalConfig.language = SimplifiedLocale(lang)
|
||||||
globalConfig.domain = dom
|
globalConfig.domain = dom
|
||||||
|
globalConfig.storage.SetDomain(dom)
|
||||||
|
|
||||||
globalConfig.Unlock()
|
globalConfig.Unlock()
|
||||||
|
|
||||||
loadStorage(true)
|
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.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func Get(str string, vars ...interface{}) string {
|
func Get(str string, vars ...interface{}) string {
|
||||||
return GetD(GetDomain(), str, vars...)
|
return GetD(GetDomain(), str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetN retrieves the (N)th plural form of translation for the given string in the default domain.
|
// GetN retrieves the (N)th plural form of Translation for the given string in the default domain.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
return GetND(GetDomain(), str, plural, n, vars...)
|
return GetND(GetDomain(), str, plural, n, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetD returns the corresponding translation in the given domain for a given string.
|
// GetD returns the corresponding Translation in the given domain for a given string.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetD(dom, str string, vars ...interface{}) string {
|
func GetD(dom, str string, vars ...interface{}) string {
|
||||||
return GetND(dom, str, str, 1, vars...)
|
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.
|
// 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 {
|
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||||
// Try to load default package Locale storage
|
// Try to load default package Locale storage
|
||||||
loadStorage(false)
|
loadStorage(false)
|
||||||
|
|
||||||
// Return translation
|
// Return Translation
|
||||||
globalConfig.RLock()
|
globalConfig.RLock()
|
||||||
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
|
||||||
globalConfig.RUnlock()
|
globalConfig.RUnlock()
|
||||||
@@ -177,43 +186,34 @@ func GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
|||||||
return tr
|
return tr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetC uses the default domain globally set to return the corresponding translation of the given string in the given context.
|
// GetC uses the default domain globally set to return the corresponding Translation of the given string in the given context.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetC(str, ctx string, vars ...interface{}) string {
|
func GetC(str, ctx string, vars ...interface{}) string {
|
||||||
return GetDC(GetDomain(), str, ctx, vars...)
|
return GetDC(GetDomain(), str, ctx, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the default domain.
|
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the default domain.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||||
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDC returns the corresponding translation in the given domain for the given string in the given context.
|
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
func GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||||
return GetNDC(dom, str, str, 1, ctx, vars...)
|
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.
|
// 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 {
|
func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||||
// Try to load default package Locale storage
|
// Try to load default package Locale storage
|
||||||
loadStorage(false)
|
loadStorage(false)
|
||||||
|
|
||||||
// Return translation
|
// Return Translation
|
||||||
globalConfig.RLock()
|
globalConfig.RLock()
|
||||||
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
|
||||||
globalConfig.RUnlock()
|
globalConfig.RUnlock()
|
||||||
|
|
||||||
return tr
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"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"
|
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||||
|
|
||||||
msgid "Some random"
|
msgid "Some random"
|
||||||
msgstr "Some random translation"
|
msgstr "Some random Translation"
|
||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Some random in a context"
|
msgid "Some random in a context"
|
||||||
msgstr "Some random translation in a context"
|
msgstr "Some random Translation in a context"
|
||||||
|
|
||||||
msgid "More"
|
msgid "More"
|
||||||
msgstr "More translation"
|
msgstr "More Translation"
|
||||||
|
|
||||||
msgid "Untranslated"
|
msgid "Untranslated"
|
||||||
msgid_plural "Several untranslated"
|
msgid_plural "Several untranslated"
|
||||||
@@ -95,13 +96,15 @@ msgstr[1] ""
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't create test file: %s", err.Error())
|
t.Fatalf("Can't create test file: %s", err.Error())
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = f.WriteString(str)
|
_, err = f.WriteString(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Can't write to test file: %s", err.Error())
|
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
|
// Set package configuration
|
||||||
Configure("/tmp", "en_US", "default")
|
Configure("/tmp", "en_US", "default")
|
||||||
|
|
||||||
@@ -125,8 +128,8 @@ msgstr[1] ""
|
|||||||
|
|
||||||
// Test context translations
|
// Test context translations
|
||||||
tr = GetC("Some random in a context", "Ctx")
|
tr = GetC("Some random in a context", "Ctx")
|
||||||
if tr != "Some random translation in a context" {
|
if tr != "Some random Translation in a context" {
|
||||||
t.Errorf("Expected 'Some random translation in a context' but got '%s'", tr)
|
t.Errorf("Expected 'Some random Translation in a context' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
v = "Variable"
|
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) {
|
func TestDomains(t *testing.T) {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
strDefault := `
|
strDefault := `
|
||||||
@@ -222,13 +257,13 @@ msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|||||||
|
|
||||||
msgid "Default text"
|
msgid "Default text"
|
||||||
msgid_plural "Default texts"
|
msgid_plural "Default texts"
|
||||||
msgstr[0] "Default translation"
|
msgstr[0] "Default Translation"
|
||||||
msgstr[1] "Default translations"
|
msgstr[1] "Default translations"
|
||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Default context"
|
msgid "Default context"
|
||||||
msgid_plural "Default contexts"
|
msgid_plural "Default contexts"
|
||||||
msgstr[0] "Default ctx translation"
|
msgstr[0] "Default ctx Translation"
|
||||||
msgstr[1] "Default ctx translations"
|
msgstr[1] "Default ctx translations"
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -238,13 +273,13 @@ msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|||||||
|
|
||||||
msgid "Custom text"
|
msgid "Custom text"
|
||||||
msgid_plural "Custom texts"
|
msgid_plural "Custom texts"
|
||||||
msgstr[0] "Custom translation"
|
msgstr[0] "Custom Translation"
|
||||||
msgstr[1] "Custom translations"
|
msgstr[1] "Custom translations"
|
||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Custom context"
|
msgid "Custom context"
|
||||||
msgid_plural "Custom contexts"
|
msgid_plural "Custom contexts"
|
||||||
msgstr[0] "Custom ctx translation"
|
msgstr[0] "Custom ctx Translation"
|
||||||
msgstr[1] "Custom ctx translations"
|
msgstr[1] "Custom ctx translations"
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -278,19 +313,19 @@ msgstr[1] "Custom ctx translations"
|
|||||||
|
|
||||||
Configure("/tmp", "en_US", "default")
|
Configure("/tmp", "en_US", "default")
|
||||||
|
|
||||||
// Check default domain translation
|
// Check default domain Translation
|
||||||
SetDomain("default")
|
SetDomain("default")
|
||||||
tr := Get("Default text")
|
tr := Get("Default text")
|
||||||
if tr != "Default translation" {
|
if tr != "Default Translation" {
|
||||||
t.Errorf("Expected 'Default translation'. Got '%s'", tr)
|
t.Errorf("Expected 'Default Translation'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
tr = GetN("Default text", "Default texts", 23)
|
tr = GetN("Default text", "Default texts", 23)
|
||||||
if tr != "Default translations" {
|
if tr != "Default translations" {
|
||||||
t.Errorf("Expected 'Default translations'. Got '%s'", tr)
|
t.Errorf("Expected 'Default translations'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
tr = GetC("Default context", "Ctx")
|
tr = GetC("Default context", "Ctx")
|
||||||
if tr != "Default ctx translation" {
|
if tr != "Default ctx Translation" {
|
||||||
t.Errorf("Expected 'Default ctx translation'. Got '%s'", tr)
|
t.Errorf("Expected 'Default ctx Translation'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
tr = GetNC("Default context", "Default contexts", 23, "Ctx")
|
tr = GetNC("Default context", "Default contexts", 23, "Ctx")
|
||||||
if tr != "Default ctx translations" {
|
if tr != "Default ctx translations" {
|
||||||
@@ -299,16 +334,16 @@ msgstr[1] "Custom ctx translations"
|
|||||||
|
|
||||||
SetDomain("custom")
|
SetDomain("custom")
|
||||||
tr = Get("Custom text")
|
tr = Get("Custom text")
|
||||||
if tr != "Custom translation" {
|
if tr != "Custom Translation" {
|
||||||
t.Errorf("Expected 'Custom translation'. Got '%s'", tr)
|
t.Errorf("Expected 'Custom Translation'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
tr = GetN("Custom text", "Custom texts", 23)
|
tr = GetN("Custom text", "Custom texts", 23)
|
||||||
if tr != "Custom translations" {
|
if tr != "Custom translations" {
|
||||||
t.Errorf("Expected 'Custom translations'. Got '%s'", tr)
|
t.Errorf("Expected 'Custom translations'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
tr = GetC("Custom context", "Ctx")
|
tr = GetC("Custom context", "Ctx")
|
||||||
if tr != "Custom ctx translation" {
|
if tr != "Custom ctx Translation" {
|
||||||
t.Errorf("Expected 'Custom ctx translation'. Got '%s'", tr)
|
t.Errorf("Expected 'Custom ctx Translation'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
tr = GetNC("Custom context", "Custom contexts", 23, "Ctx")
|
tr = GetNC("Custom context", "Custom contexts", 23, "Ctx")
|
||||||
if tr != "Custom ctx translations" {
|
if tr != "Custom ctx translations" {
|
||||||
@@ -334,7 +369,7 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Some random in a context"
|
msgid "Some random in a context"
|
||||||
msgstr "Some random translation in a context"
|
msgstr "Some random Translation in a context"
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
85
helper.go
Normal file
85
helper.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* 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]*[xsvTtbcdoqXxUeEfFgGp]`)
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
128
locale.go
128
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
|
package gotext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -22,13 +27,13 @@ Example:
|
|||||||
// Create Locale with library path and language code
|
// Create Locale with library path and language code
|
||||||
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
|
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")
|
l.AddDomain("default")
|
||||||
|
|
||||||
// Translate text from default domain
|
// Translate text from default domain
|
||||||
fmt.Println(l.Get("Translate this"))
|
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")
|
l.AddDomain("extras")
|
||||||
|
|
||||||
// Translate text from domain
|
// Translate text from domain
|
||||||
@@ -43,8 +48,11 @@ type Locale struct {
|
|||||||
// Language for this Locale
|
// Language for this Locale
|
||||||
lang string
|
lang string
|
||||||
|
|
||||||
// List of available domains for this locale.
|
// List of available Domains for this locale.
|
||||||
domains map[string]*Po
|
Domains map[string]Translator
|
||||||
|
|
||||||
|
// First AddDomain is default Domain
|
||||||
|
defaultDomain string
|
||||||
|
|
||||||
// Sync Mutex
|
// Sync Mutex
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
@@ -55,124 +63,162 @@ type Locale struct {
|
|||||||
func NewLocale(p, l string) *Locale {
|
func NewLocale(p, l string) *Locale {
|
||||||
return &Locale{
|
return &Locale{
|
||||||
path: p,
|
path: p,
|
||||||
lang: l,
|
lang: SimplifiedLocale(l),
|
||||||
domains: make(map[string]*Po),
|
Domains: make(map[string]Translator),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Locale) findPO(dom string) string {
|
func (l *Locale) findExt(dom, ext string) string {
|
||||||
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+".po")
|
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
|
||||||
if _, err := os.Stat(filename); err == nil {
|
if _, err := os.Stat(filename); err == nil {
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(l.lang) > 2 {
|
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 {
|
if _, err := os.Stat(filename); err == nil {
|
||||||
return filename
|
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 {
|
if _, err := os.Stat(filename); err == nil {
|
||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(l.lang) > 2 {
|
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.
|
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||||
// If the domain exists, it gets reloaded.
|
// If the domain exists, it gets reloaded.
|
||||||
func (l *Locale) AddDomain(dom string) {
|
func (l *Locale) AddDomain(dom string) {
|
||||||
po := new(Po)
|
var poObj Translator
|
||||||
|
|
||||||
// Parse file.
|
file := l.findExt(dom, "po")
|
||||||
po.ParseFile(l.findPO(dom))
|
if file != "" {
|
||||||
|
poObj = new(Po)
|
||||||
|
// Parse file.
|
||||||
|
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
|
// Save new domain
|
||||||
l.Lock()
|
l.Lock()
|
||||||
defer l.Unlock()
|
|
||||||
|
|
||||||
if l.domains == nil {
|
if l.Domains == nil {
|
||||||
l.domains = make(map[string]*Po)
|
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.
|
// 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 {
|
func (l *Locale) Get(str string, vars ...interface{}) string {
|
||||||
return l.GetD(GetDomain(), str, vars...)
|
return l.GetD(l.defaultDomain, str, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetN retrieves the (N)th plural form of translation for the given string in the "default" domain.
|
// GetN retrieves the (N)th plural form of Translation for the given string in the "default" domain.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
return l.GetND(GetDomain(), str, plural, n, vars...)
|
return l.GetND(l.defaultDomain, 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.
|
// 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 {
|
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||||
return l.GetND(dom, str, str, 1, vars...)
|
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.
|
// 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 {
|
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
l.RLock()
|
l.RLock()
|
||||||
defer l.RUnlock()
|
defer l.RUnlock()
|
||||||
|
|
||||||
if l.domains != nil {
|
if l.Domains != nil {
|
||||||
if _, ok := l.domains[dom]; ok {
|
if _, ok := l.Domains[dom]; ok {
|
||||||
if l.domains[dom] != nil {
|
if l.Domains[dom] != nil {
|
||||||
return l.domains[dom].GetN(str, plural, n, vars...)
|
return l.Domains[dom].GetN(str, plural, n, vars...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the same we received by default
|
// 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.
|
// 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 {
|
func (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
|
||||||
return l.GetDC(GetDomain(), str, ctx, vars...)
|
return l.GetDC(l.defaultDomain, str, ctx, vars...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNC retrieves the (N)th plural form of translation for the given string in the given context in the "default" domain.
|
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
|
||||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
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.defaultDomain, 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.
|
// 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 {
|
func (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||||
return l.GetNDC(dom, str, str, 1, ctx, vars...)
|
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.
|
// 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 {
|
func (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
l.RLock()
|
l.RLock()
|
||||||
defer l.RUnlock()
|
defer l.RUnlock()
|
||||||
|
|
||||||
if l.domains != nil {
|
if l.Domains != nil {
|
||||||
if _, ok := l.domains[dom]; ok {
|
if _, ok := l.Domains[dom]; ok {
|
||||||
if l.domains[dom] != nil {
|
if l.Domains[dom] != nil {
|
||||||
return l.domains[dom].GetNC(str, plural, n, ctx, vars...)
|
return l.Domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the same we received by default
|
// 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
|
package gotext
|
||||||
|
|
||||||
import (
|
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"
|
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||||
|
|
||||||
msgid "Some random"
|
msgid "Some random"
|
||||||
msgstr "Some random translation"
|
msgstr "Some random Translation"
|
||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Some random in a context"
|
msgid "Some random in a context"
|
||||||
msgstr "Some random translation in a context"
|
msgstr "Some random Translation in a context"
|
||||||
|
|
||||||
msgid "More"
|
msgid "More"
|
||||||
msgstr "More translation"
|
msgstr "More Translation"
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -81,14 +86,11 @@ msgstr "More translation"
|
|||||||
l := NewLocale("/tmp", "en_US")
|
l := NewLocale("/tmp", "en_US")
|
||||||
|
|
||||||
// Force nil domain storage
|
// Force nil domain storage
|
||||||
l.domains = nil
|
l.Domains = nil
|
||||||
|
|
||||||
// Add domain
|
// Add domain
|
||||||
l.AddDomain("my_domain")
|
l.AddDomain("my_domain")
|
||||||
|
|
||||||
// Set global domain
|
|
||||||
SetDomain("my_domain")
|
|
||||||
|
|
||||||
// Test translations
|
// Test translations
|
||||||
tr := l.GetD("my_domain", "My text")
|
tr := l.GetD("my_domain", "My text")
|
||||||
if tr != "Translated text" {
|
if tr != "Translated text" {
|
||||||
@@ -109,8 +111,8 @@ msgstr "More translation"
|
|||||||
|
|
||||||
// Test context translations
|
// Test context translations
|
||||||
tr = l.GetC("Some random in a context", "Ctx")
|
tr = l.GetC("Some random in a context", "Ctx")
|
||||||
if tr != "Some random translation in a context" {
|
if tr != "Some random Translation in a context" {
|
||||||
t.Errorf("Expected 'Some random translation in a context'. Got '%s'", tr)
|
t.Errorf("Expected 'Some random Translation in a context'. Got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
v = "Test"
|
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)
|
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")
|
tr = l.GetD("my_domain", "More")
|
||||||
if tr != "More translation" {
|
if tr != "More Translation" {
|
||||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
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"
|
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||||
|
|
||||||
msgid "Some random"
|
msgid "Some random"
|
||||||
msgstr "Some random translation"
|
msgstr "Some random Translation"
|
||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Some random in a context"
|
msgid "Some random in a context"
|
||||||
msgstr "Some random translation in a context"
|
msgstr "Some random Translation in a context"
|
||||||
|
|
||||||
msgid "More"
|
msgid "More"
|
||||||
msgstr "More translation"
|
msgstr "More Translation"
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -214,16 +216,28 @@ msgstr "More translation"
|
|||||||
l := NewLocale("/tmp", "en_US")
|
l := NewLocale("/tmp", "en_US")
|
||||||
|
|
||||||
// Force nil domain storage
|
// Force nil domain storage
|
||||||
l.domains = nil
|
l.Domains = nil
|
||||||
|
|
||||||
// Add domain
|
// Add domain
|
||||||
l.AddDomain("my_domain")
|
l.AddDomain("my_domain")
|
||||||
|
|
||||||
// Set default domain to make it fail
|
// Test non-existent "default" domain responses
|
||||||
SetDomain("default")
|
tr := l.GetDomain()
|
||||||
|
if tr != "my_domain" {
|
||||||
|
t.Errorf("Expected 'my_domain' but got '%s'", tr)
|
||||||
|
}
|
||||||
|
|
||||||
// Test non-existent "deafult" domain responses
|
// Set default domain to make it fail
|
||||||
tr := l.Get("My text")
|
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" {
|
if tr != "My text" {
|
||||||
t.Errorf("Expected 'My text' but got '%s'", tr)
|
t.Errorf("Expected 'My text' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
@@ -255,6 +269,121 @@ msgstr "More translation"
|
|||||||
if tr != "This are tests" {
|
if tr != "This are tests" {
|
||||||
t.Errorf("Expected 'Plural index' but got '%s'", tr)
|
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) {
|
func TestLocaleRace(t *testing.T) {
|
||||||
|
|||||||
421
mo.go
Normal file
421
mo.go
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
MoMagicBigEndian = 0xde120495
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
151
po.go
151
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
|
package gotext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -12,50 +17,8 @@ import (
|
|||||||
"github.com/leonelquinteros/gotext/plurals"
|
"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.
|
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.
|
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||||
|
|
||||||
@@ -68,12 +31,12 @@ Example:
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Create po object
|
// Create po object
|
||||||
po := new(gotext.Po)
|
po := gotext.NewPoTranslator()
|
||||||
|
|
||||||
// Parse .po file
|
// Parse .po file
|
||||||
po.ParseFile("/path/to/po/file/translations.po")
|
po.ParseFile("/path/to/po/file/translations.po")
|
||||||
|
|
||||||
// Get translation
|
// Get Translation
|
||||||
fmt.Println(po.Get("Translate this"))
|
fmt.Println(po.Get("Translate this"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,14 +57,14 @@ type Po struct {
|
|||||||
pluralforms plurals.Expression
|
pluralforms plurals.Expression
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
translations map[string]*translation
|
translations map[string]*Translation
|
||||||
contexts map[string]map[string]*translation
|
contexts map[string]map[string]*Translation
|
||||||
|
|
||||||
// Sync Mutex
|
// Sync Mutex
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
// Parsing buffers
|
// Parsing buffers
|
||||||
trBuffer *translation
|
trBuffer *Translation
|
||||||
ctxBuffer string
|
ctxBuffer string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +78,10 @@ const (
|
|||||||
msgStr
|
msgStr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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.
|
// 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) {
|
func (po *Po) ParseFile(f string) {
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
@@ -134,25 +101,25 @@ func (po *Po) ParseFile(f string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
po.Parse(string(data))
|
po.Parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse loads the translations specified in the provided string (str)
|
// 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
|
// Lock while parsing
|
||||||
po.Lock()
|
po.Lock()
|
||||||
|
|
||||||
// Init storage
|
// Init storage
|
||||||
if po.translations == nil {
|
if po.translations == nil {
|
||||||
po.translations = make(map[string]*translation)
|
po.translations = make(map[string]*Translation)
|
||||||
po.contexts = make(map[string]map[string]*translation)
|
po.contexts = make(map[string]map[string]*Translation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get lines
|
// Get lines
|
||||||
lines := strings.Split(str, "\n")
|
lines := strings.Split(string(buf), "\n")
|
||||||
|
|
||||||
// Init buffer
|
// Init buffer
|
||||||
po.trBuffer = newTranslation()
|
po.trBuffer = NewTranslation()
|
||||||
po.ctxBuffer = ""
|
po.ctxBuffer = ""
|
||||||
|
|
||||||
state := head
|
state := head
|
||||||
@@ -186,7 +153,7 @@ func (po *Po) Parse(str string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save translation
|
// Save Translation
|
||||||
if strings.HasPrefix(l, "msgstr") {
|
if strings.HasPrefix(l, "msgstr") {
|
||||||
po.parseMessage(l)
|
po.parseMessage(l)
|
||||||
state = msgStr
|
state = msgStr
|
||||||
@@ -200,7 +167,7 @@ func (po *Po) Parse(str string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save last translation buffer.
|
// Save last Translation buffer.
|
||||||
po.saveBuffer()
|
po.saveBuffer()
|
||||||
|
|
||||||
// Unlock to parse headers
|
// Unlock to parse headers
|
||||||
@@ -210,33 +177,33 @@ func (po *Po) Parse(str string) {
|
|||||||
po.parseHeaders()
|
po.parseHeaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveBuffer takes the context and translation buffers
|
// saveBuffer takes the context and Translation buffers
|
||||||
// and saves it on the translations collection
|
// and saves it on the translations collection
|
||||||
func (po *Po) saveBuffer() {
|
func (po *Po) saveBuffer() {
|
||||||
// With no context...
|
// With no context...
|
||||||
if po.ctxBuffer == "" {
|
if po.ctxBuffer == "" {
|
||||||
po.translations[po.trBuffer.id] = po.trBuffer
|
po.translations[po.trBuffer.ID] = po.trBuffer
|
||||||
} else {
|
} else {
|
||||||
// With context...
|
// With context...
|
||||||
if _, ok := po.contexts[po.ctxBuffer]; !ok {
|
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
|
// Cleanup current context buffer if needed
|
||||||
if po.trBuffer.id != "" {
|
if po.trBuffer.ID != "" {
|
||||||
po.ctxBuffer = ""
|
po.ctxBuffer = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush translation buffer
|
// Flush Translation buffer
|
||||||
po.trBuffer = newTranslation()
|
po.trBuffer = NewTranslation()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseContext takes a line starting with "msgctxt",
|
// 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) {
|
func (po *Po) parseContext(l string) {
|
||||||
// Save current translation buffer.
|
// Save current Translation buffer.
|
||||||
po.saveBuffer()
|
po.saveBuffer()
|
||||||
|
|
||||||
// Buffer context
|
// Buffer context
|
||||||
@@ -244,25 +211,25 @@ func (po *Po) parseContext(l string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseID takes a line starting with "msgid",
|
// 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) {
|
func (po *Po) parseID(l string) {
|
||||||
// Save current translation buffer.
|
// Save current Translation buffer.
|
||||||
po.saveBuffer()
|
po.saveBuffer()
|
||||||
|
|
||||||
// Set id
|
// 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"
|
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||||
func (po *Po) parsePluralID(l string) {
|
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.
|
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||||
func (po *Po) parseMessage(l string) {
|
func (po *Po) parseMessage(l string) {
|
||||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||||
|
|
||||||
// Check for indexed translation forms
|
// Check for indexed Translation forms
|
||||||
if strings.HasPrefix(l, "[") {
|
if strings.HasPrefix(l, "[") {
|
||||||
idx := strings.Index(l, "]")
|
idx := strings.Index(l, "]")
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
@@ -277,15 +244,15 @@ func (po *Po) parseMessage(l string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse translation string
|
// Parse Translation string
|
||||||
po.trBuffer.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
po.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save single translation form under 0 index
|
// Save single Translation form under 0 index
|
||||||
po.trBuffer.trs[0], _ = strconv.Unquote(l)
|
po.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseString takes a well formatted string without prefix
|
// parseString takes a well formatted string without prefix
|
||||||
@@ -295,16 +262,16 @@ func (po *Po) parseString(l string, state parseState) {
|
|||||||
|
|
||||||
switch state {
|
switch state {
|
||||||
case msgStr:
|
case msgStr:
|
||||||
// Append to last translation found
|
// Append to last Translation found
|
||||||
po.trBuffer.trs[len(po.trBuffer.trs)-1] += clean
|
po.trBuffer.Trs[len(po.trBuffer.Trs)-1] += clean
|
||||||
|
|
||||||
case msgID:
|
case msgID:
|
||||||
// Multiline msgid - Append to current id
|
// Multiline msgid - Append to current id
|
||||||
po.trBuffer.id += clean
|
po.trBuffer.ID += clean
|
||||||
|
|
||||||
case msgIDPlural:
|
case msgIDPlural:
|
||||||
// Multiline msgid - Append to current id
|
// Multiline msgid - Append to current id
|
||||||
po.trBuffer.pluralID += clean
|
po.trBuffer.PluralID += clean
|
||||||
|
|
||||||
case msgCtxt:
|
case msgCtxt:
|
||||||
// Multiline context - Append to current context
|
// Multiline context - Append to current context
|
||||||
@@ -405,7 +372,7 @@ func (po *Po) pluralForm(n int) int {
|
|||||||
return po.pluralforms.Eval(uint32(n))
|
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.
|
// 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 {
|
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
@@ -414,15 +381,15 @@ func (po *Po) Get(str string, vars ...interface{}) string {
|
|||||||
|
|
||||||
if po.translations != nil {
|
if po.translations != nil {
|
||||||
if _, ok := po.translations[str]; ok {
|
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 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.
|
// 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 {
|
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
@@ -431,17 +398,17 @@ func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
|||||||
|
|
||||||
if po.translations != nil {
|
if po.translations != nil {
|
||||||
if _, ok := po.translations[str]; ok {
|
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 {
|
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.
|
// 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 {
|
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
@@ -452,17 +419,17 @@ func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
|||||||
if _, ok := po.contexts[ctx]; ok {
|
if _, ok := po.contexts[ctx]; ok {
|
||||||
if po.contexts[ctx] != nil {
|
if po.contexts[ctx] != nil {
|
||||||
if _, ok := po.contexts[ctx][str]; ok {
|
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 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.
|
// 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 {
|
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||||
// Sync read
|
// Sync read
|
||||||
@@ -473,14 +440,14 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
|
|||||||
if _, ok := po.contexts[ctx]; ok {
|
if _, ok := po.contexts[ctx]; ok {
|
||||||
if po.contexts[ctx] != nil {
|
if po.contexts[ctx] != nil {
|
||||||
if _, ok := po.contexts[ctx][str]; ok {
|
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 {
|
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
|
package gotext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"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) {
|
func TestPo(t *testing.T) {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
str := `
|
str := `
|
||||||
@@ -28,13 +57,15 @@ msgid "Another string"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
# Multi-line msgid
|
# Multi-line msgid
|
||||||
msgid "multi"
|
msgid ""
|
||||||
|
"multi"
|
||||||
"line"
|
"line"
|
||||||
"id"
|
"id"
|
||||||
msgstr "id with multiline content"
|
msgstr "id with multiline content"
|
||||||
|
|
||||||
# Multi-line msgid_plural
|
# Multi-line msgid_plural
|
||||||
msgid "multi"
|
msgid ""
|
||||||
|
"multi"
|
||||||
"line"
|
"line"
|
||||||
"plural"
|
"plural"
|
||||||
"id"
|
"id"
|
||||||
@@ -42,7 +73,8 @@ msgstr "plural id with multiline content"
|
|||||||
|
|
||||||
#Multi-line string
|
#Multi-line string
|
||||||
msgid "Multi-line"
|
msgid "Multi-line"
|
||||||
msgstr "Multi "
|
msgstr ""
|
||||||
|
"Multi "
|
||||||
"line"
|
"line"
|
||||||
|
|
||||||
msgid "One with var: %s"
|
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"
|
msgstr[1] "This one is the plural in a Ctx context: %s"
|
||||||
|
|
||||||
msgid "Some random"
|
msgid "Some random"
|
||||||
msgstr "Some random translation"
|
msgstr "Some random Translation"
|
||||||
|
|
||||||
msgctxt "Ctx"
|
msgctxt "Ctx"
|
||||||
msgid "Some random in a context"
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Empty plural form singular"
|
msgid "Empty plural form singular"
|
||||||
@@ -73,7 +105,7 @@ msgstr[0] "Singular translated"
|
|||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
msgid "More"
|
msgid "More"
|
||||||
msgstr "More translation"
|
msgstr "More Translation"
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -170,10 +202,10 @@ msgstr "More translation"
|
|||||||
t.Errorf("Expected 'Original' but got '%s'", tr)
|
t.Errorf("Expected 'Original' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test empty translation strings
|
// Test empty Translation strings
|
||||||
tr = po.Get("Empty translation")
|
tr = po.Get("Empty Translation")
|
||||||
if tr != "Empty translation" {
|
if tr != "Empty Translation" {
|
||||||
t.Errorf("Expected 'Empty translation' but got '%s'", tr)
|
t.Errorf("Expected 'Empty Translation' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
tr = po.Get("Empty plural form singular")
|
tr = po.Get("Empty plural form singular")
|
||||||
@@ -191,10 +223,10 @@ msgstr "More translation"
|
|||||||
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
t.Errorf("Expected 'Empty plural form' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test last translation
|
// Test last Translation
|
||||||
tr = po.Get("More")
|
tr = po.Get("More")
|
||||||
if tr != "More translation" {
|
if tr != "More Translation" {
|
||||||
t.Errorf("Expected 'More translation' but got '%s'", tr)
|
t.Errorf("Expected 'More Translation' but got '%s'", tr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +247,7 @@ msgstr[2] "TR Plural 2: %s"
|
|||||||
`
|
`
|
||||||
// Create po object
|
// Create po object
|
||||||
po := new(Po)
|
po := new(Po)
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
v := "Var"
|
v := "Var"
|
||||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||||
@@ -229,7 +261,6 @@ msgstr[2] "TR Plural 2: %s"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestPluralNoHeaderInformation(t *testing.T) {
|
func TestPluralNoHeaderInformation(t *testing.T) {
|
||||||
// Set PO content
|
// Set PO content
|
||||||
str := `
|
str := `
|
||||||
@@ -246,7 +277,7 @@ msgstr[2] "TR Plural 2: %s"
|
|||||||
`
|
`
|
||||||
// Create po object
|
// Create po object
|
||||||
po := new(Po)
|
po := new(Po)
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
v := "Var"
|
v := "Var"
|
||||||
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
tr := po.GetN("Singular: %s", "Plural: %s", 2, v)
|
||||||
@@ -281,7 +312,7 @@ msgstr "Translated example"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
// Check headers expected
|
// Check headers expected
|
||||||
if po.Language != "en" {
|
if po.Language != "en" {
|
||||||
@@ -305,9 +336,9 @@ msgstr "Translated example"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
// Check translation expected
|
// Check Translation expected
|
||||||
if po.Get("Example") != "Translated example" {
|
if po.Get("Example") != "Translated example" {
|
||||||
t.Errorf("Expected 'Translated example' but got '%s'", po.Get("Example"))
|
t.Errorf("Expected 'Translated example' but got '%s'", po.Get("Example"))
|
||||||
}
|
}
|
||||||
@@ -333,7 +364,7 @@ msgstr[3] "Plural form 3"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
// Check plural form
|
// Check plural form
|
||||||
n := po.pluralForm(0)
|
n := po.pluralForm(0)
|
||||||
@@ -378,7 +409,7 @@ msgstr[3] "Plural form 3"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
// Check plural form
|
// Check plural form
|
||||||
n := po.pluralForm(0)
|
n := po.pluralForm(0)
|
||||||
@@ -419,7 +450,7 @@ msgstr[3] "Plural form 3"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
// Check plural form
|
// Check plural form
|
||||||
n := po.pluralForm(0)
|
n := po.pluralForm(0)
|
||||||
@@ -469,7 +500,7 @@ msgstr[3] "Plural form 3"
|
|||||||
po := new(Po)
|
po := new(Po)
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
|
|
||||||
// Check plural form
|
// Check plural form
|
||||||
n := po.pluralForm(1)
|
n := po.pluralForm(1)
|
||||||
@@ -495,19 +526,18 @@ msgstr[3] "Plural form 3"
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTranslationObject(t *testing.T) {
|
func TestTranslationObject(t *testing.T) {
|
||||||
tr := newTranslation()
|
tr := NewTranslation()
|
||||||
str := tr.get()
|
str := tr.Get()
|
||||||
|
|
||||||
if str != "" {
|
if str != "" {
|
||||||
t.Errorf("Expected '' but got '%s'", str)
|
t.Errorf("Expected '' but got '%s'", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set id
|
// Set id
|
||||||
tr.id = "Text"
|
tr.ID = "Text"
|
||||||
|
str = tr.Get()
|
||||||
|
|
||||||
// Get again
|
// Get again
|
||||||
str = tr.get()
|
|
||||||
|
|
||||||
if str != "Text" {
|
if str != "Text" {
|
||||||
t.Errorf("Expected 'Text' but got '%s'", str)
|
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
|
// Parse po content in a goroutine
|
||||||
go func(po *Po, done chan bool) {
|
go func(po *Po, done chan bool) {
|
||||||
po.Parse(str)
|
po.Parse([]byte(str))
|
||||||
done <- true
|
done <- true
|
||||||
}(po, pc)
|
}(po, pc)
|
||||||
|
|
||||||
// Read some translation on a goroutine
|
// Read some Translation on a goroutine
|
||||||
go func(po *Po, done chan bool) {
|
go func(po *Po, done chan bool) {
|
||||||
po.Get("My text")
|
po.Get("My text")
|
||||||
done <- true
|
done <- true
|
||||||
@@ -557,3 +587,33 @@ msgstr[2] "And this is the second plural form: %s"
|
|||||||
<-pc
|
<-pc
|
||||||
<-rc
|
<-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
|
||||||
|
}
|
||||||
|
|||||||
48
translation.go
Normal file
48
translation.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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 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
|
||||||
|
}
|
||||||
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