diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | README.md | 28 | ||||
-rw-r--r-- | _examples/de_DE.utf8/LC_MESSAGES/example.mo (renamed from examples/de_DE.utf8/LC_MESSAGES/example.mo) | bin | 606 -> 614 bytes | |||
-rw-r--r-- | _examples/de_DE.utf8/example.pot (renamed from examples/de_DE.utf8/example.pot) | 2 | ||||
-rw-r--r-- | _examples/es_MX.utf8/LC_MESSAGES/example.mo (renamed from examples/es_MX.utf8/LC_MESSAGES/example.mo) | bin | 614 -> 614 bytes | |||
-rw-r--r-- | _examples/es_MX.utf8/example.pot (renamed from examples/es_MX.utf8/example.pot) | 0 | ||||
-rw-r--r-- | _examples/gettext.go (renamed from examples/gettext.go) | 0 | ||||
-rw-r--r-- | gettext.go | 215 | ||||
-rw-r--r-- | gettext_test.go | 165 |
10 files changed, 158 insertions, 255 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45d62d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.sw? @@ -1,4 +1,4 @@ -Copyright (c) 2012-2013 José Carlos Nieto, http://xiam.menteslibres.org/ +Copyright (c) 2012-2016 José Carlos Nieto, https://menteslibres.net/xiam Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -5,8 +5,20 @@ library for writing multilingual systems. ## Requeriments -The GNU C library. If you're using GNU/Linux, FreeBSD or OSX you should already -have it. +* [GNU gettext][1] + +### OSX + +Installing gettext on a Mac is a bit awkward: + +``` +brew install gettext + +export CGO_LDFLAGS=-L/usr/local/opt/gettext/lib +export CGO_CPPFLAGS=-I/usr/local/opt/gettext/include + +go get github.com/gosexy/gettext +``` ## Installation @@ -22,18 +34,18 @@ go get github.com/gosexy/gettext package main import ( - "github.com/gosexy/gettext" "fmt" - "os" + + "github.com/gosexy/gettext" ) func main() { - gettext.BindTextdomain("example", ".") - gettext.Textdomain("example") + textDomain := "default" - os.Setenv("LANGUAGE", "es_MX.utf8") + gettext.BindTextdomain(textDomain, "path/to/domains") + gettext.Textdomain(textDomain) - gettext.SetLocale(gettext.LC_ALL, "") + gettext.SetLocale(gettext.LcAll, "es_MX.utf8") fmt.Println(gettext.Gettext("Hello, world!")) } diff --git a/examples/de_DE.utf8/LC_MESSAGES/example.mo b/_examples/de_DE.utf8/LC_MESSAGES/example.mo Binary files differindex 36cc2a3..9c8c27d 100644 --- a/examples/de_DE.utf8/LC_MESSAGES/example.mo +++ b/_examples/de_DE.utf8/LC_MESSAGES/example.mo diff --git a/examples/de_DE.utf8/example.pot b/_examples/de_DE.utf8/example.pot index 4afd100..9261267 100644 --- a/examples/de_DE.utf8/example.pot +++ b/_examples/de_DE.utf8/example.pot @@ -26,7 +26,7 @@ msgstr[1] "%d Äpfel" #: gettext_test.go:56 msgid "Good bye!" -msgstr "Aufwiedersehen!" +msgstr "Auf Wiedersehen!" #: gettext_test.go:48 msgid "Good morning" diff --git a/examples/es_MX.utf8/LC_MESSAGES/example.mo b/_examples/es_MX.utf8/LC_MESSAGES/example.mo Binary files differindex ce1ebca..ce1ebca 100644 --- a/examples/es_MX.utf8/LC_MESSAGES/example.mo +++ b/_examples/es_MX.utf8/LC_MESSAGES/example.mo diff --git a/examples/es_MX.utf8/example.pot b/_examples/es_MX.utf8/example.pot index b490af0..b490af0 100644 --- a/examples/es_MX.utf8/example.pot +++ b/_examples/es_MX.utf8/example.pot diff --git a/examples/gettext.go b/_examples/gettext.go index a01e0d5..a01e0d5 100644 --- a/examples/gettext.go +++ b/_examples/gettext.go @@ -1,31 +1,30 @@ -/* - Copyright (c) 2012 José Carlos Nieto, http://xiam.menteslibres.org/ - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - +// Copyright (c) 2012-2016 José Carlos Nieto, https://menteslibres.net/xiam +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Package gettext provides bindings for GNU Gettext. package gettext /* - #include <libintl.h> + #include <locale.h> #include <stdlib.h> */ @@ -38,127 +37,117 @@ import ( ) var ( - // For all of the locale. - LC_ALL = uint(C.LC_ALL) + // LcAll is for all of the locale. + LcAll = uint(C.LC_ALL) - // For regular expression matching (it determines the meaning of range - // expressions and equivalence classes) and string collation. - LC_COLATE = uint(C.LC_ALL) + // LcColate is for regular expression matching (it determines the meaning of + // range expressions and equivalence classes) and string collation. + LcColate = uint(C.LC_ALL) - // For regular expression matching, character classification, conversion, - // case-sensitive comparison, and wide character functions. - LC_CTYPE = uint(C.LC_CTYPE) + // LcCtype is for regular expression matching, character classification, + // conversion, case-sensitive comparison, and wide character functions. + LcCtype = uint(C.LC_CTYPE) - // For localizable natural-language messages. - LC_MESSAGES = uint(C.LC_MESSAGES) + // LcMessages is for localizable natural-language messages. + LcMessages = uint(C.LC_MESSAGES) - // For monetary formatting. - LC_MONETARY = uint(C.LC_MONETARY) + // LcMonetary is for monetary formatting. + LcMonetary = uint(C.LC_MONETARY) - // For number formatting (such as the decimal point and the thousands - // separator). - LC_NUMERIC = uint(C.LC_NUMERIC) + // LcNumeric is for number formatting (such as the decimal point and the + // thousands separator). + LcNumeric = uint(C.LC_NUMERIC) - // For time and date formatting. - LC_TIME = uint(C.LC_TIME) + // LcTime is for time and date formatting. + LcTime = uint(C.LC_TIME) ) -// Sets or queries the program's current locale. +// SetLocale sets the program's current locale. func SetLocale(category uint, locale string) string { clocale := C.CString(locale) + defer C.free(unsafe.Pointer(clocale)) - res := C.GoString(C.setlocale(C.int(category), clocale)) - - C.free(unsafe.Pointer(clocale)) - return res + return C.GoString(C.setlocale(C.int(category), clocale)) } -// Sets directory containing message catalogs. +// BindTextdomain sets the directory containing message catalogs. func BindTextdomain(domainname string, dirname string) string { cdirname := C.CString(dirname) - cdomainname := C.CString(domainname) + defer C.free(unsafe.Pointer(cdirname)) - res := C.GoString(C.bindtextdomain(cdomainname, cdirname)) + cdomainname := C.CString(domainname) + defer C.free(unsafe.Pointer(cdomainname)) - C.free(unsafe.Pointer(cdirname)) - C.free(unsafe.Pointer(cdomainname)) - return res + return C.GoString(C.bindtextdomain(cdomainname, cdirname)) } -// Sets the output codeset for message catalogs for domain domainname. +// BindTextdomainCodeset sets the output codeset for message catalogs on the +// given domainname. func BindTextdomainCodeset(domainname string, codeset string) string { cdomainname := C.CString(domainname) - ccodeset := C.CString(codeset) + defer C.free(unsafe.Pointer(cdomainname)) - res := C.GoString(C.bind_textdomain_codeset(cdomainname, ccodeset)) + ccodeset := C.CString(codeset) + defer C.free(unsafe.Pointer(ccodeset)) - C.free(unsafe.Pointer(cdomainname)) - C.free(unsafe.Pointer(ccodeset)) - return res + return C.GoString(C.bind_textdomain_codeset(cdomainname, ccodeset)) } -// Sets or retrieves the current message domain. +// Textdomain sets or retrieves the current message domain. func Textdomain(domainname string) string { cdomainname := C.CString(domainname) + defer C.free(unsafe.Pointer(cdomainname)) - res := C.GoString(C.textdomain(cdomainname)) - - C.free(unsafe.Pointer(cdomainname)) - return res + return C.GoString(C.textdomain(cdomainname)) } -// Attempt to translate a text string into the user's native language, by -// looking up the translation in a message catalog. +// Gettext attempts to translate a text string into the user's system language, +// by looking up the translation in a message catalog. func Gettext(msgid string) string { cmsgid := C.CString(msgid) + defer C.free(unsafe.Pointer(cmsgid)) - res := C.GoString(C.gettext(cmsgid)) - - C.free(unsafe.Pointer(cmsgid)) - return res + return C.GoString(C.gettext(cmsgid)) } -// Like Gettext(), but looking up the message in the specified domain. +// DGettext is like Gettext(), but looks up the message in the specified +// domain. func DGettext(domain string, msgid string) string { cdomain := cDomainName(domain) - cmsgid := C.CString(msgid) + defer C.free(unsafe.Pointer(cdomain)) - res := C.GoString(C.dgettext(cdomain, cmsgid)) + cmsgid := C.CString(msgid) + defer C.free(unsafe.Pointer(cmsgid)) - C.free(unsafe.Pointer(cdomain)) - C.free(unsafe.Pointer(cmsgid)) - return res + return C.GoString(C.dgettext(cdomain, cmsgid)) } -// Like Gettext(), but looking up the message in the specified domain and -// category. +// DCGettext is like Gettext(), but looks up the message in the specified +// domain and category. func DCGettext(domain string, msgid string, category uint) string { cdomain := cDomainName(domain) - cmsgid := C.CString(msgid) + defer C.free(unsafe.Pointer(cdomain)) - res := C.GoString(C.dcgettext(cdomain, cmsgid, C.int(category))) + cmsgid := C.CString(msgid) + defer C.free(unsafe.Pointer(cmsgid)) - C.free(unsafe.Pointer(cdomain)) - C.free(unsafe.Pointer(cmsgid)) - return res + return C.GoString(C.dcgettext(cdomain, cmsgid, C.int(category))) } -// Attempt to translate a text string into the user's native language, by -// looking up the appropriate plural form of the translation in a message -// catalog. -func NGettext(msgid string, msgid_plural string, n uint64) string { +// NGettext attempts to translate a text string into the user's system +// language, by looking up the appropriate plural form of the translation in a +// message catalog. +func NGettext(msgid string, msgidPlural string, n uint64) string { cmsgid := C.CString(msgid) - cmsgid_plural := C.CString(msgid_plural) - - res := C.GoString(C.ngettext(cmsgid, cmsgid_plural, C.ulong(n))) + defer C.free(unsafe.Pointer(cmsgid)) - C.free(unsafe.Pointer(cmsgid)) - C.free(unsafe.Pointer(cmsgid_plural)) + cmsgidPlural := C.CString(msgidPlural) + defer C.free(unsafe.Pointer(cmsgidPlural)) - return res + return C.GoString(C.ngettext(cmsgid, cmsgidPlural, C.ulong(n))) } -// Like fmt.Sprintf() but without %!(EXTRA) errors. +// Sprintf is like fmt.Sprintf() but without %!(EXTRA) errors. func Sprintf(format string, a ...interface{}) string { expects := strings.Count(format, "%") - strings.Count(format, "%%") @@ -175,35 +164,36 @@ func Sprintf(format string, a ...interface{}) string { return format } -// Like NGettext(), but looking up the message in the specified domain. -func DNGettext(domainname string, msgid string, msgid_plural string, n uint64) string { +// DNGettext is like NGettext(), but looks up the message in the specified +// domain. +func DNGettext(domainname string, msgid string, msgidPlural string, n uint64) string { cdomainname := cDomainName(domainname) cmsgid := C.CString(msgid) - cmsgid_plural := C.CString(msgid_plural) + cmsgidPlural := C.CString(msgidPlural) - res := C.GoString(C.dngettext(cdomainname, cmsgid, cmsgid_plural, C.ulong(n))) + defer func() { + C.free(unsafe.Pointer(cdomainname)) + C.free(unsafe.Pointer(cmsgid)) + C.free(unsafe.Pointer(cmsgidPlural)) + }() - C.free(unsafe.Pointer(cdomainname)) - C.free(unsafe.Pointer(cmsgid)) - C.free(unsafe.Pointer(cmsgid_plural)) - - return res + return C.GoString(C.dngettext(cdomainname, cmsgid, cmsgidPlural, C.ulong(n))) } -// Like NGettext(), but looking up the message in the specified domain and -// category. -func DCNGettext(domainname string, msgid string, msgid_plural string, n uint64, category uint) string { +// DCNGettext is like NGettext(), but looks up the message in the specified +// domain and category. +func DCNGettext(domainname string, msgid string, msgidPlural string, n uint64, category uint) string { cdomainname := cDomainName(domainname) cmsgid := C.CString(msgid) - cmsgid_plural := C.CString(msgid_plural) - - res := C.GoString(C.dcngettext(cdomainname, cmsgid, cmsgid_plural, C.ulong(n), C.int(category))) + cmsgidPlural := C.CString(msgidPlural) - C.free(unsafe.Pointer(cdomainname)) - C.free(unsafe.Pointer(cmsgid)) - C.free(unsafe.Pointer(cmsgid_plural)) + defer func() { + C.free(unsafe.Pointer(cdomainname)) + C.free(unsafe.Pointer(cmsgid)) + C.free(unsafe.Pointer(cmsgidPlural)) + }() - return res + return C.GoString(C.dcngettext(cdomainname, cmsgid, cmsgidPlural, C.ulong(n), C.int(category))) } // cDomainName returns the domain name CString that can be nil. @@ -211,5 +201,6 @@ func cDomainName(domain string) *C.char { if domain == "" { return nil } + // The caller is responsible for freeing this up. return C.CString(domain) } diff --git a/gettext_test.go b/gettext_test.go index 8e88180..6c58ab3 100644 --- a/gettext_test.go +++ b/gettext_test.go @@ -1,163 +1,62 @@ package gettext import ( - "fmt" - "os" "testing" -) - -/* - NOTE: - - xgettext does not officially support Go syntax, however, you can generate a valid .pot file by forcing - xgettest to use the C++ syntax: - - % xgettext -d example -s gettext_test.go -o example.pot -L c++ -i --keyword=NGettext:1,2 --keyword=Gettext - - This will generate a example.pot file. - - After translating the .pot file, you must generate .po and .mo files. - - Remember to set the UTF-8 charset. - - % msginit -l es_MX -o example.po -i example.pot - % msgfmt -c -v -o example.mo example.po - - And finally, move the .mo file to an appropriate location. - - % mv example.mo examples/es_MX.utf8/LC_MESSAGES/example.mo - -*/ - -func TestSpanishMexico(t *testing.T) { - - os.Setenv("LANGUAGE", "es_MX.utf8") - - SetLocale(LC_ALL, "") - BindTextdomain("example", "./examples/") - Textdomain("example") - - t1 := Gettext("Hello, world!") - - fmt.Println(t1) - - if t1 != "¡Hola mundo!" { - t.Errorf("Failed translation.") - } - - t2 := Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage") - - fmt.Println(t2) - - if t2 != "Una manzana" { - t.Errorf("Failed translation.") - } - t3 := Sprintf(NGettext("An apple", "%d apples", 3), 3) + "github.com/stretchr/testify/assert" +) - fmt.Println(t3) +const ( + spanishMexico = "es_MX.utf8" + deutschDeutschland = "de_DE.utf8" + frenchFrance = "fr_FR.utf8" +) - if t3 != "3 manzanas" { - t.Errorf("Failed translation.") - } +func TestSpanish(t *testing.T) { + SetLocale(LcAll, spanishMexico) - t4 := Gettext("Good morning") + textDomain := "example" - fmt.Println(t4) + BindTextdomain(textDomain, "_examples/") + Textdomain(textDomain) - if t4 != "Buenos días" { - t.Errorf("Failed translation.") - } + assert.Equal(t, "¡Hola mundo!", Gettext("Hello, world!")) - t5 := Gettext("Good bye!") + assert.Equal(t, "Una manzana", Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage")) - fmt.Println(t5) + assert.Equal(t, "3 manzanas", Sprintf(NGettext("An apple", "%d apples", 3), 3)) - if t5 != "¡Hasta luego!" { - t.Errorf("Failed translation.") - } + assert.Equal(t, "Buenos días", Gettext("Good morning")) + assert.Equal(t, "¡Hasta luego!", Gettext("Good bye!")) } -func TestGermanDeutschland(t *testing.T) { - - os.Setenv("LANGUAGE", "de_DE.utf8") - - SetLocale(LC_ALL, "") - BindTextdomain("example", "./examples/") - Textdomain("example") - - t1 := Gettext("Hello, world!") - - fmt.Println(t1) - - if t1 != "Hallo, Welt!" { - t.Errorf("Failed translation.") - } - - t2 := Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage") - - fmt.Println(t2) - - if t2 != "Ein Apfel" { - t.Errorf("Failed translation.") - } +func TestDeutsch(t *testing.T) { + SetLocale(LcAll, deutschDeutschland) - t3 := Sprintf(NGettext("An apple", "%d apples", 3), 3) + assert.Equal(t, "Hallo, Welt!", Gettext("Hello, world!")) - fmt.Println(t3) + assert.Equal(t, "Ein Apfel", Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage")) - if t3 != "3 Äpfel" { - t.Errorf("Failed translation.") - } + assert.Equal(t, "3 Äpfel", Sprintf(NGettext("An apple", "%d apples", 3), 3)) - t4 := Gettext("Good morning") - - fmt.Println(t4) - - if t4 != "Guten morgen" { - t.Errorf("Failed translation.") - } - - t5 := Gettext("Good bye!") - - fmt.Println(t5) - - if t5 != "Aufwiedersehen!" { - t.Errorf("Failed translation.") - } + assert.Equal(t, "Guten morgen", Gettext("Good morning")) + assert.Equal(t, "Auf Wiedersehen!", Gettext("Good bye!")) } -func TestDGettextFallback(t *testing.T) { - os.Setenv("LANGUAGE", "de_DE.utf8") - - SetLocale(LC_ALL, "") - BindTextdomain("example", "./examples/") - Textdomain("example") - - t1 := DGettext("", "Hello, world!") - - fmt.Println(t1) - - if t1 != "Hallo, Welt!" { - t.Errorf("Failed translation fallback.") - } - - t2 := Sprintf(DNGettext("", "An apple", "%d apples", 1), 1, "garbage") +func TestFrench(t *testing.T) { + // Note that we don't have a french translation. - fmt.Println(t2) + SetLocale(LcAll, frenchFrance) - if t2 != "Ein Apfel" { - t.Errorf("Failed translation fallback.") - } + assert.Equal(t, "Hello, world!", Gettext("Hello, world!")) - t3 := Sprintf(DNGettext("", "An apple", "%d apples", 3), 3) + assert.Equal(t, "An apple", Sprintf(NGettext("An apple", "%d apples", 1), 1, "garbage")) - fmt.Println(t3) + assert.Equal(t, "3 apples", Sprintf(NGettext("An apple", "%d apples", 3), 3)) - if t3 != "3 Äpfel" { - t.Errorf("Failed translation fallback.") - } + assert.Equal(t, "Good morning", Gettext("Good morning")) + assert.Equal(t, "Good bye!", Gettext("Good bye!")) } |