Skip to main content

Romani ite domum

Localization with gettext and locale

/images/10_lokalisation.thumbnail.png

Glade

Strings in widgets are by default configurated as translatable so there are no preparations required. GetText directly provercesses Glade project files.

Python

Translatable strings

Approved translatable strings are recognized by xgettext by brackets with a leading underscore:

_ = gettext.gettext
translatable_string = _("translate me")

configure (bind)textdomain

Now name and location of the MO files have to be configured in the source code:

locale.bindtextdomain(appname,locales_dir)
locale.textdomain(locales_dir)
gettext.bindtextdomain(appname,locales_dir)
gettext.textdomain(appname)
builder.set_translation_domain(appname)

set_translation_domain has to be called before loading Glade files.

GetText

POT

POT is the abbrevation for Portable Object Template. This file contains all original translatable strings. After generating an empty POT file, xgettext is executed for all source files containing translatable strings:

$ xgettext --options -o output.pot sourcefile.ext

The identified strings are added to the POT file.

#: sourcefile.ext:line number
msgid "translatable string"
msgstr ""

The file number reference comment can be avoided by passting the option --no-location.

In this article's example it is required to run xgettext once for the Glade file and once for the Python source code; the -j (--join-existing) option adds new found strings to an existing file:

$ xgettext --sort-output --keyword=translatable --language=Glade -j -o 10_localization/TUT.pot 10_lokalisation.glade
$ xgettext --language=Python -j -o 10_localization/TUT.pot 10_lokalisation.py

PO

Translated strings are stored in a PO file per language. A new translation ist invoked by

$ msginit --input=source.pot --locale=xx
# xx=language code

that generates a file after the pattern xx.po (p.e. de.po). This file can be edited in any text editor or dedicated tools such like PoEdit. A German localization for example is created by the command

$ msginit --input=TUT.pot --locale=de

If the POT file is altered the PO files are updated with the new strings by executing msgmerge:

$ msgmerge lang.po template.pot > new_lang.po

MO

MO files are (machine readable) binary files and mandatory for gettext to work. Localization files are located below the bindtextdomain following the file structure path/to/bindtextdomain)/locale/language code/LC_MESSAGES/appname.po.

In the example the bindtextdomain is created in the local directory, the generated de.po translation text file then transformed into the corresponding MO file:

$ msgfmt --output locale/de/LC_MESSAGES/TUT.mo de.po

Tipps

xgettext options

--no-location

Oppress writing line number(s) and file name as comment

--omit-header

Avoid overwriting header information

Remove obsolete strings

Strings that are removed from the template remain in the translation files. You can get rid of these by executing this command:

$ msgattrib --set-obsolete --ignore-file=PRJ.pot -o xx.po xx.po

Listings

Glade

10_lokalisation.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Localization example (English)</property>
    <property name="default_width">400</property>
    <property name="default_height">400</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkMenuBar">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_File</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-new</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-open</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-save</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-save-as</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkSeparatorMenuItem">
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-quit</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_Edit</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-cut</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-copy</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-paste</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-delete</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_View</property>
                <property name="use_underline">True</property>
              </object>
            </child>
            <child>
              <object class="GtkMenuItem">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">_Help</property>
                <property name="use_underline">True</property>
                <child type="submenu">
                  <object class="GtkMenu">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <child>
                      <object class="GtkImageMenuItem">
                        <property name="label">gtk-about</property>
                        <property name="visible">True</property>
                        <property name="can_focus">False</property>
                        <property name="use_underline">True</property>
                        <property name="use_stock">True</property>
                      </object>
                    </child>
                  </object>
                </child>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkScrolledWindow">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="shadow_type">in</property>
            <child>
              <object class="GtkViewport">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <child>
                  <object class="GtkLabel">
                    <property name="visible">True</property>
                    <property name="can_focus">False</property>
                    <property name="label" translatable="yes">Good morning. In less than an hour, aircraft from here will join others from around the world. And you will be launching the largest aerial battle in this history of mankind.

Mankind -- that word should have new meaning for all of us today.

We can't be consumed by our petty differences anymore.

We will be united in our common interests.

Perhaps its fate that today is the 4th of July, and you will once again be fighting for our freedom, not from tyranny, oppression, or persecution -- but from annihilation.

We're fighting for our right to live, to exist.

And should we win the day, the 4th of July will no longer be known as an American holiday, but as the day when the world declared in one voice:

"We will not go quietly into the night!
We will not vanish without a fight!
We're going to live on!
We're going to survive!"

Today, we celebrate our Independence Day!</property>
                    <property name="wrap">True</property>
                  </object>
                </child>
              </object>
            </child>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
    <child>
      <placeholder/>
    </child>
  </object>
</interface>

Python

10_lokalisation.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import gettext
import locale
import os

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk

_ = gettext.gettext


class Handler:

    def on_window_destroy(self, *args):
        Gtk.main_quit()


class Example:

    def __init__(self):

        #setting up localization
        locales_dir = os.path.join(os.getcwd(),
                                    "10_localization",
                                    "locale",
                                    )
        appname = "TUT"

        #required for showing Glade file translations
        locale.bindtextdomain(appname, locales_dir)
        locale.textdomain(locales_dir)
        #required for code translations
        gettext.bindtextdomain(appname, locales_dir)
        gettext.textdomain(appname)

        self.builder = Gtk.Builder()
        self.builder.set_translation_domain(appname)

        self.builder.add_from_file("10_lokalisation.glade")
        self.builder.connect_signals(Handler())

        #translatable strings
        print(_("It's a trap!"))
        print(_("""These aren't the droids you're looking for.\n"""))

        #not translatable
        nonono = """\"Jar Jar is the key to all of this.\""""
        george = "...ruined it."
        print(nonono, george)

        window = self.builder.get_object("window")
        window.show_all()

    def main(self):
        Gtk.main()


x = Example()
x.main()

POT

10_localization/TUT.pot (Source)

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-28 13:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

msgid ""
"Good morning. In less than an hour, aircraft from here will join others from "
"around the world. And you will be launching the largest aerial battle in "
"this history of mankind.\n"
"\n"
"Mankind -- that word should have new meaning for all of us today.\n"
"\n"
"We can't be consumed by our petty differences anymore.\n"
"\n"
"We will be united in our common interests.\n"
"\n"
"Perhaps its fate that today is the 4th of July, and you will once again be "
"fighting for our freedom, not from tyranny, oppression, or persecution -- "
"but from annihilation.\n"
"\n"
"We're fighting for our right to live, to exist.\n"
"\n"
"And should we win the day, the 4th of July will no longer be known as an "
"American holiday, but as the day when the world declared in one voice:\n"
"\n"
"\"We will not go quietly into the night!\n"
"We will not vanish without a fight!\n"
"We're going to live on!\n"
"We're going to survive!\"\n"
"\n"
"Today, we celebrate our Independence Day!"
msgstr ""

msgid "It's a trap!"
msgstr ""

msgid "Localization example (English)"
msgstr ""

msgid "These aren't the droids you're looking for.\n"
msgstr ""

msgid "_Edit"
msgstr ""

msgid "_File"
msgstr ""

msgid "_Help"
msgstr ""

msgid "_View"
msgstr ""

Comments

Comments powered by Disqus