i18n Golang Web Application by gettext and html/template


GNU gettext tools are great for creating i18n (web) applications. In this post, assume that PO and MO files generated by gettext are ready. We will use the PO and MO files to let Go (web) applications to render HTML of specific languages by html/template. If you do not know what PO or MO files are, or you do not know how to create them by gettext tools, please read my previous post [1] and [2] first.

PO and MO files

I will show how to use MO and PO files by example. In the example, we support two locale, zh_TW (Traditional Chinese) and vi_VN (Vietnamese). The zh_TW PO file are located at locale/zh_TW/LC_MESSAGES/messages.po and vi_VN PO file are located at locale/vi_VN/LC_MESSAGES/messages.po.

zh_TW PO file locale/zh_TW/LC_MESSAGES/messages.po:

messages.po | 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
# Chinese translations for PACKAGE package.
# Copyright (C) 2013 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Automatically generated, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-04 10:20+0800\n"
"PO-Revision-Date: 2013-03-10 05:19+0800\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

msgid "Home"
msgstr "首頁"

msgid "Canon"
msgstr "經典"

msgid "About"
msgstr "關於"

msgid "Setting"
msgstr "設定"

msgid "Translation"
msgstr "翻譯"

vi_VN PO file locale/vi_VN/LC_MESSAGES/messages.po:

messages.po | 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
# Vietnamese translations for PACKAGE package.
# Copyright (C) 2013 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Automatically generated, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-06-06 23:05+0800\n"
"PO-Revision-Date: 2013-06-06 22:50+0800\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: vi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"

msgid "Home"
msgstr "Trang chính"

msgid "Canon"
msgstr "Kinh điển"

msgid "About"
msgstr "Giới thiệu"

msgid "Setting"
msgstr "Thiết lập"

msgid "Translation"
msgstr "Dịch"

Generate corresponding MO files by msgfmt. Put MO files together with PO files. So we have two PO files and two MO files:

locale/zh_TW/LC_MESSAGES/messages.po
locale/zh_TW/LC_MESSAGES/messages.mo
locale/vi_VN/LC_MESSAGES/messages.po
locale/vi_VN/LC_MESSAGES/messages.mo

Source Code

Next, we need the gettext-go package to handle the PO and MO files for us. Install gettext-go by:

$ go get github.com/chai2010/gettext-go/gettext

Now we can let Go application speak local language:

html.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
package main

import (
	"github.com/chai2010/gettext-go/gettext"
	"html/template"
	"os"
)

func setup(locale string, domain string, dir string) {
	gettext.SetLocale(locale)
	gettext.Textdomain(domain)

	gettext.BindTextdomain(domain, dir, nil)
}

func changeLocale(locale string) {
	gettext.SetLocale(locale)
}

func translate(input string) string {
	return gettext.PGettext("", input)
}

const tmpl = `
<span>{{gettext "Home"}}</span>
<span>{{gettext "Canon"}}</span>
<span>{{gettext "About"}}</span>
<span>{{gettext "Setting"}}</span>
<span>{{gettext "Translation"}}</span>
`

func main() {
	setup("zh_TW", "messages", "locale")
	setup("vi_VN", "messages", "locale")
	funcMap := template.FuncMap{
		"gettext": translate,
	}

	t, _ := template.New("foo").Funcs(funcMap).Parse(tmpl)
	changeLocale("zh_TW")
	t.Execute(os.Stdout, nil)
	changeLocale("vi_VN")
	t.Execute(os.Stdout, nil)
}

Note that xgettext cannot extract translatable strings directly from {{gettext "Home"}}. We can use the method similar to [4] by using sed to handle input files first and then feed the output to xgettext to extract translatable strings (read [7] for further details):

sed "s/{{gettext \(".*"\)}}/{{gettext(\1)}}/g" html.go | xgettext --no-wrap --language=c --from-code=UTF-8 --output=locale/messages.pot -

Output of Above Code

<span>首頁</span>
<span>經典</span>
<span>關於</span>
<span>設定</span>
<span>翻譯</span>

<span>Trang chính</span>
<span>Kinh điển</span>
<span>Giới thiệu</span>
<span>Thiết lập</span>
<span>Dịch</span>

Tested on: Ubuntu Linux 15.10, Go 1.5.3.


References:

[1]Internationalization (i18n) of Web Application by GNU gettext Tools
[2][Golang] Internationalization (i18n) of Go Application by GNU gettext Tools
[3]xgettext example
[4]php - Let xgettext find keywords in comments - Stack Overflow
[5]gmarty/xgettext · GitHub (Extract translatable strings from Handlebars templates.)
[6]arendjr/grunt-xgettext: Grunt xgettext plugin for JavaScript and Handlebars
[7]xgettext Extract Translatable Strings From Golang html/template