django-admin makemessages --no-obsolete doesn't seem to be working - django

First of all, I am expecting --no-obsolete would comment out msgid and msgstr if gettext is deleted, right?
How I am testing is:
I wrote gettext("some string here") in view
I ran makemessages command
It wrote a .po file as expected
Then I deleted gettext() line from view and saved file, verified runserver working.
I ran makemessages --no-obsolete and it has not made any changes to .po file.
.po file content extract .
#. Translators: This message is a test of wrap line
#: servers/views.py:31
msgid "Do let me know if it works."
msgstr ""
dev environment
Django = 1.11
OS = Mac/Ubuntu 14.04
settings.py
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LOCALE = (
os.path.join(os.path.dirname(__file__), "locale"),
)

Now with the help of Julien and Tarun, I found following observations.
python manage.py makemessages -l <locale>
If there is no gettext in the file being processed, the above command won't write/update .po file. That means if the corresponding .po file earlier had entries for msgstr and msgid, then it won't remove those entries unless file being processed had at least one gettext.
Note: Above behavior is irrespective of --no-obsolete
Now to make the --no-obsolete work as expected we need to follow the steps below.
First thing run python manage.py makemessages -l <locale>, this would write .po file with msgid and msgstr.
Now set msgstr and run python manage.py compilemessages -l <locale>. This command writes .mo file in the same directory as .po file.
Now next time when you run makemessages again (without --no-obsolete), .po and .mo files are compared and missing/deleted gettext are commented in .po file.
And when you run makemessages --no-obsolete, commented entries are removed from the .po file.
E.g
if you have 3 gettext entries, and you run makemessages first time, it would write 3 msgid and 3 msgstr in .po file. Now if you remove all gettext entries, .po file won't be updated after you run makemessages again, but if your keep at least 1 gettext entry in same file and run makemessages again, it would delete all msgid and msgstr for deleted gettext entries.
But if you run compilemessages after makemessages, .mo file is created and then for subsequent makemessages commands .po and .mo files are compared and then msgid and msgstr is commented in .po file for deleted gettext entries.
Then finally when you run makemessages with --no-obsolete option the commented messages from .po files are deleted permanently.

What the --no-obsolete does is to run a command called msgattrib with the --no-obsolete option on the content the po file. A typical case would be you generate your po file with makemessages, you get this:
#: servers/views.py:31
msgid "Do let me know if it works."
msgstr ""
Then you translate:
#: servers/views.py:31
msgid "Do let me know if it works."
msgstr "translation"
Then you remove the gettext entry, it'll still by default keep the translation, but mark it as obsolete.
#: servers/views.py:31
#~msgid "Do let me know if it works."
#~msgstr "translation"
If you set --no-obsolete option, then once your po file is done, it'll run msgattr with no-obsolete option. This will remove lines tagged with #~. See https://linux.die.net/man/1/msgattrib
But, the way makemessages is built, is that this will be called once the po file is written. But if there are no gettext in the files being processed, then it won't write to the po file. It'll just stop before getting to this msgattrib command. The po file you see is the one generated by the previous makemessages command. So the no-obsolete won't do anything.
There's no real solution to this. the no-obsolete option doesn't deal with the cases where you don't have any gettext to process.

So I think #JulienGrégoire was right about the fact that if there is no translation processed then the --no-obsolete won't work. There needs to be at least one translation captured for --no-obsolete to work.
But the solution to this quite simple. You can update your settings.py to define LANGUAGES like below
from django.utils.translation import ugettext_lazy as _
LANGUAGES = (
('en', _('English')),
('fr', _('French')),
)
Now your settings will always generate a translation. So it will make sure that you get --no-obsolete working every time you use it

Related

French translation raises "ValueError('invalid token in plural form: %s' % value)"

I want to handle a french version of my website.
I use Django 2.2 with i18n and I already set locale variables in settings.py.
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGES = (
('en', _('English')),
('fr', _('French')),
('it', _('Italian')),
('es', _('Spanish')),
)
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
TIME_ZONE = 'Europe/Paris'
USE_I18N = True
USE_L10N = True
USE_TZ = True
When I use ./manage.py makemessages -l fr, I correctly have a django.po french file but after ./manage.py compilemessages -l fr the server crashes with the following error (trimed) :
File "/usr/lib/python3.7/gettext.py", line 93, in _tokenize
raise ValueError('invalid token in plural form: %s' % value)
ValueError: invalid token in plural form: EXPRESSION
English, Italian and Spanish translations work well
EDIT : Well, the issue has been resolved, but I'm not really sure how. I deleted my venv, recreated it and french translation suddenly worked. Upgrading from Django 2.2.1 to 2.2.2 may be what caused the resolution.
For other language facing this error:
There exists a line that tells Django evaluating this expression, decide which form of the word it should use, and for some languages, this expression is not written, e.g. Farsi.
For these languages, a default line is written in your main .po file(not the specific ones):
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
Here the EXPRESSION part should be changed to your language.
HERE you can read the exact format of EXPRESSION, but for short if your language has just two forms for singular and plural form change the line to this:
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
And recompile your messages.
I had the same issue.
The reason was that i accidentally wrote a word in messages.po file in configuration lines (that are on top of .po file, containing "Project-Id-Version", "Plural-Forms", ...)
So i checked what changes i made with my VSC (git) and it was fixed.
Don't forget to recompile your .mo files

How to customize folder name when compilemessages in website insternationalization

For website Internationalization when run this command
django-admin compilemessages -l de
it produce the .po file under the locale folder like
locale/de/LC_MESSAGES/django.po
I want to rename LC_MESSAGES
How can I do this ???
Please help me
You should add a code snippet to rename the directory, this is the hard way but unfortunately, I don't know a better one.
import os
basedir = 'locale/de/' # Or you should add something like C:/locale/de
newname = 'Your script'
os.rename(os.path.join(basedir, newname)

django-admin.py makemessages creates no files

I'm trying to enable translations for a django project and django-admin.py makemessages -l de doesn't seem to create any .po files, although there is a couple of {% trans ... %} in templates and a couple of gettext(...) in models for tests.
Accorgin to the documentation https://docs.djangoproject.com/en/1.7/ref/django-admin/#django-admin-makemessages, the command should search for translations in the whole project tree and create corresponding files in e.g. conf/locale directory if no setting is specified.
The only output I get is processing locale ru.
Any way to debug it or maybe well known pitfalls that I didn't find in google?
Let me answer my own question :)
The problem was that I:
Didn't import gettext as _ but did import gettext as t, thus makemessage didn't recognize translated strings in .py files
Tried to translate non-existing variables in templates instead of strings. {% trans some_var %} instead of {% trans "some_string" %}
Make sure you have the following settings available in your settings.py file:
DJANGO_ROOT = dirname(dirname(abspath(__file__)))
SITE_ROOT = dirname(DJANGO_ROOT)
USE_I18N = True
LOCALE_PATHS = (
SITE_ROOT + '/locale',
)
If the LOCALE_PATHS value is not set it does not know where it should create the locale directories and translation files, also you don't have to have the DJANGO_ROOT and SITE_ROOT values that's just for ease of use.

Django: mixed translations issue

I configured my application to support 2 languages: english and italian.
In my settings.py I specified i18n-related stuff as following:
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = (
('en', 'ENGLISH'),
('it', 'ITALIAN'),
)
then I created a "locale" folder for each application and used the following commands to generate .po/.mo files:
django-admin.py makemessages -l en --no-location --no-obsolete
django-admin.py makemessages -l it --no-location --no-obsolete
django-admin.py compilemessages
All works fine, but I have a template where the two translations get mixed up (ie: part of the texts are in italian and other in english). I think that the problem is related to how my browser (Chrome) sends language headers (currently: Accept-Language: it,en-US;q=0.8,en;q=0.6).
Other browser on my machine like Firefox are sending: Accept-Language: en-US,en;q=0.5
Ok, this may be a personal problem related to a "strange browser configuration", but is not acceptable to have such result... how can I avoid this issue and have a coherent translation?
Is it possible that there is some content with origin from DB and thus not provided with translations?
On the other hand it may be the problem in rendering multiline fields in your .po files or .html files (make sure everything is {% trans ... %} wrapped.
Lastly if in Chrome you ask for translation in English when you are in the Italian site, does it provide you with all English content and vice verso?

Django makemessages ignore switch doesn't work for me

I have problems localizing a django-nonrel project, which is deployed to GAE. Because of GAE I have to put everything into my project folder, so it looks like something like this
project
+ django
+ dbindexer
+ registration
+ myapp
...
+ locale
+ templates
I have strings to localize in templates directory, and in the myapp directory.
When I run python manage.py makemessages -l en --ignore django\* from the project dir it crawl through all the directories of the project, including django, so I get a quite big po file. My strings from the templates are there, along with all of the strings from django directory.
after --ignore ( or just -i ) I tried to pu django django/* , but nothing changed.
Any ideas?
./manage.py help makemessages
-i PATTERN, --ignore=PATTERN
Ignore files or directories matching this glob-style
pattern. Use multiple times to ignore more.
I have just tested it, and this command successfully ignored my application:
./manage.py makemessages -l da -i "django*"
But beware that before you test it, you should delete the old .po file, as I think it will not automatically remove the translation lines from your previous makemessages execution.
The problem is with the pattern - maybe the shell was expanding it for you.
In general - it is good to avoid path separators (whether / or \) in the pattern.
If you need to always pass specific options to the makemessages command, you could consider your own wrapper, like this one, which I use myself:
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
help = "Scan i18n messages without going into externals."
def handle(self, *args, **options):
call_command('makemessages',
all=True,
extensions=['html', 'inc'],
ignore_patterns=['externals*'])
This saves you typing, and gives a common entry point for scanning messages across the project (your translator colleague will not destroy translations by missing out some parameter).
Don't delete the old .po file, once you have cleared it from the totally unwanted (i.e. - those from 'django' directory) messages. This allows gettext to recycle old unused messages, once they are used again (or simmilar ones, which will be marked as #, fuzzy.
Edit - as mt4x noted - the wrapper above doesn't allow for passing the options to the wrapped command. This is easy to fix:
from django.core.management import call_command
from django.core.management.commands.makemessages import (
Command as MakeMessagesCommand
)
class Command(MakeMessagesCommand):
help = "Scan i18n messages without going into externals."
def handle(self, *args, **options):
options['all'] = True
options['extensions'] = ['html', 'inc']
if 'ignore_patterns' not in options:
options['ignore_patterns'] = []
options['ignore_patterns'] += ['externals*']
call_command('makemessages', **options)
Thus - you can fix what needs to be fixed, and flex the rest.
And this needs not be blind override like above, but also some conditional edit of the parameters passed to the command - appending something to a list or only adding it when it's missing.