The site is fully localizable. Localization files are not shipped with the code distribution, but are available on SVN:
git svn clone https://svn.mozilla.org/projects/mozilla.com/trunk/locales/ locale # or svn checkout https://svn.mozilla.org/projects/mozilla.com/trunk/locales/ locale
Bedrock supports a workflow similar to gettext. You extract all the strings from the codebase, then merge them into each locale to get them translated.
The files containing the strings are called ".lang files" and end with
a .lang
extension.
To extract all the strings from the codebase, run:
$ ./manage.py l10n_extract
That will use gettext to get all the needed localizations from python
and html files, and will convert the result into a bunch of .lang
files inside locale/templates
. This directory represents the
"reference" set of strings to be translated, and you are free to
modify or split up .lang files here as needed (just make sure they are
being reference correctly, from the code, see
:ref:`Which .lang file should it use? <which-lang>`).
To merge new strings into locale directories, run:
$ ./manage.py l10n_merge
If you want to merge only specific locales, you can pass any number of them as arguments:
$ ./manage.py l10n_merge fr de
To translate a string from a .lang file, simply use the gettext interface.
In a jinja2 template:
<div>{{ _('Hello, how are you?') }}<div> <div>{{ _('<a href="%s">Click here</a>')|format('http://mozilla.org/') }}</div> <div>{{ _('<a href="%(url)s">Click here</a>')|format(url='http://mozilla.org/') }}</div>
Note the usage of variable substitution in the latter examples. It is important not to hardcode URLs or other parameters in the string. jinja's format filter lets us apply variables outsite of the string.
There's another way to translate content within jinja2 templates. If you need a big chunk of content translated, you can put it all inside a trans block.
{% trans %} <div>Hello, how are you</div> {% endtrans %} {% trans url='http://mozilla.org' %} <div><a href="{{ url }}">Click here</a></div> {% endtrans %}
Note that it also allows variable substitution by passing variables into the block and using template variables to apply them.
Translated strings are split across several .lang files to make it easier to manage separate projects and pages. So how does the system know which one to use when translating a particular string?
- All translations from Python files are put into main.lang. This should be a very limited set of strings and most likely should be available to all pages.
- Templates always load in main.lang, base.lang, and newsletter.lang
- Additionally, each template has its own .lang file, so a template at mozorg/firefox.html would use the .lang file at <locale>/mozorg/firefox.lang.
- Templates can override which lang files are loaded. The above 3 global ones are always loaded, but instead of loading <locale>/mozorg/firefox.lang, the template can specify a list of additional lang files to load with a template block:
{% add_lang_files "foo" "bar" %}
That will make the page load foo.lang and bar.lang in addition to main.lang, base.lang, and newsletter.lang.
When strings are extracted from a template, that are added to the template-specific .lang file. If the template explicitly specifies .lang files like above, it will add the strings to the first .lang file specified, so extracted strings from the above template would go into foo.lang.
Bedrock also has a block-based translation system that works like the
{% block %}
template tag, and marks large sections of translatable
content. This should not be used very often; lang files are the
preferred way to translate content. However, there may be times when
you want to control a large section of a page and customize it
without caring very much about future updates to the English page.
Let's look at how we would translate an example file from English to German.
The English source template, created by a developer, lives under apps/appname/templates/appname/example.html and looks like this:
{% extends "base.html" %}
{% block content %}
<img src="someimage.jpg">
{% l10n foo, 20110801 %}
<h1>Hello world!</h1>
{% endl10n %}
<hr>
{% l10n bar, 20110801 %}
<p>This is an example!</p>
{% endl10n %}
{% endblock %}
The l10n
blocks mark content that should be localized.
Realistically, the content in these blocks would be much larger. For a
short string like above, please use lang files. We'll use this trivial
code for our example though.
The l10n
blocks are named and tagged with a date (in ISO format).
The date indicates the time that this content was updated and needs to
be translated. If you are changing trivial things, you shouldn't
update it. The point of l10n blocks is that localizers completely
customize the content, so they don't care about small updates.
However, you may add something important that needs to be added in the
localized blocks; hence, you should update the date in that case.
When the command ./manage.py l10n_extract
is run, it generates
the corresponding files in the locale
folder (see below for more
info on this command).
The german version of this template is created at
locale/de/templates/appname/example.html
. The contents of it are:
{% extends "appname/example.html" %}
{% l10n foo %}
<h1>Hello world!</h1>
{% endl10n %}
{% l10n bar %}
<p>This is an example!</p>
{% endl10n %}
This file is an actual template for the site. It extends the main template and contains a list of l10n blocks which override the content on the page.
The localizer just needs to translate the content in the l10n blocks.
When the reference template is updated with new content and the date is updated on an l10n block, the generated l10n file will simply add the new content. It will look like this:
{% extends "appname/example.html" %}
{% l10n foo %}
<h1>This is an English string that needs translating.</h1>
{% was %}
<h1>Dies ist ein English string wurde nicht.</h1>
{% endl10n %}
{% l10n bar %}
<p>This is an example!</p>
{% endl10n %}
Note the was
block in foo
. The old translated content is in
there, and the new content is above it. The was
content is always
shown on the site, so the old translation still shows up. The
localizer needs to update the translated content and remove the ``was`
block.
./manage.py l10n_check
This command will check which blocks need to be translated and update the locale templates with needed translations. It will copy the English blocks into the locale files if a translation is needed.
You can specify a list of locales to update:
./mange.py l10n_check fr ./mange.py l10n_check fr de es