Infrastructure for supporting translations of plugins

This commit is contained in:
Kovid Goyal 2013-10-28 10:10:59 +05:30
parent e4557408a8
commit 6d08762344
3 changed files with 73 additions and 5 deletions

View File

@ -92,6 +92,11 @@ The first thing to note is that this zip file has a lot more files in it, explai
**about.txt** **about.txt**
A text file with information about the plugin A text file with information about the plugin
**translations**
A folder containing .mo files with the translations of the user
interface of your plugin into different languages. See below for
details.
Now let's look at the code. Now let's look at the code.
__init__.py __init__.py
@ -175,6 +180,42 @@ You can see the ``prefs`` object being used in main.py:
.. literalinclude:: plugin_examples/interface_demo/main.py .. literalinclude:: plugin_examples/interface_demo/main.py
:pyobject: DemoDialog.config :pyobject: DemoDialog.config
Adding translations to your plugin
--------------------------------------
You can have all the user interface strings in your plugin translated and
displayed in whatever language is set for the main calibre user interface.
The first step is to go through your plugin's source code and mark all user
visible strings as translatable, by surrounding them in _(). For example::
action_spec = (_('My plugin'), None, _('My plugin is cool'), None)
Then use some program to generate .po files from your plugin source code. There
should be one .po file for every language you want to translate into. For
example: de.po for German, fr.po for French and so on. You can use the
`poedit <http://www.poedit.net/>`_ program for this.
Send these .po files to your translators. Once you get them back, compile them
into .mo files. You can again use poedit for that, or just do::
calibre-debug -c "from calibre.translations.msgfmt import main; main()" filename.po
Put the .mo files into the ``translations`` folder in your plugin.
The last step is to simply call the function `load_translations()` at the top
of your plugin's .py files. For performance reasons you should only call this
function in those .py files that actually have translatable strings. So in a
typical User Interface plugin you would call it at the top of ``ui.py`` but not
``__init__.py``.
You can test the translations of your plugins by changing the user interface
language in calibre under Preferences->Look & Feel or by running calibre like
this::
CALIBRE_OVERRIDE_LANG=de calibre
Replace ``de`` with the language code of the language you want to test.
The plugin API The plugin API
-------------------------------- --------------------------------

View File

@ -81,6 +81,30 @@ def get_icons(zfp, name_or_list_of_names):
ians = ians.pop(names[0]) ians = ians.pop(names[0])
return ians return ians
_translations_cache = {}
def load_translations(namespace, zfp):
null = object()
trans = _translations_cache.get(zfp, null)
if trans is None:
return
if trans is null:
from calibre.utils.localization import get_lang
lang = get_lang()
if not lang or lang == 'en': # performance optimization
_translations_cache[zfp] = None
return
mo = get_resources(zfp, 'translations/%s.mo' % lang)
if mo is None:
_translations_cache[zfp] = None
return
from gettext import GNUTranslations
from io import BytesIO
trans = _translations_cache[zfp] = GNUTranslations(BytesIO(mo))
namespace['_'] = trans.gettext
namespace['ngettext'] = trans.ngettext
class PluginLoader(object): class PluginLoader(object):
def __init__(self): def __init__(self):
@ -147,11 +171,11 @@ class PluginLoader(object):
import_name), 'exec', dont_inherit=True) import_name), 'exec', dont_inherit=True)
mod.__dict__['get_resources'] = partial(get_resources, zfp) mod.__dict__['get_resources'] = partial(get_resources, zfp)
mod.__dict__['get_icons'] = partial(get_icons, zfp) mod.__dict__['get_icons'] = partial(get_icons, zfp)
mod.__dict__['load_translations'] = partial(load_translations, mod.__dict__, zfp)
exec compiled in mod.__dict__ exec compiled in mod.__dict__
return mod return mod
def load(self, path_to_zip_file): def load(self, path_to_zip_file):
if not os.access(path_to_zip_file, os.R_OK): if not os.access(path_to_zip_file, os.R_OK):
raise PluginNotFound('Cannot access %r'%path_to_zip_file) raise PluginNotFound('Cannot access %r'%path_to_zip_file)
@ -193,7 +217,6 @@ class PluginLoader(object):
del self.loaded_plugins[plugin_name] del self.loaded_plugins[plugin_name]
raise raise
def _locate_code(self, zf, path_to_zip_file): def _locate_code(self, zf, path_to_zip_file):
names = [x if isinstance(x, unicode) else x.decode('utf-8') for x in names = [x if isinstance(x, unicode) else x.decode('utf-8') for x in
zf.namelist()] zf.namelist()]

View File

@ -143,7 +143,7 @@ def make(filename, outfile):
(infile, lno) (infile, lno)
sys.exit(1) sys.exit(1)
l = l[12:] l = l[12:]
msgid += '\0' # separator of singular and plural msgid += '\0' # separator of singular and plural
is_plural = True is_plural = True
# Now we are in a msgstr section # Now we are in a msgstr section
elif l.startswith('msgstr'): elif l.startswith('msgstr'):
@ -155,7 +155,7 @@ def make(filename, outfile):
sys.exit(1) sys.exit(1)
l = l.split(']', 1)[1] l = l.split(']', 1)[1]
if msgstr: if msgstr:
msgstr += '\0' # Separator of the various plural forms msgstr += '\0' # Separator of the various plural forms
else: else:
if is_plural: if is_plural:
print >> sys.stderr, 'indexed msgstr required for plural on %s:%d' %\ print >> sys.stderr, 'indexed msgstr required for plural on %s:%d' %\
@ -184,7 +184,11 @@ def make(filename, outfile):
# Compute output # Compute output
output = generate() output = generate()
outfile.write(output) try:
outfile.write(output)
except AttributeError:
with open(outfile, 'wb') as f:
f.write(output)
def main(): def main():