Install and use¶
Install¶
Activate your virtual environment, then do:
$ pip install puente
Optional: If you want to extract strings from Django templates, you will also need to install django-babel which has an extractor for Django templates:
$ pip install django-babel
Configure¶
In settings.py
add Puente to INSTALLED_APPS
:
INSTALLED_APPS = [
# ...
'puente',
# ...
]
In settings.py
add puente.ext.i18n
as an extension in your Jinja2
template environment configuration. For example, if you were using django-jinja,
then it might look like this:
TEMPLATES = [
{
'BACKEND': 'django_jinja.backend.Jinja2',
# ...
'OPTIONS': {
# ...
'autoescape': True,
'extensions': [
# ...
'puente.ext.i18n',
# ...
],
},
}
]
Puente configuration goes in the PUENTE
setting in your Django settings
file. Here’s a minimal example:
PUENTE = {
'BASE_DIR': BASE_DIR,
'DOMAIN_METHODS': {
'django': [
('**.py', 'python'),
('fjord/**/jinja2/**.html', 'jinja2'),
('fjord/**/templates/**.html', 'django'),
],
'djangojs': [
('**.js', 'javascript'),
]
}
}
This sets up string extraction for Jinja2 templates using the Jinja2 extractor,
Python files using the Python extractor, and Django templates using the Django
extractor [1] and puts all those strings in django.pot
files.
[1] | You need to install django-babel for the Django extractor for it to be available. |
Note that BASE_DIR
is the path to the project root. It’s in the
settings.py
file that is generated when you create a new Django project.
-
BASE_DIR
¶ Type: String Default: None Required: Yes This is the absolute path to the root directory which has
locale/
in it. In most cases, it’s probably fine to set it toBASE_DIR
which is in thesettings.py
file that Django generates when you create a new project.For example:
/home/willkg/ - fjord/ <-- BASE_DIR - .git/ - locale/ - fjord/ - code!!! - manage.py
-
DOMAIN_METHODS
¶ Type: Dict of string to list of (string, string) tuples Default: None Required: Yes Dict of domain name to list of (file matcher, extractor) tuples.
A domain name here is the name that’s used to name the
.pot
and.po
files. For example, if the domain was “django”, then the resulting files would bedjango.pot
anddjango.po
.The file matcher uses
*
and**
glob patterns.The only valid domains are
django
anddjangojs
.Valid extractors include:
python
for Python files (Babel)javascript
for Javascript files (Babel)ignore
for files to ignore to alleviate difficulties in file matching (Babel)jinja2
for Jinja2 templates (Jinja2)django
for django templates (django-babel) [2]
[2] You need to install django-babel for the Django extractor for it to be available. You can use extractors provided by other libraries, too. You can also write your own extractors and use a dotted path to the extraction function.
Example of
DOMAIN_METHODS
:PUENTE = { 'DOMAIN_METHODS': { 'django': [ ('fjord/**/jinja2/**.html', 'jinja2'), ('**.py', 'python') ('fjord/**/templates/**.html', 'django'), ], 'djangojs': [ ('**.js', 'javascript'), ] } }
Note
The syntax is an inclusion-style syntax where you specify some group of files to use some extractor.
In some cases, this is very inconvenient because you might need to say something like “use this extractor with all the files with this glob pattern except this one….”.
To exclude files, you create a rule higher up in the list and use the
ignore
extractor.For example, to use jinja2 for all files in a directory except ones named
whaleshark.html
, you’d do something like this:PUENTE = { 'DOMAIN_METHODS': { 'django': [ ('fjord/**/jinja2/whaleshark.html', 'ignore'), ('fjord/**/jinja2/**.html', 'jinja2') ] } }
The example is pretty contrived, but hopefully that helps.
-
KEYWORDS
¶ Type: Dict of keyword to Babel magic Default: Common gettext indicators Required: No Babel has keywords:
https://github.com/python-babel/babel/blob/5116c167/babel/messages/extract.py#L31
Puente adds
'_lazy': None
to that.Babel uses the keywords to know what strings to extract and how to extract them.
There’s a
puente.utils.generate_keywords
function to make it easier to get all the defaults plus the ones you want:from puente.utils import generate_keywords PUENTE = { 'KEYWORDS': generate_keywords({'foo': None}) }
-
COMMENT_TAGS
¶ Type: List of strings Default: ['Translators:', 'L10n:', 'L10N:', 'l10n:', 'l10N:']
Required: No The list of prefixes that denote a comment tag intended for the translator.
For example, if you had code like this:
# l10n: This is a menu name. menu_name = _('File')
Then the comment will get extracted as a translator comment.
Note
Django project uses “Translators:”, so if you use that, you’re closer to vanilla Django.
-
JINJA2_CONFIG
¶ Type: Dict Default: Complicated… Required: Possibly This has the options to pass to
babel_extract
.http://jinja.pocoo.org/docs/dev/integration/#babel-integration
Setting it yourself
Generally, you can add syntax-related options that’d you’d pass in to build a new Jinja2 Environment:
http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment
Additionally, in Jinja2 2.7, they added a
silent
option which dictates whether the parser fails silently when parsing Jinja2 templates. This commonly happens in two scenarios:- The list of extensions passed isn’t the complete list.
- The HTML file isn’t a Jinja2 template.
For debugging purposes, you definitely want
silent=False
.Example of
JINJA2_CONFIG
:PUENTE = { 'JINJA2_CONFIG`: { 'extensions': [ 'jinja2.ext.do', 'jinja2.ext.loopcontrols', 'django_jinja.builtins.extensions.CsrfExtension', 'django_jinja.builtins.extensions.StaticFilesExtension', 'django_jinja.builtins.extensions.DjangoFiltersExtension', 'puente.ext.i18n', ] } }
Having Puente figure it out for you
If you’re using Jingo or django-jinja, then Puente will try to extract the list of extensions from the relevant settings. If that works for you, then you don’t need to set this.
If Puente is figuring it out, it will automatically add silent=False.
For example, if you’re using django-jinja with these settings:
TEMPLATES = [ { 'BACKEND': 'django_jinja.backend.Jinja2', # ... 'OPTIONS': { # ... 'extensions': [ # ... 'puente.ext.i18n', # ... ], } } ]
Then Puente will build something like this:
PUENTE = { # ... 'JINJA_CONFIG': { 'extensions': [ # ... 'puente.ext.i18n', # ... ], 'silent': 'False' } }
-
PROJECT
¶ Type: String Default: “PROJECT” Required: No The name of this project. This goes in the
.pot
and.po
files and could help translators know which project this file that they’re translating belongs to.
-
VERSION
¶ Type: String Default: “1.0” Required: No The version of this project. This goes in the
.pot
and.po
files and could help translators know which version of the project this file that they’re translating belongs to.
-
MSGID_BUGS_ADDRESS
¶ Type: String Default: “” Required: No The email address or url to send bugs related to msgids to. Without this, it’s hard for a translator to know how to report issues back. If they have this, then reporting issues is much easier.
You want good strings, so this is a good thing to set.
For example:
PUENTE = { # ... 'MSGID_BUGS_ADDRESS': 'https://bugzilla.mozilla.org/enter_bug.cgi?project=Input' }
Templates¶
We hope you’re using Jinja2’s newstyle gettext and autoescape = True
. If
that’s the case, then these docs will help:
Further, Puente adds support for pgettext
and npgettext
in templates:
{{ pgettext("some context", "message string") }}
{{ npgettext("some context", "singular message", "plural message", 5) }}
FIXME: Expand on this and talk about escaping and |safe
.
Extract and merge usage¶
Message extraction¶
After you’ve configured Puente, you can extract messages like this:
$ ./manage.py extract
This will extract all the strings specified by the DOMAIN_METHODS
setting and put them into a <domain>.pot
file.
Message merge¶
After you’ve extracted messages, you’ll want to merge new messages into
new or existing locale-specific .po
files. You can merge messages
like this:
$ ./manage.py merge