2016-08-16 17:27:37 -07:00
|
|
|
|
======================
|
|
|
|
|
Translation in Horizon
|
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
What is the point of translating my code?
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
You introduced an awesome piece of code and revel in your glorious
|
|
|
|
|
accomplishment. Suddenly your world comes crashing down when a core hands you
|
|
|
|
|
a -1 because your code is not translated. What gives?
|
|
|
|
|
|
|
|
|
|
If you are writing software for a global audience, you must ensure that it is
|
|
|
|
|
translated so that other people around the world are able to use it. Adding
|
|
|
|
|
translation to your code is not that hard and a requirement for horizon.
|
|
|
|
|
|
|
|
|
|
If you are interested in contributing translations, you may want to investigate
|
|
|
|
|
`Zanata <https://translate.openstack.org>`_ and the
|
|
|
|
|
`upstream translations <http://docs.openstack.org/developer/i18n/>`_.
|
|
|
|
|
You can visit the internationalization project IRC channel **#openstack-i18n**,
|
|
|
|
|
if you need further assistance.
|
|
|
|
|
|
|
|
|
|
Overview and Architecture
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
You can skip this section if you are only interested in learning how to use
|
|
|
|
|
translation. This section explains the two main components to translation:
|
|
|
|
|
message extraction and message substitution. We will briefly go over what each
|
|
|
|
|
one does for translation as a whole.
|
|
|
|
|
|
|
|
|
|
Message Extraction
|
|
|
|
|
------------------
|
|
|
|
|
|
|
|
|
|
.. The source can be found at:
|
|
|
|
|
https://drive.google.com/open?id=0B5nlaOV3OEj5MTNMdG9WV1RiVEU
|
|
|
|
|
|
|
|
|
|
.. figure:: ../images/message_extraction.png
|
|
|
|
|
:width: 80%
|
|
|
|
|
:align: center
|
|
|
|
|
:alt: Message extraction diagram
|
|
|
|
|
|
|
|
|
|
Message extraction is the process of collecting translatable strings from the
|
|
|
|
|
code. The diagram above shows the flow of how messages are extracted and then
|
|
|
|
|
translated. Lets break this up into steps we can follow:
|
|
|
|
|
|
|
|
|
|
1. The first step is to mark untranslated strings so that the extractor is able
|
|
|
|
|
to locate them. Refer to the guide below on how to use translation and what
|
|
|
|
|
these markers look like.
|
|
|
|
|
|
2016-10-06 14:27:22 +01:00
|
|
|
|
2. Once marked, we can then run ``tox -e manage -- extract_messages``, which
|
2016-08-16 17:27:37 -07:00
|
|
|
|
searches the codebase for these markers and extracts them into a Portable
|
|
|
|
|
Object Template (POT) file. In horizon, we extract from both the ``horizon``
|
|
|
|
|
folder and the ``openstack_dashboard`` folder. We use the AngularJS extractor
|
|
|
|
|
for JavaScript and HTML files and the Django extractor for Python and Django
|
|
|
|
|
templates; both extractors are Babel plugins.
|
|
|
|
|
|
2016-10-06 14:27:22 +01:00
|
|
|
|
3. To update the .po files, you can run ``tox -e manage -- update_catalog`` to
|
|
|
|
|
update the .po file for every language, or you can specify a specific
|
|
|
|
|
language to update like this: ``tox -e manage -- update_catalog de``. This
|
|
|
|
|
is useful if you want to add a few extra translatabale strings for a
|
|
|
|
|
downstream customisation.
|
|
|
|
|
|
2016-08-16 17:27:37 -07:00
|
|
|
|
.. Note ::
|
|
|
|
|
|
|
|
|
|
When pushing code upstream, the only requirement is to mark the strings
|
|
|
|
|
correctly. All creation of POT and PO files is handled by a daily upstream
|
|
|
|
|
job. Further information can be found in the
|
2016-08-25 21:24:46 +09:00
|
|
|
|
`translation infrastructure documentation
|
2016-08-16 17:27:37 -07:00
|
|
|
|
<http://docs.openstack.org/developer/i18n/infra.html>`_.
|
|
|
|
|
|
|
|
|
|
Message Substitution
|
|
|
|
|
--------------------
|
|
|
|
|
|
|
|
|
|
.. The source can be found at:
|
|
|
|
|
https://drive.google.com/open?id=0B5nlaOV3OEj5UHZCNmFGT0lPQVU
|
|
|
|
|
|
|
|
|
|
.. figure:: ../images/message_substitution.png
|
|
|
|
|
:width: 80%
|
|
|
|
|
:align: center
|
|
|
|
|
:alt: Message substitution diagram
|
|
|
|
|
|
|
|
|
|
Message substitution is not the reverse process of message extraction. The
|
|
|
|
|
process is entirely different. Lets walk through this process.
|
|
|
|
|
|
|
|
|
|
* Remember those markers we talked about earlier? Most of them are functions
|
|
|
|
|
like gettext or one of its variants. This allows the function to serve a dual
|
|
|
|
|
purpose - acting as a marker and also as a replacer.
|
|
|
|
|
|
|
|
|
|
* In order for translation to work properly, we need to know the user’s locale.
|
|
|
|
|
In horizon, the user can specify the locale using the Settings panel. Once we
|
|
|
|
|
know the locale, we know which Portable Object (PO) file to use. The PO file
|
|
|
|
|
is the file we received from translators in the message extraction process.
|
|
|
|
|
The gettext functions that we wrapped our code around are then able to
|
|
|
|
|
replace the untranslated strings with the translated one by using the
|
|
|
|
|
untranslated string as the message id.
|
|
|
|
|
|
|
|
|
|
* For client-side translation, Django embeds a corresponding Django message
|
|
|
|
|
catalog. Javascript code on the client can use this catalog to do string
|
|
|
|
|
replacement similar to how server-side translation works.
|
|
|
|
|
|
|
|
|
|
If you are setting up a project and need to know how to make it translatable,
|
|
|
|
|
please refer to `this guide
|
|
|
|
|
<http://docs.openstack.org/infra/manual/creators.html#enabling-translation-infrastructure>`_.
|
|
|
|
|
|
|
|
|
|
.. _making_strings_translatable:
|
|
|
|
|
|
|
|
|
|
Making strings translatable
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
To make your strings translatable, you need to mark it so that horizon can
|
|
|
|
|
locate and extract it into a POT file. When a user from another locale visits
|
|
|
|
|
your page, your string is replaced with the correct translated version.
|
|
|
|
|
|
|
|
|
|
In Django
|
|
|
|
|
---------
|
|
|
|
|
|
|
|
|
|
To translate a string, simply wrap one of the gettext variants around the
|
|
|
|
|
string. The examples below show you how to do translation for various
|
|
|
|
|
scenarios, such as interpolation, contextual markers and translation comments.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
from django.utils.translation import pgettext
|
|
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
|
from django.utils.translation import ungettext
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IndexView(request):
|
|
|
|
|
|
|
|
|
|
# Single example
|
|
|
|
|
_("Images")
|
|
|
|
|
|
|
|
|
|
# Plural example
|
|
|
|
|
ungettext(
|
|
|
|
|
'there is %(count)d object',
|
|
|
|
|
'there are %(count)d objects',
|
|
|
|
|
count) % { 'count': count }
|
|
|
|
|
|
|
|
|
|
# Interpolated example
|
|
|
|
|
mood = ‘wonderful’
|
|
|
|
|
output = _('Today is %(mood)s.') % mood
|
|
|
|
|
|
|
|
|
|
# Contextual markers
|
|
|
|
|
pgettext("the month name", "May")
|
|
|
|
|
|
|
|
|
|
# Translators: This message appears as a comment for translators!
|
|
|
|
|
ugettext("Welcome translators.")
|
|
|
|
|
|
|
|
|
|
.. Note ::
|
|
|
|
|
|
|
|
|
|
In the example above, we imported ``ugettext`` as ``_``. This is a common
|
|
|
|
|
alias for gettext or any of its variants. In Django, you have to explicitly
|
|
|
|
|
spell it out with the import statement.
|
|
|
|
|
|
|
|
|
|
In Django templates
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
To use translation in your template, make sure you load the i18n module. To
|
|
|
|
|
translate a line of text, use the ``trans`` template tag. If you need to
|
|
|
|
|
translate a block of text, use the ``blocktrans`` template tag.
|
|
|
|
|
|
|
|
|
|
Sometimes, it is helpful to provide some context via the ``comment`` template
|
|
|
|
|
tag. There a number of other tags and filters at your disposal should you need
|
|
|
|
|
to use them. For more information, see the
|
|
|
|
|
`Django docs <https://docs.djangoproject.com/en/1.8/topics/i18n/translation/>`_
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
{% extends 'base.html' %}
|
|
|
|
|
{% load i18n %}
|
|
|
|
|
{% block title %}
|
|
|
|
|
{% trans "Images" %}
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block main %}
|
|
|
|
|
{% comment %}Translators: Images is an OpenStack resource{% endcomment %}
|
|
|
|
|
{% blocktrans with amount=images.length %}
|
|
|
|
|
There are {{ amount }} images available for display.
|
|
|
|
|
{% endblocktrans %}
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
In JavaScript
|
|
|
|
|
-------------
|
|
|
|
|
|
|
|
|
|
The Django message catalog is injected into the front-end. The gettext function
|
|
|
|
|
is available as a global function so you can just use it directly. If you are
|
|
|
|
|
writing AngularJS code, we prefer that you use the gettext service, which is
|
|
|
|
|
essentially a wrapper around the gettext function.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
Angular
|
|
|
|
|
.module(…)
|
|
|
|
|
.controller(myCtrl);
|
|
|
|
|
|
|
|
|
|
myCtrl.$inject = [‘horizon.framework.util.i18n.gettext’];
|
|
|
|
|
function myCtrl(gettext) {
|
|
|
|
|
var translated = gettext(‘Images’);
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-31 18:44:40 +09:00
|
|
|
|
.. warning ::
|
2016-08-16 17:27:37 -07:00
|
|
|
|
|
|
|
|
|
For localization in AngularJS files, use the
|
|
|
|
|
AngularJS service ``horizon.framework.util.i18n.gettext``. Ensure that the
|
|
|
|
|
injected dependency is named ``gettext`` or ``nggettext``. If you do not do this,
|
|
|
|
|
message extraction will not work properly!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
In AngularJS templates
|
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
|
|
To use translation in your AngularJS template, use the translate tag or the
|
|
|
|
|
translate filter. Note that we are using
|
|
|
|
|
`angular-gettext <https://angular-gettext.rocketeer.be/>`_
|
|
|
|
|
for message substitution but not for message extraction.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
|
|
|
|
<translate>Directive example</translate>
|
|
|
|
|
<div translate>Attribute example</div>
|
|
|
|
|
<div translate>Interpolated {{example}}</div>
|
|
|
|
|
<span>{$ ‘Filter example’|translate $}</span>
|
|
|
|
|
|
|
|
|
|
<span translate>
|
|
|
|
|
This <em>is</em> a <strong>bad</strong> example
|
|
|
|
|
because it contains HTML and makes it harder to translate.
|
|
|
|
|
However, it will still translate.
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
.. Note ::
|
|
|
|
|
|
2016-08-25 21:24:46 +09:00
|
|
|
|
The annotations in the example above are guaranteed to work. However, not all of
|
|
|
|
|
the angular-gettext annotations are supported because we wrote our own custom
|
2016-08-16 17:27:37 -07:00
|
|
|
|
babel extractor. If you need support for the annotations, ask on IRC in the
|
|
|
|
|
#openstack-horizon room or report a bug. Also note that you should avoid embedding
|
|
|
|
|
HTML fragments in your texts because it makes it harder to translate. Use your
|
|
|
|
|
best judgement if you absolutely need to include HTML.
|
|
|
|
|
|
|
|
|
|
.. _pseudo_translation:
|
|
|
|
|
|
|
|
|
|
Pseudo translation tool
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The pseudo translation tool can be used to verify that code is ready to be
|
|
|
|
|
translated. The pseudo tool replaces a language's translation with a complete,
|
|
|
|
|
fake translation. Then you can verify that your code properly displays fake
|
|
|
|
|
translations to validate that your code is ready for translation.
|
|
|
|
|
|
|
|
|
|
Running the pseudo translation tool
|
|
|
|
|
-----------------------------------
|
|
|
|
|
|
2016-10-06 14:27:22 +01:00
|
|
|
|
#. Make sure your .pot files are up to date:
|
|
|
|
|
``tox -e manage -- extract_messages``
|
2016-08-16 17:27:37 -07:00
|
|
|
|
#. Run the pseudo tool to create pseudo translations. For example, to replace
|
|
|
|
|
the German translation with a pseudo translation:
|
2016-10-06 14:27:22 +01:00
|
|
|
|
``tox -e manage -- update_catalog de --pseudo``
|
|
|
|
|
#. Compile the catalog: ``tox -e manage -- compilemessages``
|
2016-08-16 17:27:37 -07:00
|
|
|
|
#. Run your development server.
|
|
|
|
|
#. Log in and change to the language you pseudo translated.
|
|
|
|
|
|
|
|
|
|
It should look weird. More specifically, the translatable segments are going
|
|
|
|
|
to start and end with a bracket and they are going to have some added
|
|
|
|
|
characters. For example, "Log In" will become "[~Log In~您好яшçあ]"
|
|
|
|
|
This is useful because you can inspect for the following, and consider if your
|
|
|
|
|
code is working like it should:
|
|
|
|
|
|
|
|
|
|
* If you see a string in English it's not translatable. Should it be?
|
|
|
|
|
* If you see brackets next to each other that might be concatenation. Concatenation
|
|
|
|
|
can make quality translations difficult or impossible. See
|
2016-08-25 21:24:46 +09:00
|
|
|
|
`"Use string formatting variables, never perform string concatenation"
|
2016-08-16 17:27:37 -07:00
|
|
|
|
<https://wiki.openstack.org/wiki/I18n/TranslatableStrings#Use_string_formating_variables.2C_never_perform_string_concatenation>`_
|
|
|
|
|
for additional information.
|
|
|
|
|
* If there is unexpected wrapping/truncation there might not be enough
|
|
|
|
|
space for translations.
|
|
|
|
|
* If you see a string in the proper translated language, it comes from an
|
|
|
|
|
external source. (That's not bad, just sometimes useful to know)
|
|
|
|
|
* If you get new crashes, there is probably a bug.
|
|
|
|
|
|
|
|
|
|
Don't forget to remove any pseudo translated ``.pot`` or ``.po`` files.
|
|
|
|
|
Those should not be submitted for review.
|