Подтвердить что ты не робот

Стратегии I18n для Go с App Engine

Не обязательно специфический для GAE, я полагаю, но мне любопытно, что люди используют для перевода или локализации своих веб-приложений.

Мой собственный подход. Я боюсь, это безнадежно наивный, на самом деле просто ручная волна в этом вопросе, загружая сущность из хранилища данных для каждого пакета на основе значения локали, записанного в профиле пользователя. По крайней мере, это позволяет выполнять переводы нескольких строк:

package foo

...

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

Сохранить с идентификатором строки, соответствующим языку, затем загрузить в контекст 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). Может ли кто-нибудь указать мне на более полезные реализации или предложить лучший подход?

Изменить (актуально, но не полностью полезно):

4b9b3361

Ответ 1

Джонатан Чан указывает Самуэль Штауффер 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

Подготовка источника не может использовать короткую форму _ ( "Строка для перевода" ), из-за подчеркивания специальных характеристик в Go. Вы можете указать xgettext искать имя функции camelcase "GetText", используя флаг -k.

Минимальный рабочий пример:

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 <[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."

Создайте двоичный файл (файл, который будет фактически развернут с помощью приложения):

$ 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 содержит тестовые данные, может быть удален для развертывания.)

Если все пойдет хорошо, посещение appname должно отображать "Oui".

Ответ 2

go-i18n - альтернативный пакет с некоторыми приятными функциями:

Ответ 3

GNU Gettext широко используется как стандарт де-факто для решений i18n.

Чтобы использовать файлы .po непосредственно из проекта Go и загружать все переводы в память для лучшей производительности, вы можете использовать мой пакет: https://github.com/leonelquinteros/gotext

Это довольно просто и прямо к делу.

Итак, учитывая файл default.po(отформатированный после GNU gettext: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html), расположенный в /path/to/locales/es_ES/default.po, вы можете загрузить его, используя этот пакет и начните использовать перевод сразу:

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). Этот пакет обрабатывает все, что для вас. Существуют также модульные тесты для пакета, и приветствуются вклады.