Reputation: 7654
Not necessarily specific to GAE I suppose, but I'm curious as to what people are using to translate or localise their web applications.
My own approach I'm afraid is hopelessly naive, really just a hand-wave at the issue by loading an entity from the datastore for each package based on a locale value recorded in the user's profile. At least this allows translations of a few strings to be provided:
package foo
...
type Messages struct {
Locale string
ErrorDatastore string
LoginSuccessful string
...
}
Store with a string id corresponding to a locale, then load to Gorilla context or similar:
const Messages ContextKey = iota
...
k := datastore.NewKey(c, "Messages", "en_US", 0, nil)
m := new(Messages)
if err := datastore.Get(c, k, m); err != nil {
...
} else {
context.Set(r, Messages, m)
}
Which is obviously incredibly limited, but at least makes strings available from calling code via context.Get(r, foo.Messages). Can anyone point me at more useful implementations, or suggest a better approach?
Edit (relevant but not completely useful):
Upvotes: 21
Views: 5930
Reputation: 298
GNU Gettext is widely adopted as a de facto standard for i18n solutions.
To use .po files directly from your Go project and load all translations in memory for better performance, you can use my package: https://github.com/leonelquinteros/gotext
It's fairly simple and directly to the point.
So, given a default.po file (formatted after GNU gettext: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) located in /path/to/locales/es_ES/default.po
you can load it using this package and start consuming the translations right away:
import "github.com/leonelquinteros/gotext"
func main() {
// Configure package
gotext.SetLibrary("/path/to/locales")
gotext.SetLanguage("es_ES")
// Translate text from default domain
println(gotext.Get("Translate this text"))
}
If you prefer to have the translations defined in a string for a more "focused" use, you can parse a PO formatted string with a Po object:
import "github.com/leonelquinteros/gotext"
func main() {
// Set PO content
str := `
msgid "One apple"
msgstr "Una manzana"
msgid "One orange"
msgstr "Una naranja"
msgid "My name is %s"
msgstr "Mi nombre es %s"
`
// Create Po object
po := new(Po)
po.Parse(str)
// Get a translated string
println(po.Get("One orange"))
// Get a translated string using variables inside the translation
name := "Tom"
println(po.Get("My name is %s", name))
}
As you can see on the last example, it's also possible to use variables inside the translation strings.
While most solutions are pretty much similar, including yours, using a common format as gettext can bring some extra benefits.
Also, your solution doesn't seems to be safe for concurrent use (when consumed from several goroutines). This package handles all that for you. There are also unit tests for the package and contributions are welcome.
Upvotes: 1
Reputation: 2966
go-i18n is an alternative package with some nice features:
Upvotes: 4
Reputation: 7654
Jonathan Chan points out Samuel Stauffer's go-gettext which seems to do the trick. Given the directories:
~appname/
|~app/
| `-app.go
|+github.com/
`-app.yaml
Start with (assumes *nix):
$ cd appname
$ git clone git://github.com/samuel/go-gettext.git github.com/samuel/go-gettext
Source preparation cannot use the _("String to be translated") short form, due to underscore's special characteristics in Go. You can tell xgettext to look for the camelcase function name "GetText" using the -k flag.
Minimal working example:
package app
import (
"fmt"
"log"
"net/http"
"github.com/samuel/go-gettext"
)
func init () {
http.HandleFunc("/", home)
}
func home(w http.ResponseWriter, r *http.Request) {
d, err := gettext.NewDomain("appname", "locale")
if err != nil {
log.Fatal("Failed at NewDomain.")
}
cat := d.GetCatalog("fr_FR")
if cat == gettext.NullCatalog {
log.Fatal("Failed at GetCatalog.")
}
fmt.Fprintf(w, cat.GetText("Yes."))
}
Create the template with:
$ xgettext -d appname -kGetText -s -o appname.pot app/app.go
Note -k, without it there'll be no output as xgettext won't recognise calls to GetText. Edit relevant strings, email etc in appname.pot. Let's assume we're localising for French:
$ mkdir -p locale/fr_FR/LC_MESSAGES
$ msginit -l fr_FR -o french.po -i appname.pot
Edit french.po:
# Appname l10n
# Copyright (C) 2013 Wombat Inc
# This file is distributed under the same license as the appname package.
# Wombat <[email protected]>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: appname v0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-01-13 11:03+1300\n"
"PO-Revision-Date: 2013-01-13 11:10+1300\n"
"Last-Translator: Rich <[email protected]>\n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: app/app.go:15
msgid "Yes."
msgstr "Oui."
Generate the binary (the file that'll actually get deployed with the app):
$ msgfmt -c -v -o locale/fr_FR/LC_MESSAGES/appname.mo french.po
Final directory structure:
~appname/
|~app/
| `-app.go
|~github.com/
| `~samuel/
| `~go-gettext/
| +locale/
| |-catalog.go
| |-domain.go
| `-mo.go
|~locale/
| `~fr_FR/
| `LC_MESSAGES/
| `-appname.mo
`-app.yaml
(locale directory under go-gettext holds test data, could be removed for deployment.)
If all goes well, a visit to appname should display "Oui."
Upvotes: 9