Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

django-admin makemessages: how does it work with txt, xml and other files?

I am in the process of translating a Django app. I have translatable strings in files of a specific extensions (.vue files, but that's not very important for now).

I have to run the makemessages command to parse those strings and generate .po files.

The documentation says:

makemessages: Runs over the entire source tree of the current directory and pulls out all strings marked for translation.

Also, the docs states that the default file extensions are: html, txt, py and gives an example with explicitly defined extentions:

django-admin makemessages --locale=de --extension=html,txt --extension xml

My question is: how is makemessages supposed to work with non-compiled files like txt and xml? How would I mark string for translation in files like that?

I know how to do this in a template or a .py file:

.html

{% trans "Text to be translated" %}

.py

gettext("Text to be translated")
# or
_("Text to be translated")

But what about other extensions? .txt, .xml? ... and eventually .vue?

like image 553
gkpo Avatar asked Mar 05 '23 05:03

gkpo


1 Answers

The makemessages command searches for files to translate and invokes the (x)gettext utility to extract the strings marked for translation. It will behave differently depending on whether you tell it to use the django or djangojs --domain.

When using the django domain, it runs non-.py files through django.utils.translation.templatize to "Turn a Django template into something that is understood by xgettext"). It basically turns the whole file into XXXXX apart from the parts the lexer determines are relevant to gettext, which keeps the line numbers intact, etc.

>>> from django.utils.translation import templatize
>>> content = """This is a {% trans "test" %}!
... {# Translators: these comments remain intact for translators #}
... {% blocktrans %}Only applies to --domain=django and non-.py files {% endblocktrans %}
... 
... Everything else is {# ignored #}
... {% trans "EOM" %}
... :)"""
>>> print(templatize(content))
XXXX XX X  gettext(u'test') X
# Translators: these comments remain intact for translators
 gettext(u'Only applies to --domain=django and non-.py files ') SSSS SSSSSSS SS SSSSSSSSSSSSSSS SSS SSSSSSS SSSSS 

XXXXXXXXXX XXXX XX 
 gettext(u'EOM') 
XX
>>> 

So for most non-Python, non-Django-template files, this will obliterate your translatable content.

When using the djangojs domain, Django won't perform any such preprocessing on the files. (For versions of gettext older than 0.18.3, makemessages would call django.utils.jslex.prepare_js_for_gettext, which is somewhat less aggressive and just tweaks any escaping/regex syntax if necessary).

If you run makemessages -a -d djangojs -e "js,vue", Django will tell xgettext to parse your .js and .vue files with --language=JavaScript and a certain number of extra --keyword configurations to support gettext_noop, gettext_lazy, etc (gettext defaults to a keywordspec of _, gettext, dgettext:2, dcgettext:2, ngettext:1,2, dngettext:2,3, pgettext:1c,2, dpgettext:2c,3 for JavaScript). makemessages will also pass --from-code=UTF-8 and --add-comments=Translators arguments.

It's then over to gettext to do the parsing based on those specifications, and its own understanding of the files based on the --language specified.

Therefore your best bet for translating .txt, .xml, etc files with makemessages is to use the djangojs domain and see what gettext picks up based on --language=JavaScript, so you'd mark your strings as you would for JavaScript.

Or for Jinja2 templates, etc you could use an alternative solution such as Babel's Message Extraction

Or you could even customize makemessages to pass different parameters to gettext based on your requirements.

So for your Vue example...

If your .vue file contains calls to gettext in javascript code sections (default parsing doesn't seem to pick up on gettext calls within template attributes, etc) you should find that makemessages will extract these strings for translation (and compilemessages will generate the required binary files after the .po files are edited).

Then in order to see the translations when the code runs, you'll need to use the Django JavaScript Catalog, so ensure you include something like <script type="text/javascript" src="{% url 'javascript-catalog' %}"></script> in your code so that the gettext etc functions actually exist. (It sounds like you've already got this, but included for the sake of completeness.)

In your .vue files you could use something like:

<script> // trick to ensure xgettext doesn't omit the code below: </closetag> 
    data(){
        const _ = gettext;
        // gettext default JavaScript keywordspec includes "_" shortcut
        // and should extract the strings below by default
        // -- or you could just use gettext('my string') directly
        return {                                                           
            heading: _('This is my translatable heading'),                                               
            button_text: _('Click here'),                                                                                      
        }
    }
</script>

<template>
    <h1>{{ heading }}</h1>
    <button type="button">{{ button_text }}</button>
</template>

-- the strings should be translated based on the currently activated language courtesty of Django's translation mechanism.

like image 195
DrMeers Avatar answered Apr 16 '23 17:04

DrMeers