适用于App Engine的I18n策略

时间:2013-01-02 15:31:42

标签: google-app-engine localization internationalization go

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

我自己的方法恐怕绝对是天真的,实际上只是根据用户配置文件中记录的区域设置值从每个包的数据存储区加载实体来解决问题。至少这允许提供几个字符串的翻译:

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)调用代码来使字符串可用。任何人都可以指出我更有用的实现,或建议更好的方法吗?

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

3 个答案:

答案 0 :(得分:9)

Jonathan Chan指出Samuel Stauffer's go-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

源代码准备不能使用_(“要翻译的字符串”)简短形式due to underscore's special characteristics in Go。您可以告诉xgettext使用-k标志查找camelcase函数名“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。”

答案 1 :(得分:3)

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

答案 2 :(得分:-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() {
    // Configure package
    gotext.SetLibrary("/path/to/locales")
    gotext.SetLanguage("es_ES")

    // Translate text from default domain
    println(gotext.Get("Translate this text"))
}

如果您希望在字符串中定义翻译以实现更“专注”的使用,则可以使用Po对象解析PO格式的字符串:

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

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

虽然大多数解决方案都非常相似,包括你的解决方案,但使用通用格式作为gettext可以带来一些额外的好处。

此外,您的解决方案对于并发使用似乎并不安全(从多个goroutines使用时)。这个包处理所有这些。还有包装的单元测试,欢迎贡献。