Skip to content
/ pogo Public

po/mo-compliant i18n with gettext-ish functionality for go

License

Notifications You must be signed in to change notification settings

Sam-Izdat/pogo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pogo

...provides gettext-like internationalization for golang

Why?

...because most of the major, recurrent challenges in internationalizing software have been addressed by GNU almost twenty years ago and golang is badly lacking in this functionality. Gettext enjoys extremely wide adoption, established toolchains and great popularity among both translators and programmers. It's simple. It provides for better separation of localization from the rest of your architecture, allows non-programmers to translate your software, eliminates ugly, unmanageable JSON files and, when used properly, effectively solves the problems of context and grammatical differences in pluralization. Until something better comes along, it just doesn't make much sense to reinvent the square wheel. This project is meant to be a loose interpretation replicating the most useful functionality and making use of common po/mo standards.

Features

  • i18n in your templates, not crammed into them as an afterthought
  • Simple CLI package to extract strings in lieu of xgettext
  • Built-in support for pluralization rules, from Alcholi to Yoruba
  • Built-in support for context fields

Install

$ go get github.com/Sam-Izdat/pogo

Status

It's still very raw. A pre-release is available. Contributions and bug reports are most welcome.

License MIT GoDoc

Getting started

If your GOPATH and GOBIN environment variables were set correctly, you should now be able to run pogo from any directory. This little CLI package will scan your project for calls pogo's gettext-ish functions and compile string literals into po files. The first thing is to initialize it in the main project directory and set up a configuration file.

$ cd github.com/MyStuff/MyProject
$ pogo init

Now, edit the top few settings in the generated POGO.toml file, describing the translation project and providing a list of target locales. You can specify an arbitrary name and base filename for the set of catalogs (.pot/.po files) covered by this pogo project. For example:

targets             = ["en_US", "en_GB", "ja"]
project_name        = "My Application User Control Panel"
project_filename    = "my_app_user_cp"

or

targets             = ["en_US", "en_GB", "ja"]
project_name        = "My Application Administrator Control Panel"
project_filename    = "my_app_admin_cp"

That's it. There are no domains, as such. When pogo walks the directory it'll search until it bumps into another POGO.toml file somewhere; if it does, that subdirectory will be ignored and left to another configuration and collection of catalogs. If needed, an application's translation files can be compartmentalized by packages, or just by separate directories of views.

Putting it to use

Here's a basic webserver that can be found in the example folder.

package main
    
import (
    "fmt"
    "net/http"
    "html/template"
    "path/filepath"
    "github.com/Sam-Izdat/pogo/translate"
)

// Load your POGO.toml configuration file *before* processing the request
var POGO = translate.LoadCfg("github.com/MyStuff/MyProject")

func handler(w http.ResponseWriter, r *http.Request) {
    // Grab a translator in your request flow
    var T = POGO.New("ru") // "ru" is Russian

    // Set up some data for the template
    var bottles []int
    for i := 99; i >= 0; i-- { bottles = append(bottles, i) }

    data := struct {
        T translate.Translator  // Throw a translator at the template
        Title string
        Bottles []int
    } {
        T,                      // The Russian translator above
        T.G("Internationalization Example"), // Translate outside template
        bottles,
    }

    lp := filepath.Join("views", "layout.html")
    fp := filepath.Join("views", "index.html")
    templates := template.Must(template.ParseFiles(lp, fp))
    templates.ExecuteTemplate(w, "layout", data)
}

func main() {
    // Point to a handler and serve
    port := ":8383"
    fmt.Println("Serving on port", port)
    http.HandleFunc("/", handler)
    http.ListenAndServe(port, nil)
}

Translating strings

By default, a pogo "translator" exports four methods called:

  • G() - for basic translation (roughly equivalent to gettext() or _())
  • PG() - for translation with context (pgettext())
  • NG()- for translation with quantities (ngettext())
  • NPG() - for translation with both quantities and context (npgettext())

It will be expected by the CLI program that the above are reserved for pogo in .go files (as function and method names) and in templates (as function, method and variable names). If you would like something more verbose with less chance conflict/collision, just alias these methods and edit the POGO.toml file for the scanner, to reflect the new names.

String literals can be queued up for translation directly in your go files but, chances are, most of the content to be translated will reside in templates. Passing the translator to a template as above now lets you do this:

G - just translate

{{.T.G "A robot?!"}}

All of these functions are variadic and will take an arbitrary number of arguments. G() will pass along all subsequent arguments to fmt.Sprintf(), often eliminating the need to nest it in another function:

{{.T.G "I seem to have lost %s" "my shoes"}}

The words "my shoes" above will be translated if a translation is available.

PG - context

PG() allows you to provide a context for the translator, which is passed as the first argument:

{{.T.PG "childrens' game" "tag"}}
{{.T.PG "category or identifier" "tag"}}
{{.T.PG "the price on an item" "tag"}}

NG - plurals

NG() is for anything that may vary in quantity:

{{.T.NG "You have %d new message" "You have %d new messages" .ctMsg}}

The first argument must be the singular form of the string to be translated; the second must be the plural. The very last argument must be the quantity of the thing, which will be used to pull up a locale-specific pluralization rule. Anything in between will be interpolated.

{{.T.NG "I ate %[2]d muffin %[1]s" "I ate %[2]d muffins %[1]s" "yesterday" .ctMuffins}}

NPG - plurals & context

NPG() takes at least four arguments, combining the purposes of PG() and NG():

{{.T.NPG "sternly!" "Delete %d file?" "Delete %d files?" .ctFiles}}

The first argument is context, then singular, then plural, then zero or more other arguments, then the quantity.

Use a sigil ($) to access T while ranging over something or within conditional statements:

{{range .Bottles}}
<p>{{$.T.NG "%[2]d bottle of beer %[1]s!" "%[2]d bottles of beer %[1]s!" "on the wall" (.)}}</p>
{{end}}

There's really not much more to it.

Mos, pos and pots and other things

Now that there's some stuff to be translated you can compile the po files. One file with the extension "pot" will serve as the original template and every locale will have its own "po" file (catalog) containing the actual translations. Editors like Poedit can merge these catalogs with any new messages added to the template.

$ cd github.com/MyStuff/MyProject
$ mkdir locale
$ pogo build pot

...will compile the template...

$ pogo build po

...will produce individual po files for all your targets with some meta-data already in place. Whenever you have new strings to translate, just run pogo build -o pot again. It does roughly what xgettext does. Currently pogo does not compile mo files and leaves that up the fancy editors.

On the to-do list

  • Unit tests
  • Better documentation
  • General code cleanup
  • "pogo build mo" CLI command
  • "pogo status" CLI command
  • HTML UI stats, previews

Attributions

built upon:

License

MIT for pogo; see dependencies' docs for their respective licenses

About

po/mo-compliant i18n with gettext-ish functionality for go

Resources

License

Stars

Watchers

Forks

Packages

No packages published