Internationalization (or localization) is the ability to customize an app for the local language without having to change the source code. In the Dabo framework, we've strived to wrap all strings in a localization function, which by convention in the Python world at least is named simply with the underscore character: _().
Here is a post from the dabo-dev list that helps explain our approach:
> Since you both appear to be working - could someone explain the > following code: >raise dException.NoRecordsException, _("No rows to sort.")> The part I don't understand is the underscore?
In the Python world, _() is the standard name for the internationalization function: it takes a string and, depending on the settings, returns the proper localized translation of the original. In Dabo, it is nothing more than a placeholder for now; if we get some international developers involved in the future, they can write translations and hook them into the dLocalize program, allowing for localized apps without having to mess with the source code.
Look at the top of any file where you see the underscore function; it will have a line like:
from dabo.dLocalize import _
New attempt...
We are writing a small dabo app, that needs a German and English locale (German is default) and is packaged on OSX with py2app (perhaps on Windows using py2exe later).
We use Babel and POedit? for translation.
The project tree looks like (fat names are directories):
- setup.py (see below)
- ez_setup.py (from distutils)
- translate.sh (see below)
- makeit.sh (see below)
- build (py2app's build tree)
- dist (py2app's app target)
- src
- main.py
- biz (our "functional" code, not shown)
- gui (our GUI code, not shown)
- icons (our application icon)
- appIcon.icns
- appIcon.png
- locale
- de
- LC_MESSAGES
- myapp.po
- myapp.mo
- LC_MESSAGES
- en
- LC_MESSAGES
- myapp.po
- myapp.mo
- LC_MESSAGES
- myapp.pot
- de
The main script contains:
import osBeware: order of the above commands matters! You'll still get some wrong settings (e.g. date format), while http://trac.dabodev.com/ticket/1298 is unresolved.
import dabo
# i18n/i10n stuff
# better get the default locale setting from the OS! (doesn't work for me, will add later)
dabo.settings.defaultLanguage = 'de'
dabo.settings.firstDayOfWeek = 'Monday'
from dabo.dLocalize import _
dabo.dLocalize.install('myapp', os.path.join(os.path.dirname(__file__), 'locale'), True)
dabo.dLocalize.setLanguage('de', 'utf-8')
Of course all output strings are i18n-prepared like
print _(u'Foo bar')
The setup script looks like:
import ez_setup
ez_setup.use_setuptools()
import os, sys, glob
import dabo, dabo.icons
from setuptools import setup
# locales:
daboDir = os.path.dirname(dabo.__file__)
localeDir = os.path.join(daboDir, 'locale')
locales = [
('lib/python2.6/dabo.locale/de/LC_MESSAGES', (os.path.join(localeDir, 'de', 'LC_MESSAGES', 'dabo.mo'),)),
('lib/python2.6/dabo.locale/en/LC_MESSAGES', (os.path.join(localeDir, 'en', 'LC_MESSAGES', 'dabo.mo'),)),
('locale/de/LC_MESSAGES', ('src/locale/de/LC_MESSAGES/myapp.mo',)),
('locale/en/LC_MESSAGES', ('src/locale/en/LC_MESSAGES/myapp.mo',)),
]
# this would include all of dabo's locales:
"""
#locales = [("dabo.locale", (os.path.join(daboDir, "locale", "dabo.pot"),))]
locales = []
def getLocales(arg, dirname, fnames):
if ".svn" not in dirname and dirname[-1] != "\\":
mo_files = tuple(glob.glob(os.path.join(dirname, "*.mo")))
if mo_files:
subdir = os.path.join("dabo.locale", dirname[len(arg)+1:])
locales.append((subdir, mo_files))
os.path.walk(localeDir, getLocales, localeDir)
"""
icons = [
#('lib/python2.6/dabo.icons', tuple(glob.glob(os.path.join(daboDir, 'icons', 'dabo*.png')))) # if you need dabo's icons
('icons', tuple(glob.glob('src/icons/appIcon.*')))
]
mainscript = 'src/main.py'
NAME = 'My App'
VERSION = '0.1.0'
APP = [mainscript]
DATA_FILES = locales + icons
APP_OPTIONS = {
'argv_emulation': True,
'includes' : ['wx', 'wx.gizmos', 'wx.lib.calendar', 'dabo', 'Growl', 'ftputil'], #, 'dabo.icons',
'excludes' : ['kinterbasdb', 'psycopg2', 'MySQLdb', 'numpy', 'wx.tools.Editra', 'wx.tools.XRCed'], # in this case, we don't need database support
'iconfile' : 'src/icons/appIcon.icns',
}
# included packages must not be zipped eggs, use easy_install -Z foo or pip unzip foo
EXE_OPTIONS = {
}
plist = dict(
CFBundleName = NAME,
CFBundleShortVersionString = VERSION,
CFBundleGetInfoString = '%s %s' % (NAME, VERSION),
CFBundleExecutable = NAME,
CFBundleIdentifier = 'com.example.myapp',
# CFBundleDocumentTypes = [
# {'CFBundleTypeExtensions': ['jpg', 'tif', 'bmp'],
# 'CFBundleTypeName':'Image File',
# 'CFBundleTypeRole':'Viewer'}
# ]
 
# BEWARE Windows py2exe code is untested!
manifest = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<assemblyIdentity
version="0.64.1.0"
processorArchitecture="x86"
name="Controls"
type="win32"
/>
<description>edNet Exporter</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
"""
if sys.platform == 'darwin':
APP_OPTIONS['plist'] = plist
extra_options = dict(
# Cross-platform applications generally expect sys.argv to
# be used for opening files.
setup_requires=['py2app'],
app=APP,
options={'py2app': APP_OPTIONS},
 
elif sys.platform == 'win32':
"""
windows = [
{
"script": mainscript,
"icon_resources": [(1, "appIcon.png")],
"other_resources": [(24,1,manifest)]
}
],
data_files=["src/icons/appIcon.png"]
"""
extra_options = dict(
script = mainscript,
setup_requires=['py2exe'],
app=APP,
)
else:
extra_options = dict(
# Normally unix-like platforms will use "setup.py install"
# and install the main script as such
scripts=APP,
)
setup(
name=NAME,
data_files=DATA_FILES,
**extra_options
)
translate.sh controls pybabel
#!/bin/shAfter that open the .po files with POedit? (saving creates .mo files). If you don't like POedit?, edit the .po files in your favourite editor and run "pybabel compile" (commented in the above script).
# Extract strings from Python files (make .pot) and update translation source (.po)
# use POedit to edit .po files and to compile translations to .mo!
LD='./src/locale'
DOM=myapp
if [ ! -d "$LD" ]
then
mkdir $LD
fi
for L
do
if [ ! -d "$LD/$L" ]
then
mkdir $LD/$L
fi
if [ ! -d "$LD/$L/LC_MESSAGES" ]
then
mkdir $LD/$L/LC_MESSAGES
fi
done
echo "Extracting messages..."
pybabel extract -o $LD/$DOM.pot src
for LOC in de en
do
if [ ! -f $LD/$LOC/LC_MESSAGES/$DOM.po ]
then
echo "Making new locale $LOC"
pybabel init -D $DOM -i $LD/$DOM.pot -l $LOC -d $LD
else
echo "Updating locale $LOC"
pybabel update -D $DOM -i $LD/$DOM.pot -l $LOC -d $LD
fi
#echo "Compiling locale $LOC"
#pybabel compile -D $DOM -i $LD/$LOC/LC_MESSAGES/$DOM.po -l $LOC -d $LD
done
makeit.sh cleans the previous build files and creates a new application in "dist"
#!/bin/bash
rm -R dist
rm -R build
mkdir build
echo "Making My App"
python setup.py py2app -O2 -b build/myapp > build/build.log
