i18n Python Web Application by gettext and Jinja2


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 Python (web) applications render HTML of specific languages by Jinja2 template engine. 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 [9] 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

Now we can let Python (web) application speak local language. The application-independent code is put in the following module:

i18n.py | 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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

"""
References:
http://docs.python.org/2/library/gettext.html
http://jinja.pocoo.org/docs/extensions/
http://webpy.org/cookbook/i18n_support_in_template_file
http://webpy.org/cookbook/runtime-language-switch
https://code.google.com/p/webapp-improved/source/browse/webapp2_extras/i18n.py
http://docs.python.org/2/library/threading.html
"""

import os
import gettext
import threading

localedir = os.path.join(os.path.dirname(__file__), 'locale')
domain = 'messages'
threadLocalData = threading.local()
threadLocalData.locale = 'en_US'

# find out all supported locales in locale directory
locales = []
for dirpath, dirnames, filenames in os.walk(localedir):
  for dirname in dirnames:
    locales.append(dirname)
  break

AllTranslations = {}
for locale in locales:
  AllTranslations[locale] = gettext.translation(domain, localedir, [locale])

def gettext(message):
  return AllTranslations[ threadLocalData.locale ].gettext(message)

def ugettext(message):
  return AllTranslations[ threadLocalData.locale ].ugettext(message)

def ngettext(singular, plural, n):
  return AllTranslations[ threadLocalData.locale ].ngettext(singular, plural, n)

def ungettext(singular, plural, n):
  return AllTranslations[ threadLocalData.locale ].ungettext(singular, plural, n)

def setLocale(locale):
  if locale in locales:
    threadLocalData.locale = locale


if __name__ == '__main__':
  # for test purpose
  for dirpath, dirnames, filenames in os.walk(localedir):
    for dirname in dirnames:
      print(dirname)
    break

The HTML to be rendered is saved as view/demo.html:

demo.html | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!doctype html>
<html>
<head>
  <title>i18n Python webapp with gettext and jinja2</title>
</head>
<body>
  <div>{{ _("Home") }}</div>
  <div>{{ _("Canon") }}</div>
  <div>{{ _("About") }}</div>
  <div>{{ _("Setting") }}</div>
  <div>{{ _("Translation") }}</div>
</body>
</html>

Use above module and template file to render HTML with translated texts:

jj2.py | 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
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import os
import jinja2
import i18n

env = jinja2.Environment(
    loader=jinja2.FileSystemLoader(
      [os.path.join(os.path.dirname(__file__), 'view')]),
    extensions=['jinja2.ext.i18n'])

env.install_gettext_translations(i18n)


if __name__ == '__main__':
  template = env.get_template('demo.html')
  i18n.setLocale("zh_TW")
  print(template.render())

  print("\n-----\n")

  i18n.setLocale("vi_VN")
  print(template.render())

Note

The domain in this case is messages. If you name your PO and MO file as hello, the domain becomes hello.

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

Output of Demo

<!doctype html>
<html>
<head>
  <title>i18n Python webapp with gettext and jinja2</title>
</head>
<body>
  <div>首頁</div>
  <div>經典</div>
  <div>關於</div>
  <div>設定</div>
  <div>翻譯</div>
</body>
</html>

-----

<!doctype html>
<html>
<head>
  <title>i18n Python webapp with gettext and jinja2</title>
</head>
<body>
  <div>Trang chính</div>
  <div>Kinh điển</div>
  <div>Giới thiệu</div>
  <div>Thiết lập</div>
  <div>Dịch</div>
</body>
</html>

Tested on: Ubuntu Linux 15.10, Python 2.7.10, jinja2 2.8.


References:

[1]Internationalization (i18n) of Web Application by GNU gettext Tools
[2]22.1. gettext — Multilingual internationalization services — Python 2.7.11 documentation
[3]Extensions — Jinja2 Documentation
[4]i18n support in template file (web.py)
[5]Run-time language switch (web.py)
[6]i18n.py - webapp-improved - Google App Engine's webapp, take two - Google Project Hosting
[7]16.2. threading — Higher-level threading interface — Python 2.7.11 documentation
[8][Golang] Internationalization (i18n) of Go Application by GNU gettext Tools
[9][Python] Internationalization (i18n) of Python Application by GNU gettext Tools