Angular translation makemessages

Registered by Thai Tran on 2015-05-06

Summary:
An approach to i18n for Angular HTML templates.

Motivation:
We currently have a robust translation layer provided by Django for python, HTML templates, and javascript but none for plain HTML. As we move toward Angular templates (aka plain HTML), we need Django’s makemessages command to pick up this as well.

Description:
Before we talk about what approaches are available to us, we need to understand the currently state of things.

Translation client-side today entails listing translatable strings in the gettext function or the i18n angular module. Even using the module requires us to explicitly use the gettext keyword or equivalent in order for Django’s makemessages to pick it up.

Why do we need to explicitly use the gettext function? Django’s makemessages command basically does the following:
1. Search all files containing JS, HTML, or PY extensions
2. Passes these files to GNU gettext with options
3. GNU xgettext then looks for keywords and generates a PO file

---------------------------------------------------

So if the keyword does not match what GNU xgettext is looking for, it does not get extracted into the PO file. The next logical question is, can we specify our own keywords? The answer is YES but it may not help us. First lets take a look at our options and then we’ll come back and answer this question.

Approach 1:
<html>{$ gettext(“ singular”) $} </html>
This is the easiest approach, we treat this html file like it is a javascript and let xgettext do its work. Not sure how line breaks will factor in though. This solution requires angular to evaluate this expression each time the template gets fetch (or in the worse case, each digest cycle). This also pollutes the global space, which is something we want to avoid in Angular.

Approach 2:
<html>{% trans “string” %}</html>
This uses the existing Django template tags, but it would require this file to be a Django template, which means it requires pre-processing, not something we want. It also does not work in the static folder, as it is not a static resource. We are grouping files based on feature, which means we group HTML, JS, CSS, etc.. into the same folder. Long story short, this approach will not work for us.

Approach 3:
<trans>string</trans>
We use a trans directive to do the translation. This is essentially the same approach as angular-gettext. In fact, we can even emulate the same format as angular-gettext, but will require us to extract the texts via a different mechanism. We still require angular to evaluate this, but its confined and controlled.

Approach 4:
<html>{$ ”string” | trans $}</html>
We use trans as a filter. This approach is similar to Approach 3 with the same benefits and drawbacks. The only real difference is that one is a directive while the other is a filter. I personally prefer this syntax because its cleaner but also fine with Approach 3 since its a format that another opensource project is currently supporting.

---------------------------------------------------

Depending on what approach we pick, we still need a way to extract the translatable texts. As mentioned before, Django already provides a command for doing this, but it only works for JS, Django templates, and PY files. However, we can subclass this command and intercept it so it will extract other things for us. Lets examine the options we have for extracting:

Option 1:
Use the angular-gettext grunt job, this requires us to go with Approach 3. This approach will not use Django’s translation workflow, it is completely independent. Not sure if this can be hook into Django’s catalog object for client-side at all. This is also going to be a problem in the short term because the tool chain requires npm, which is something packagers are reluctant to include. Long story short, this is not going to be a viable strategy.

Option 2:
Use another python tool to extract the strings and generate an independent PO file. The advantage here is that the tool would be python based, but we would still need it to go through infra and requirements. Again, this does not use Django’s translation workflow and not guarantee to support legacy code. You can say, lets just leave the legacy stuff with legacy code and maintain 2 ways for translating client-side, but that is far from desirable.

Option 3:
Pre-process plain HTMLs and generate a temporary JS file to hold translatable texts. Before we run the makemessages command, we look for a certain pattern in our static HTML and extract the translatable texts. We then write it into a JS file wrapped by gettext so that the Django makemessages command will mark it for translation. The advantage is that we continue to use Django’s translation workflow - which is guaranteed to work. The down-side is that we will have to maintain this pre-processing code ourselves. Doing it this way ensures that the Django catalog object gets populated and merged correctly. Yes, merging is something we are doing at the moment. The catalog object you get today is a merged object between horizon and dashboard domain.

Option 4:
Investigate how Django is doing it using the trans and blocktrans tag. This doesn’t sound as bad if you think about it. Template tags eventually invoke some python function somewhere. Since we are intercepting the makemessages command (which is in python), we might be able to use the template tags function to do the dirty work for us. This requires further investigation but something to consider.

---------------------------------------------------

I have listed an exhaustive list of approaches for translation format and options for extracting them. For the record, I am not a Django or xgettext expert, so if I missed something, feel free to correct me in the comments below. The primary goal of this blueprint was to educate and inform others in the community so that we can all be on the same page. Lets have a meaningful discussion and decide on a course of action, looking forward to hearing feedback, thanks!

---------------------------------------------------

UPDATE:

The community is comfortable going with babel plugin architecture for message extraction. Since then, we have made tremendous progress.

1. We have replaced the Django makemessages with babel-django plugin.
2. We have introduced a plugin for extracting angular bable-angular (almost complete).
3. We are pushing angular-gettext through global requirements (work in progress).

UX:
N/A

Outside Dependencies:
N/A

Doc Impact:
Need to update Angular translation section.

Blueprint information

Status:
Complete
Approver:
David Lyle
Priority:
High
Drafter:
Thai Tran
Direction:
Approved
Assignee:
Thai Tran
Definition:
Approved
Series goal:
Accepted for liberty
Implementation:
Implemented
Milestone target:
milestone icon 8.0.0
Started by
Thai Tran on 2015-05-06
Completed by
David Lyle on 2015-07-28

Related branches

Sprints

Whiteboard

Richard Jones [May 29, 2015]

I'm already on the record for preferring markup approach 3 so we can avoid having to write our own implementation of a translation engine. I really, really don't want to see us write our own :/

I'd also like to see us use a minimum-effort approach for extraction and I think approach 2 is that. It gives is a single point to invoke for message catalog maintenance and doesn't require deployers to use a node.js tool for message extraction. I believe Babel (http://babel.pocoo.org/) is the most appropriate tool already existing for extracting the messages. There's already a babel plugin for django, we would just need to write one for angular-gettext. I believe this should allay concerns raised in extraction approach 2.

And if you're going to do extraction approach 3 then you've practically implemented a plugin for Babel so you might as well do approach 2 :)

I've looked into extraction approach 4 and got lost in a twisty maze of ick. I'm not sure there's a clean way to extend Django's makemessage.

Thai Tran [June 1, 2015]

So I took a look at Babel, looks like it does provide some lower level extraction capabilities but looks like it also follows the GNU xgettext format. Unless I missed something, this format will not support Approach 3 or 4. http://babel.pocoo.org/docs/api/messages/extract/?highlight=extract#module-babel.messages.extract

Gerrit topic: https://review.openstack.org/#q,topic:bp/angular-translate,n,z

Addressed by: https://review.openstack.org/187321 -- Abandoned
    Angular translation makemessages for singular

Gerrit topic: https://review.openstack.org/#q,topic:bp/angular-translate-makemessages,n,z

Addressed by: https://review.openstack.org/192035 -- Merged
    Angular translation via babel (singular only)

Gerrit topic: https://review.openstack.org/#q,topic:bp/ng-translate,n,z

Addressed by: https://review.openstack.org/197244 -- Merged
    Adding Angular-gettext to requirements

Addressed by: https://review.openstack.org/201700 -- Merged
    Add plurals and comments to angular i18n

Addressed by: https://review.openstack.org/201894 -- Merged
    Add extraction of filter-based translations

Addressed by: https://review.openstack.org/205591
    Revert "Adding Angular-gettext to requirements"

(?)

Work Items

This blueprint contains Public information 
Everyone can see this information.

Subscribers

No subscribers.