From 6d087623448fdb8b9de110f5e223e32c4c627900 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 28 Oct 2013 10:10:59 +0530 Subject: [PATCH] Infrastructure for supporting translations of plugins --- manual/creating_plugins.rst | 41 ++++++++++++++++++++++++++++++ src/calibre/customize/zipplugin.py | 27 ++++++++++++++++++-- src/calibre/translations/msgfmt.py | 10 +++++--- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/manual/creating_plugins.rst b/manual/creating_plugins.rst index 9418f4a955..fe58b6e34e 100644 --- a/manual/creating_plugins.rst +++ b/manual/creating_plugins.rst @@ -92,6 +92,11 @@ The first thing to note is that this zip file has a lot more files in it, explai **about.txt** 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. __init__.py @@ -175,6 +180,42 @@ You can see the ``prefs`` object being used in main.py: .. literalinclude:: plugin_examples/interface_demo/main.py :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 `_ 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 -------------------------------- diff --git a/src/calibre/customize/zipplugin.py b/src/calibre/customize/zipplugin.py index ca07462b9c..a5145d8141 100644 --- a/src/calibre/customize/zipplugin.py +++ b/src/calibre/customize/zipplugin.py @@ -81,6 +81,30 @@ def get_icons(zfp, name_or_list_of_names): ians = ians.pop(names[0]) 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): def __init__(self): @@ -147,11 +171,11 @@ class PluginLoader(object): import_name), 'exec', dont_inherit=True) mod.__dict__['get_resources'] = partial(get_resources, zfp) mod.__dict__['get_icons'] = partial(get_icons, zfp) + mod.__dict__['load_translations'] = partial(load_translations, mod.__dict__, zfp) exec compiled in mod.__dict__ return mod - def load(self, path_to_zip_file): if not os.access(path_to_zip_file, os.R_OK): raise PluginNotFound('Cannot access %r'%path_to_zip_file) @@ -193,7 +217,6 @@ class PluginLoader(object): del self.loaded_plugins[plugin_name] raise - def _locate_code(self, zf, path_to_zip_file): names = [x if isinstance(x, unicode) else x.decode('utf-8') for x in zf.namelist()] diff --git a/src/calibre/translations/msgfmt.py b/src/calibre/translations/msgfmt.py index a27a6c007f..cd3c890cf0 100644 --- a/src/calibre/translations/msgfmt.py +++ b/src/calibre/translations/msgfmt.py @@ -143,7 +143,7 @@ def make(filename, outfile): (infile, lno) sys.exit(1) l = l[12:] - msgid += '\0' # separator of singular and plural + msgid += '\0' # separator of singular and plural is_plural = True # Now we are in a msgstr section elif l.startswith('msgstr'): @@ -155,7 +155,7 @@ def make(filename, outfile): sys.exit(1) l = l.split(']', 1)[1] if msgstr: - msgstr += '\0' # Separator of the various plural forms + msgstr += '\0' # Separator of the various plural forms else: if is_plural: print >> sys.stderr, 'indexed msgstr required for plural on %s:%d' %\ @@ -184,7 +184,11 @@ def make(filename, outfile): # Compute output output = generate() - outfile.write(output) + try: + outfile.write(output) + except AttributeError: + with open(outfile, 'wb') as f: + f.write(output) def main():