Changes
Jump to navigation
Jump to search
Development Team/Almanac/Internationalization (view source)
Revision as of 14:33, 26 July 2008
, 14:33, 26 July 2008no edit summary
== Broad Steps to Internationalize/Localize Your Code ==
Most of these steps are adapted from the [http://wiki.laptop.org/go/Python_i18n 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 [http://docs.python.org/lib/node731.html 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.
<pre>
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)
</pre>
=== 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.
<pre>
>> 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
</pre>
=== 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.
<pre>
from sugar.activity import bundlebuilder
bundlebuilder.start("Annotate")
</pre>
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.
<pre>
[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 ""
</pre>
=== Step 4: Use msginit to generate a .po file ===
Once you have created a ".pot" file, the next step is to create a ".po". You use the msginit command from within your po directory to do this. Below, I create an "es.po" file using msginit that will eventually contain translations to Spanish.
<pre>
[root@localhost Annotate.activity]# cd po
[root@localhost po]# msginit -l es
The new message catalog should contain your email address, so that users can
give you feedback about the translations, and so that maintainers can contact
you in case of unexpected technical problems.
Is the following your email address?
root@localhost.localdomain
Please confirm by pressing Return, or enter your email address.
Retrieving http://www.iro.umontreal.ca/translation/registry.cgi?team=index... done.
A translation team for your language (es) does not exist yet.
If you want to create a new translation team for es, please visit
http://www.iro.umontreal.ca/contrib/po/HTML/teams.html
http://www.iro.umontreal.ca/contrib/po/HTML/leaders.html
http://www.iro.umontreal.ca/contrib/po/HTML/index.html
Created es.po.
[root@localhost po]# cat es.po
# Spanish translations for PACKAGE package.
# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Faisal Anwar <root@localhost.localdomain>, 2008.
#
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: 2008-07-17 17:25+0000\n"
"Last-Translator: Faisal Anwar <root@localhost.localdomain>\n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: 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 ""
</pre>
Notice that the "msgstr" lines are empty. This is where translations go, as we shall discuss next.
=== Step 5: Translate all the strings that need translation. ===
You can do this in several different ways. One is to go in to the ".po" file directly and add your msgstr translation for each msgid entry.
<pre>
#: annotateactivity.py:66
msgid "First Page"
msgstr "pagina uno"
#: annotateactivity.py:67
msgid "Second Page"
msgstr "pagina dos"
#: annotateactivity.py:68
msgid "Third Page"
msgstr "pagina tres"
</pre>
A better way is to plug in to the Pootle system that employs translators from all different languages. Visit the [https://dev.laptop.org/translate/ One Laptop Per Child: Translation System] to find out more about using Pootle and putting your activity's po files up for translation.
=== Step 6: Generate a ".mo" file using msgfmt and install the translation in to your activity by putting the .mo file in the locale/LANG/LC_MESSAGES directory. ===
Finally, you have to compile the .po file with your translations in to a ".mo" file. The following code generates a .mo file called "org.laptop.AnnotateActivity.mo" using the msgfmt command. Your .mo file has to have this standard naming scheme that uses the URI for your activity (as it is defined in your activity.info file). Note that in the same command in which I generate the .mo file for Spanish translations, I also place this file in the appropriate directory where the translations can be picked up at runtime. This directory is "locale/es/LC_MESSAGES/" and is located within your activity bundle. Make sure to create this directory structure if it doesn't exist already.
<pre>
[root@localhost po]# pwd
/home/fanwar/sugar-activities/Annotate.activity/po
[root@localhost po]# msgfmt es.po --output='../locale/es/LC_MESSAGES/org.laptop.AnnotateActivity.mo'
</pre>
== Other, more specific internationalization/localization tasks ==
=== 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.
<pre>
#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
</pre>
= Additional Resources =
[http://wiki.laptop.org/go/Localization Sugar Localization]
[http://wiki.laptop.org/go/Python_i18n Python i18n]
Most of these steps are adapted from the [http://wiki.laptop.org/go/Python_i18n 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 [http://docs.python.org/lib/node731.html 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.
<pre>
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)
</pre>
=== 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.
<pre>
>> 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
</pre>
=== 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.
<pre>
from sugar.activity import bundlebuilder
bundlebuilder.start("Annotate")
</pre>
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.
<pre>
[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 ""
</pre>
=== Step 4: Use msginit to generate a .po file ===
Once you have created a ".pot" file, the next step is to create a ".po". You use the msginit command from within your po directory to do this. Below, I create an "es.po" file using msginit that will eventually contain translations to Spanish.
<pre>
[root@localhost Annotate.activity]# cd po
[root@localhost po]# msginit -l es
The new message catalog should contain your email address, so that users can
give you feedback about the translations, and so that maintainers can contact
you in case of unexpected technical problems.
Is the following your email address?
root@localhost.localdomain
Please confirm by pressing Return, or enter your email address.
Retrieving http://www.iro.umontreal.ca/translation/registry.cgi?team=index... done.
A translation team for your language (es) does not exist yet.
If you want to create a new translation team for es, please visit
http://www.iro.umontreal.ca/contrib/po/HTML/teams.html
http://www.iro.umontreal.ca/contrib/po/HTML/leaders.html
http://www.iro.umontreal.ca/contrib/po/HTML/index.html
Created es.po.
[root@localhost po]# cat es.po
# Spanish translations for PACKAGE package.
# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Faisal Anwar <root@localhost.localdomain>, 2008.
#
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: 2008-07-17 17:25+0000\n"
"Last-Translator: Faisal Anwar <root@localhost.localdomain>\n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: 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 ""
</pre>
Notice that the "msgstr" lines are empty. This is where translations go, as we shall discuss next.
=== Step 5: Translate all the strings that need translation. ===
You can do this in several different ways. One is to go in to the ".po" file directly and add your msgstr translation for each msgid entry.
<pre>
#: annotateactivity.py:66
msgid "First Page"
msgstr "pagina uno"
#: annotateactivity.py:67
msgid "Second Page"
msgstr "pagina dos"
#: annotateactivity.py:68
msgid "Third Page"
msgstr "pagina tres"
</pre>
A better way is to plug in to the Pootle system that employs translators from all different languages. Visit the [https://dev.laptop.org/translate/ One Laptop Per Child: Translation System] to find out more about using Pootle and putting your activity's po files up for translation.
=== Step 6: Generate a ".mo" file using msgfmt and install the translation in to your activity by putting the .mo file in the locale/LANG/LC_MESSAGES directory. ===
Finally, you have to compile the .po file with your translations in to a ".mo" file. The following code generates a .mo file called "org.laptop.AnnotateActivity.mo" using the msgfmt command. Your .mo file has to have this standard naming scheme that uses the URI for your activity (as it is defined in your activity.info file). Note that in the same command in which I generate the .mo file for Spanish translations, I also place this file in the appropriate directory where the translations can be picked up at runtime. This directory is "locale/es/LC_MESSAGES/" and is located within your activity bundle. Make sure to create this directory structure if it doesn't exist already.
<pre>
[root@localhost po]# pwd
/home/fanwar/sugar-activities/Annotate.activity/po
[root@localhost po]# msgfmt es.po --output='../locale/es/LC_MESSAGES/org.laptop.AnnotateActivity.mo'
</pre>
== Other, more specific internationalization/localization tasks ==
=== 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.
<pre>
#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
</pre>
= Additional Resources =
[http://wiki.laptop.org/go/Localization Sugar Localization]
[http://wiki.laptop.org/go/Python_i18n Python i18n]