Internationalization in Sugar

From OLPC
Revision as of 17:21, 17 July 2008 by Fanwar (talk | contribs)
Jump to: navigation, search

Broad Steps to Internationalize/Localize Your Code

Most of these steps are adapted from the Python i18n with some changes for clarity and accuracy.

Step 1: Instrument all the translatable strings in your source code to use the gettext utility.

To ensure that string output from your activity is correctly translated, you would use the gettext utility. The code below imports gettext, renaming it as '_' for code brevity. Then, whenever there is a string that you want to make sure is translated based on language settings, you simply pass it to the _() function. According to the Python Reference Library, gettext will "return the localized translation of message, based on the current global domain, language, and locale directory."

The code snippet below is part of a larger UI creation routine that creates a sugar.graphics.Notebook object and three pages for that notebook. Each page label should be appropriately translated.

    from gettext import gettext as _
    ...        
        #Add the pages to the notebook. 
        top_container.add_page(_('First Page'), first_page)
        top_container.add_page(_('Second Page'), second_page)
        top_container.add_page(_('Third Page'), third_page)

Step 2: Create a "po" directory within your activity bundle to store some files needed to support translation.

Go in to your activity's source directory and create a new subdirectory called "po". In this directory, create a file called POTFILES.in. In POTFILES.in, your first line should be "encoding: UTF-8". Then, each subsequent line should be the name of a source file in your activity bundle that you want translated.

Below is output from a sample shell session where I carry out all the tasks in this step.

>> ls -l
total 292
drwxr-xr-x 2 fanwar fanwar  4096 2008-06-06 16:49 activity
-rw-r--r-- 1 fanwar fanwar  3143 2008-07-17 16:20 annotateactivity.py
drwxr-xr-x 2 fanwar fanwar  4096 2008-06-26 11:24 icons
-rw-r--r-- 1 fanwar fanwar   834 2008-07-02 14:30 setup.py
-rw-r--r-- 1 root   root    1759 2008-07-17 15:03 TextWidget.py

>> mkdir po

>> cd po

>> emacs POTFILES.in &
[1] 7164

>> ls
POTFILES.in

>> cat POTFILES.in 
encoding: UTF-8
annotateactivity.py
TextWidget.py


Step 3: Generate a .pot File that has a list of all the strings marked for translation by gettext in your activity source code.

If you setup your POTFILES.in file properly, you can generate the .pot file by invoking your setup.py script with the genpot option. Below is the source code for setup.py for my sample activity.

from sugar.activity import bundlebuilder
bundlebuilder.start("Annotate")

If your setup.py file exists, then you simply go to the terminal activity in sugar and run setup.py with genpot. The following snapshot of a shell session shows how I invoke genpot and then look at the contents of the newly generated Annotate.pot file. The "sugar-activities" directory is just a symbolic link to the place in my sugar file structure where activity bundles live.

[fanwar@localhost Annotate.activity]$ pwd
/home/fanwar/sugar-activities/Annotate.activity

[fanwar@localhost Annotate.activity]$ python setup.py genpot
WARNING:root:bundle_name deprecated, now comes from activity.info
WARNING:root:Activity directory lacks a MANIFEST file.

[fanwar@localhost Annotate.activity]$ cd po

[fanwar@localhost Annotate.activity]$ pwd
/home/fanwar/sugar-activities/Annotate.activity/po

[fanwar@localhost po]$ ls
Annotate.pot  POTFILES.in

[fanwar@localhost po]$ cat Annotate.pot 
# 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: 2008-07-17 17:14+0000\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"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: activity/activity.info:2 annotate-env.py:75 annotate-registry.py:92
#: annotate-mime.py:104 annotate-profile.py:101 annotate-original.py:70
#: annotate-alerts.py:179 annotate.py:83 annotate-pango.py:81
#: annotate-datastore.py:107 annotate-logging.py:59
msgid "Annotate"
msgstr ""

#: annotate-env.py:98 annotate-registry.py:115 annotate-mime.py:127
#: annotate-profile.py:125 annotate-original.py:93
#: annotate-internationalize.py:103 annotate-alerts.py:275 annotate.py:105
#: annotate-datastore.py:203 annotate-logging.py:82
msgid "Go to Page"
msgstr ""

#: annotateactivity.py:66
msgid "First Page"
msgstr ""

#: annotateactivity.py:67
msgid "Second Page"
msgstr ""

#: annotateactivity.py:68
msgid "Third Page"
msgstr ""

#: annotateactivity.py:81
msgid "Custom Annotate Toolbar"
msgstr ""

How do I ensure that using gettext does not crash my activity, especially when I try to translate more complex string substitution?

Since some strings require variables to be substituted into them, they need to be translated carefully. If they're not translated correctly, trying to do a string substitution can crash your activity.

The code below redefines the _() to use the gettext method only if gettext actually works. If there is an exception, the code will simply return the untranslated string. This ensures that instead of crashing, your activity will simply not translate your string.

#Do the import of the gettext, but do not give it the underscore alias
from gettext import gettext
...
#defensive method against variables not translated correctly
def _(s):
    #todo: permanent variable


    istrsTest = {}
    for i in range (0,4):
        istrsTest[str(i)] = str(i)

    #try to use gettext. If it fails, then just return the string untranslated. 
    try:
            #test translating the string with many replacements
            i = gettext(s)
            test = i % istrsTest
            print test
    except:
            #if it doesn't work, revert
            i = s
    return i
...
        #Now we can use the _() function and it should not crash if gettext fails.
        substitutionMap = {}
        substitutionMap[str(1)] = 'one'
        substitutionMap[str(2)] = 'two'
        substitutionMap[str(3)] = 'three'
        print _("Lets count to three: %(1)s, %(2)s, %(3)s") % substitutionMap

Additional Resources

Sugar Localization

Python i18n