This is where playing with APIs beyond their defaults comes into play. What I did is basically use a different compiled binary translation file for the GNUTranslations class to use. I am probably one of those devious devs in my circle whose never handled (or survived in) a Django project before, so bear with me. The software stack used in this project is:
- Twisted
- Rammi
- Babel
- Jinja2
- txTemplate
Basic Idea
So the core idea here is that we use a different *.mo file depending on the kind of translation that we need for a particular resource / template / mode. Turns out to be simple idea, eh?
Disclaimer
Code snippets posted here are not suitable for direct copy paste as a whole bunch of them are not actually tested (well, they're created on the fly) and only serve the purpose of giving the reader -- that is you, the idea on its implementation.
Current Working Directory
I will be issuing CLI commands in my project root directory which basically contains:
- (d) locale # contains translation-related files
- (d) resources # contains the python code
- (d) templates # contains the jinja2 templates
- (f) babel.cfg
Needless to say, I used Babel to extract the messages marked for translation all throughout the project. Now, let's go over how I leveraged Babel to complete this task (it is also, by the way, our star tool in this article).
Babel
Installing Babel in your virtualenv gives you the ability to invoke the `pybabel` command which demands usage of its subcommands, namely:
- extract
- update
- compile
Note that they need to be invoked in that order
Invoking pybabel extract will cause pybabel to dig through the files of the project in search for messages marked for translation and then generate a *.pot (*.po template) file. This command requires a mapping file which is basically a configuration file which allows us to specify which file it should search or which directory. A sample for this is:
[extractors]
jinja2 = jinja2.ext.babel_extract
[python: **.py]
encoding = utf-8
[jinja2: **/templates/**.html]
encoding = utf-8
This causes the pybabel extract command to recursively search all folders for *.py files and then recursively search the templates for any *.html file. Next, I do the pybabel extract command twice:
# format
pybabel extract --sort-by-file --no-wrap --mapping=<mapping-file> --output=<path-to-*.pot-file>
# example
pybabel extract --sort-by-file --no-wrap --mapping=babel.cfg --output=locale/messages.pot
pybabel extract --sort-by-file --no-wrap --mapping=babel.cfg --output=locale/messages-alternate.pot
That now yields two *.pot files which we can use with the pybabel update command, which generates/updates the *.po file. Again, we do this for each *.pot file that we have.
# format
pybabel update --input-file=<path-to-*.pot-file> --output-dir=<base-locale-directory> --domain=<name-of-*.po-file>
# example
pybabel update --input-file=locale/messages.pot --output-dir=locale --domain=messages
pybabel update --input-file=locale/messages-alternate.pot --output-dir=locale --domain=messages-alternate
The --domain option specifies the name of both the *.po file and the *.mo file (to be discussed later) which will be used by the GNUTranslations instance in our code later. Alright! So, do some translations in the *.po file and when you're done, time to do the pybabel compile command to generate our binary *.mo files.
# format
pybabel compile --directory=<base-locale-directory> --domain=<name-of-*.mo-file> -f
# example
pybabel compile --directory=locale --domain=messages -f
pybabel compile --directory=locale --domain=messages-alternate -f
The --domain option here also pretty much doubles as a way to select which *.po file is going to be compiled and what the file name of the *.mo file will be.
Python
Now that you have the *.mo files, what happens next? What to do in the code? Basically, simply use the *.mo file by instantiating another GNUTranslations class. I am using Babel, but there should not be that much difference as the Babel API also extends from the GNUTranslations class
# resources/translations.py
# Load default translations
import locale
from babel.support import Translations
os.environ['LANGUAGE'] = 'en_US.UTF-8'
locale.setlocale(locale.LC_MESSAGES, 'en_US.UTF-8')
# load 'messages.mo'
trans_default = Translations().load(dirname='locale')
# load 'messages-alternate.mo'
trans_altern8 = Translations().load(dirname='locale', domain='messages-alternate.mo')
# resources/some-other-file.py
# Now, it's all a matter of using the tranlations
from resources.translations import (
trans_default,
trans_altern8
)
trans_default.ugettext('Translate Me')
trans_atlern8.ugettext('Translate Me')
Jinja2
We have the translations instances. We can use it in the python code. How about in our Jinja2 templates? Unfortunately, we have to create a separate Environment instance:
# resources/tranlations.py
from jinja2 import Environment, FileSystemLoader
env_opts = {
'loader' : FileSystemLoader('templates'),
'extensions': [
'jinja2.ext.i18n'
]
}
env_default = Environment(**env_opts)
env_default.install_gettext_translations(trans_default, newstyle=True)
env_altern8 = Environment(**env_opts)
env_altern8.install_gettext_translations(trans_altern8, newstyle=True)
Jinja2 uses ugettext by default.
Back to Python - Rammi
To hook it up in the code.
# resources/resources.py
from rammi.resources import BaseResource
from txtemplate import Jinja2TemplateLoader
from resources import translations
class IndexResource(BaseResource):
def __init__(self, mode=0, *args, **kwargs):
BaseResource.__init__(self, *args, **kwargs)
if mode:
# Tranlations
self._ = translations.trans_default.ugettext
# Template Renderers
self.templates.environment = translations.env_default
else:
# Tranlations
self._ = translations.trans_altern8.ugettext
# Template Renderers
self.templates.environment = translations.env_altern8
Rammi should be able to use the templates with the customized Jinja environment for your translations usage and then at the same time, you should be using self._ instead of the plain ugettext function for translations inside your Resource, especially ones whose translations branch out according to its mode
No comments:
Post a Comment