[Golang] gettext Function on Frontend (Browser) by GopherJS


Introduction

Implement gettext function on front-end (browsers) by Go and GopherJS, which compiles Golang program to JavaScript. The gettext function translates a text string into the user's native language. Before you start, you need to prepare PO files (see [4], [5], [6]) and convert the PO files to JSON format (see [7]). The translations in JSON format will be used by our gettext function.

Install GopherJS

Run the following command to install GopherJS:

$ go get -u github.com/gopherjs/gopherjs

Source Code

HTML file for demo:

index.html | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<!doctype html>
<html>
<head>
  <title>Golang gettext on front-end (browser)</title>
</head>
<body>

<button type="button" value="vi_VN">Vietnamese</button>
<button type="button" value="zh_TW">Traditional Chinese</button>
<button type="button" value="en">English</button>

<div data-default-string="Home">Home</div>
<div data-default-string="Canon">Canon</div>
<div data-default-string="About">About</div>
<div data-default-string="Setting">Setting</div>
<div data-default-string="Translation">Translation</div>

<script src="demo.js"></script>
</body>
</html>

In our HTML file, we wrap a text string to be translated in div element, and set the data-default-string attribute of div element to the text string. For example, Home is a text string to be translated, it will be wrapped as:

<div data-default-string="Home">Home</div>

You can also wrap a text string in span element or other elements instead of div.

jsgettext-raw.go | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import "github.com/gopherjs/gopherjs/js"
import "encoding/json"
import "strings"

const jsondata = `{"vi_VN":{"About":"Giới thiệu","Canon":"Kinh điển","Home":"Trang chính","Setting":"Thiết lập","Translation":"Dịch"},"zh_TW":{"About":"關於","Canon":"經典","Home":"首頁","Setting":"設定","Translation":"翻譯"}}`

type msgIdStrPairs map[string]string
type localesMsg map[string]msgIdStrPairs

var msg = localesMsg{}

func setupJSON() {
	dec := json.NewDecoder(strings.NewReader(jsondata))
	if err := dec.Decode(&msg); err != nil {
		panic(err)
	}
}

func gettext(locale, str string) string {
	if val, ok := msg[locale]; ok {
		if val2, ok2 := val[str]; ok2 {
			return val2
		} else {
			return str
		}
	} else {
		return str
	}
}

func translate(locale string) {
	d := js.Global.Get("document")
	nodeList := d.Call("querySelectorAll", "[data-default-string]")
	length := nodeList.Get("length").Int()
	for i := 0; i < length; i++ {
		element := nodeList.Call("item", i)
		str := element.Get("dataset").Get("defaultString").String()
		element.Set("textContent", gettext(locale, str))
	}
}

func main() {
	setupJSON()

	d := js.Global.Get("document")
	nodeList := d.Call("querySelectorAll", "button")
	length := nodeList.Get("length").Int()
	for i := 0; i < length; i++ {
		btn := nodeList.Call("item", i)
		btn.Call("addEventListener", "click", func() {
			translate(btn.Get("value").String())
		})
	}
}

We put translations converted from PO files in the source code. The translations are used by gettext function to translate a text string.

When a user clicks the language button, we will select the div elements with data-default-string attribute. The string in the attribute will be translated by gettext function and replace the original string in the div.

Compile the Go code to JavaScript by:

$ gopherjs build jsgettext-raw.go -o demo.js

Put demo.js together with the index.html in the same directory. Open the index.html with your browser. Click language buttons to translate strings.


Appendix

If you want to use GopherJS bindings for the JavaScript DOM APIs to write idiomatic Go code, install DOM binding by:

$ go get -u honnef.co/go/js/dom

And compile the following code to JavaScript instead of above:

jsgettext.go | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package main

import "honnef.co/go/js/dom"
import "encoding/json"
import "strings"

const jsondata = `{"vi_VN":{"About":"Giới thiệu","Canon":"Kinh điển","Home":"Trang chính","Setting":"Thiết lập","Translation":"Dịch"},"zh_TW":{"About":"關於","Canon":"經典","Home":"首頁","Setting":"設定","Translation":"翻譯"}}`

type msgIdStrPairs map[string]string
type localesMsg map[string]msgIdStrPairs

var msg = localesMsg{}

func setupJSON() {
	dec := json.NewDecoder(strings.NewReader(jsondata))
	if err := dec.Decode(&msg); err != nil {
		panic(err)
	}
}

func gettext(locale, str string) string {
	if val, ok := msg[locale]; ok {
		if val2, ok2 := val[str]; ok2 {
			return val2
		} else {
			return str
		}
	} else {
		return str
	}
}

func translate(value string) {
	d := dom.GetWindow().Document()
	elements := d.QuerySelectorAll("[data-default-string]")
	for _, element := range elements {
		elm := element.(*dom.HTMLDivElement)
		elm.SetTextContent(gettext(value, elm.Dataset()["defaultString"]))
	}
}

func main() {
	setupJSON()

	d := dom.GetWindow().Document()
	buttons := d.QuerySelectorAll("button")

	for _, btn := range buttons {
		elm := btn.(*dom.HTMLButtonElement)
		elm.AddEventListener("click", false, func(event dom.Event) {
			translate(elm.Value)
		})
	}
}

Note that if you wrap strings in span or other elements instead of div, remember to modify the above code accordingly.


Tested on: Ubuntu Linux 15.10, Go 1.5.3, Chromium Version 48.0.2564.82 Ubuntu 15.10 (64-bit).


References:

[1]GopherJS - A compiler from Go to JavaScript (GitHub, GopherJS Playground, godoc)
[2]Bindings · gopherjs/gopherjs Wiki · GitHub
[3]dom - GopherJS bindings for the JavaScript DOM APIs (GitHub)
[4]Internationalization (i18n) of Web Application by GNU gettext Tools
[5]i18n Golang Web Application by gettext and html/template
[6]xgettext Extract Translatable Strings From Golang html/template
[7][Golang] Convert PO file to JSON Format
[8]queryselector
[9]Document.querySelector() - Web APIs | MDN
[10]HTML DOM querySelector() Method
[11]CSS Selectors Reference
[12]queryselector attribute selector
[13]javascript - How to use querySelectorAll only for elements that have a specific attribute set? - Stack Overflow
[14]javascript - document.querySelector multiple data-attributes in one element - Stack Overflow
[15]json - The Go Programming Language
[16]golang map key exists
[17]dictionary - How to check if a map contains a key in go? - Stack Overflow
[18]javascript gettext
[19]Jed | Gettext Style i18n for Modern JavaScript Apps
[20]javascript - .po files and gettext VS JSON and custom i18n library? - Stack Overflow
[21]javascript gettext frontend
[22]gettext - How to split frontend and backend translations? - Stack Overflow