I18n在Go语言中与App Engine的策略

huangapple go评论83阅读模式
英文:

I18n strategies for Go with App Engine

问题

不一定特定于GAE,但我很好奇人们用什么来翻译或本地化他们的Web应用程序。

我自己的方法恐怕太天真了,实际上只是通过从数据存储加载一个实体来解决这个问题,该实体基于用户配置文件中记录的区域设置值。至少这样可以提供一些字符串的翻译:

package foo

...

type Messages struct {
    Locale string
    ErrorDatastore string
    LoginSuccessful string
    ...
}

使用与区域设置对应的字符串ID进行存储,然后加载到Gorilla上下文或类似的上下文中:

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)
}

这显然非常有限,但至少通过context.Get(r, foo.Messages)使调用代码中的字符串可用。有人可以指导我更有用的实现,或者提出更好的方法吗?

编辑(相关但不完全有用):

英文:

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):

答案1

得分: 9

Jonathan Chan指出Samuel Stauffer'sgo-gettext似乎可以解决问题。给定的目录结构如下:

~appname/
 |~app/
 | `-app.go
 |+github.com/
 `-app.yaml

开始操作(假设是*nix系统):

$ cd appname
$ git clone git://github.com/samuel/go-gettext.git github.com/samuel/go-gettext

由于Go语言中下划线的特殊性,源代码准备阶段不能使用_("String to be translated")的简写形式。可以使用-k标志告诉xgettext查找驼峰命名的函数名"GetText"。

最小工作示例:

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."))
}

使用以下命令创建模板:

$ xgettext -d appname -kGetText -s -o appname.pot app/app.go

注意-k标志,如果没有它,xgettext将无法识别对GetText的调用,因此不会有输出。在appname.pot文件中编辑相关字符串、电子邮件等。假设我们要本地化为法语:

$ mkdir -p locale/fr_FR/LC_MESSAGES
$ msginit -l fr_FR -o french.po -i appname.pot

编辑french.po文件:

# Appname l10n
# Copyright (C) 2013 Wombat Inc
# This file is distributed under the same license as the appname package.
# Wombat <wombat@example.com>, 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 <rich@example.com>\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."

生成二进制文件(实际上将与应用程序一起部署的文件):

$ msgfmt -c -v -o locale/fr_FR/LC_MESSAGES/appname.mo french.po

最终的目录结构如下:

~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

(go-gettext目录下的locale目录保存了测试数据,可以在部署时删除。)

如果一切顺利,访问appname应该显示"Oui."

英文:

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 <wombat@example.com>, 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 <rich@example.com>\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."

答案2

得分: 4

go-i18n 是一个具有一些不错特性的替代包:

英文:

go-i18n is an alternative package with some nice features:

答案3

得分: 1

GNU Gettext被广泛采用作为i18n解决方案的事实标准。

要直接从您的Go项目中使用.po文件,并将所有翻译加载到内存中以提高性能,您可以使用我的包:https://github.com/leonelquinteros/gotext

这很简单直接。

因此,假设有一个位于/path/to/locales/es_ES/default.po的default.po文件(按照GNU gettext的格式:https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html),您可以使用此包加载它并立即开始使用翻译:

import "github.com/leonelquinteros/gotext"

func main() {
    // 配置包
    gotext.SetLibrary("/path/to/locales")
    gotext.SetLanguage("es_ES")

    // 从默认域翻译文本
    println(gotext.Get("Translate this text"))
}

如果您更喜欢将翻译定义为字符串以进行更“专注”的使用,您可以使用Po对象解析一个PO格式的字符串:

import "github.com/leonelquinteros/gotext"

func main() {
    // 设置PO内容
    str := `
msgid "One apple"
msgstr "Una manzana"

msgid "One orange"
msgstr "Una naranja"

msgid "My name is %s"
msgstr "Mi nombre es %s"
`

    // 创建Po对象
    po := new(Po)
    po.Parse(str)
    
    // 获取翻译后的字符串
    println(po.Get("One orange"))

    // 使用翻译中的变量获取翻译后的字符串
    name := "Tom"
    println(po.Get("My name is %s", name))
}

正如您在最后一个示例中所看到的,也可以在翻译字符串中使用变量。

尽管大多数解决方案都非常相似,包括您的解决方案,但使用gettext等常见格式可以带来一些额外的好处。

此外,您的解决方案似乎不适用于并发使用(从多个goroutine中使用时)。此包为您处理所有这些。该包还包含单元测试,并欢迎贡献。

英文:

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.

huangapple
  • 本文由 发表于 2013年1月2日 23:31:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/14124630.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定