From 982d029c78672afc6c72fb1fda4556c7653286ae Mon Sep 17 00:00:00 2001 From: Leonel Quinteros Date: Thu, 23 Jun 2016 11:41:38 -0300 Subject: [PATCH] Add support for plural forms --- gotext.go | 17 ++++++- gotext_test.go | 15 ++++-- locale.go | 19 +++++++- locale_test.go | 15 ++++-- po.go | 126 ++++++++++++++++++++++++++++++++++++++++++------- po_test.go | 15 ++++-- 6 files changed, 178 insertions(+), 29 deletions(-) diff --git a/gotext.go b/gotext.go index 284e4f9..d464c65 100644 --- a/gotext.go +++ b/gotext.go @@ -79,15 +79,30 @@ func Configure(lib, lang, dom string) { } // Get uses the default domain globally set to return the corresponding translation of a given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func Get(str string, vars ...interface{}) string { return GetD(domain, str, vars...) } +// GetN retrieves the (N)th plural form translation for the given string in the "default" domain. +// If n == 0, usually the singular form of the string is returned as defined in the PO file. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. +func GetN(str, plural string, n int, vars ...interface{}) string { + return GetND("default", str, plural, n, vars...) +} + // GetD returns the corresponding translation in the given domain for a given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func GetD(dom, str string, vars ...interface{}) string { + return GetND(dom, str, str, 0, vars...) +} + +// GetND retrieves the (N)th plural form translation in the given domain for a given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. +func GetND(dom, str, plural string, n int, vars ...interface{}) string { // Try to load default package Locale storage loadStorage(false) // Return translation - return storage.GetD(dom, str, vars...) + return storage.GetND(dom, str, plural, n, vars...) } diff --git a/gotext_test.go b/gotext_test.go index 3f77096..35dcfc7 100644 --- a/gotext_test.go +++ b/gotext_test.go @@ -17,7 +17,10 @@ msgid "Another string" msgstr "" msgid "One with var: %s" -msgstr "This one sets the var: %s" +msgid_plural "Several with vars: %s" +msgstr[0] "This one is the singular: %s" +msgstr[1] "This one is the plural: %s" +msgstr[2] "And this is the second plural form: %s" ` @@ -50,7 +53,13 @@ msgstr "This one sets the var: %s" v := "Variable" tr = Get("One with var: %s", v) - if tr != "This one sets the var: Variable" { - t.Errorf("Expected 'This one sets the var: Variable' but got '%s'", tr) + if tr != "This one is the singular: Variable" { + t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr) + } + + // Test plural + tr = GetN("One with var: %s", "Several with vars: %s", 2, v) + if tr != "And this is the second plural form: Variable" { + t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr) } } diff --git a/locale.go b/locale.go index 4f2b56f..58b95f3 100644 --- a/locale.go +++ b/locale.go @@ -53,20 +53,35 @@ func (l *Locale) AddDomain(dom string) { } // Get uses a domain "default" to return the corresponding translation of a given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func (l *Locale) Get(str string, vars ...interface{}) string { return l.GetD("default", str, vars...) } +// GetN retrieves the (N)th plural form translation for the given string in the "default" domain. +// If n == 0, usually the singular form of the string is returned as defined in the PO file. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. +func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string { + return l.GetND("default", str, plural, n, vars...) +} + // GetD returns the corresponding translation in the given domain for a given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func (l *Locale) GetD(dom, str string, vars ...interface{}) string { + return l.GetND(dom, str, str, 0, vars...) +} + +// GetND retrieves the (N)th plural form translation in the given domain for a given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. +func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string { if l.domains != nil { if _, ok := l.domains[dom]; ok { if l.domains[dom] != nil { - return l.domains[dom].Get(str, vars...) + return l.domains[dom].GetN(str, plural, n, vars...) } } } // Return the same we received by default - return fmt.Sprintf(str, vars...) + return fmt.Sprintf(plural, vars...) } diff --git a/locale_test.go b/locale_test.go index db346f9..0aebb1f 100644 --- a/locale_test.go +++ b/locale_test.go @@ -17,7 +17,10 @@ msgid "Another string" msgstr "" msgid "One with var: %s" -msgstr "This one sets the var: %s" +msgid_plural "Several with vars: %s" +msgstr[0] "This one is the singular: %s" +msgstr[1] "This one is the plural: %s" +msgstr[2] "And this is the second plural form: %s" ` @@ -56,7 +59,13 @@ msgstr "This one sets the var: %s" v := "Variable" tr = l.GetD("my_domain", "One with var: %s", v) - if tr != "This one sets the var: Variable" { - t.Errorf("Expected 'This one sets the var: Variable' but got '%s'", tr) + if tr != "This one is the singular: Variable" { + t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr) + } + + // Test plural + tr = l.GetND("my_domain", "One with var: %s", "Several with vars: %s", 2, v) + if tr != "And this is the second plural form: Variable" { + t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr) } } diff --git a/po.go b/po.go index d1a3557..d734c30 100644 --- a/po.go +++ b/po.go @@ -9,10 +9,44 @@ import ( "sync" ) +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 { + return t.trs[0] + } + + // Return unstranlated id by default + return t.id +} + +func (t *translation) getN(n int) string { + // Look for translation index + if _, ok := t.trs[n]; ok { + return t.trs[n] + } + + // Return unstranlated plural by default + return t.pluralId +} + +// Po type handles the content of any PO file. type Po struct { - // Storage - translations map[string]string - + // Storage + translations map[string]*translation + // Sync Mutex sync.RWMutex } @@ -41,16 +75,16 @@ func (po *Po) ParseFile(f string) { // Parse loads the translations specified in the provided string (str) func (po *Po) Parse(str string) { - po.Lock() + po.Lock() defer po.Unlock() - + if po.translations == nil { - po.translations = make(map[string]string) + po.translations = make(map[string]*translation) } lines := strings.Split(str, "\n") - var msgid, msgstr string + tr := newTranslation() for _, l := range lines { // Trim spaces @@ -62,35 +96,93 @@ func (po *Po) Parse(str string) { } // Skip invalid lines - if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgstr") { + if !strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") && !strings.HasPrefix(l, "msgstr") { continue } // Buffer msgid and continue - if strings.HasPrefix(l, "msgid") { - msgid = strings.TrimSpace(strings.TrimPrefix(l, "msgid")) - msgid, _ = strconv.Unquote(msgid) + if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") { + // Save current translation buffer. + po.translations[tr.id] = tr + // Flush buffer + tr = newTranslation() + + // Set id + tr.id, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid"))) + + // Loop continue } - // Save translation for buffered msgid - if strings.HasPrefix(l, "msgstr") { - msgstr = strings.TrimSpace(strings.TrimPrefix(l, "msgstr")) - msgstr, _ = strconv.Unquote(msgstr) + // Check for plural form + if strings.HasPrefix(l, "msgid_plural") { + tr.pluralId, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural"))) - po.translations[msgid] = msgstr + // Loop + continue } + + // Save translation + if strings.HasPrefix(l, "msgstr") { + l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr")) + + // Check for indexed translation forms + if strings.HasPrefix(l, "[") { + in := strings.Index(l, "]") + if in == -1 { + // Skip wrong index formatting + continue + } + + // Parse index + i, err := strconv.Atoi(l[1:in]) + if err != nil { + // Skip wrong index formatting + continue + } + + // Parse translation string + tr.trs[i], _ = strconv.Unquote(strings.TrimSpace(l[in+1:])) + + // Loop + continue + } + + // Save single translation form under 0 index + tr.trs[0], _ = strconv.Unquote(l) + } + } + + // Save last translation buffer. + if tr.id != "" { + po.translations[tr.id] = tr } } +// Get retrieves the corresponding translation for the given string. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. func (po *Po) Get(str string, vars ...interface{}) string { if po.translations != nil { if _, ok := po.translations[str]; ok { - return fmt.Sprintf(po.translations[str], vars...) + return fmt.Sprintf(po.translations[str].get(), vars...) } } // Return the same we received by default return fmt.Sprintf(str, vars...) } + +// GetN retrieves the (N)th plural form translation for the given string. +// If n == 0, usually the singular form of the string is returned as defined in the PO file. +// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax. +func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string { + if po.translations != nil { + if _, ok := po.translations[str]; ok { + return fmt.Sprintf(po.translations[str].getN(n), vars...) + } + } + + // Return the plural string we received by default + return fmt.Sprintf(plural, vars...) +} diff --git a/po_test.go b/po_test.go index 521c842..b92b4d8 100644 --- a/po_test.go +++ b/po_test.go @@ -17,7 +17,10 @@ msgid "Another string" msgstr "" msgid "One with var: %s" -msgstr "This one sets the var: %s" +msgid_plural "Several with vars: %s" +msgstr[0] "This one is the singular: %s" +msgstr[1] "This one is the plural: %s" +msgstr[2] "And this is the second plural form: %s" ` // Write PO content to file @@ -46,7 +49,13 @@ msgstr "This one sets the var: %s" v := "Variable" tr = po.Get("One with var: %s", v) - if tr != "This one sets the var: Variable" { - t.Errorf("Expected 'This one sets the var: Variable' but got '%s'", tr) + if tr != "This one is the singular: Variable" { + t.Errorf("Expected 'This one is the singular: Variable' but got '%s'", tr) + } + + // Test plural + tr = po.GetN("One with var: %s", "Several with vars: %s", 2, v) + if tr != "And this is the second plural form: Variable" { + t.Errorf("Expected 'And this is the second plural form: Variable' but got '%s'", tr) } }