diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py
index f3cc6348f6..803bbb746a 100644
--- a/django/utils/translation/__init__.py
+++ b/django/utils/translation/__init__.py
@@ -21,6 +21,11 @@ __all__ = [
'npgettext', 'npgettext_lazy',
]
+
+class TranslatorCommentWarning(SyntaxWarning):
+ pass
+
+
# Here be dragons, so a short explanation of the logic won't hurt:
# We are trying to solve two problems: (1) access settings, in particular
# settings.USE_I18N, as late as possible, so that modules can be imported
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index cf6270cc0c..8014b5ea3a 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -7,6 +7,7 @@ import re
import sys
import gettext as gettext_module
from threading import local
+import warnings
from django.utils.importlib import import_module
from django.utils.encoding import force_str, force_text
@@ -14,6 +15,7 @@ from django.utils._os import upath
from django.utils.safestring import mark_safe, SafeData
from django.utils import six
from django.utils.six import StringIO
+from django.utils.translation import TranslatorCommentWarning
# Translations are cached in a dictionary for every language+app tuple.
@@ -41,6 +43,7 @@ accept_language_re = re.compile(r'''
language_code_prefix_re = re.compile(r'^/([\w-]+)(/|$)')
+
def to_locale(language, to_lower=False):
"""
Turns a language name (en-us) into a locale name (en_US). If 'to_lower' is
@@ -468,6 +471,9 @@ def templatize(src, origin=None):
plural = []
incomment = False
comment = []
+ lineno_comment_map = {}
+ comment_lineno_cache = None
+
for t in Lexer(src, origin).tokenize():
if incomment:
if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment':
@@ -529,7 +535,27 @@ def templatize(src, origin=None):
plural.append(contents)
else:
singular.append(contents)
+
else:
+ # Handle comment tokens (`{# ... #}`) plus other constructs on
+ # the same line:
+ if comment_lineno_cache is not None:
+ cur_lineno = t.lineno + t.contents.count('\n')
+ if comment_lineno_cache == cur_lineno:
+ if t.token_type != TOKEN_COMMENT:
+ for c in lineno_comment_map[comment_lineno_cache]:
+ filemsg = ''
+ if origin:
+ filemsg = 'file %s, ' % origin
+ warn_msg = ("The translator-targeted comment '%s' "
+ "(%sline %d) was ignored, because it wasn't the last item "
+ "on the line.") % (c, filemsg, comment_lineno_cache)
+ warnings.warn(warn_msg, TranslatorCommentWarning)
+ lineno_comment_map[comment_lineno_cache] = []
+ else:
+ out.write('# %s' % ' | '.join(lineno_comment_map[comment_lineno_cache]))
+ comment_lineno_cache = None
+
if t.token_type == TOKEN_BLOCK:
imatch = inline_re.match(t.contents)
bmatch = block_re.match(t.contents)
@@ -586,7 +612,10 @@ def templatize(src, origin=None):
else:
out.write(blankout(p, 'F'))
elif t.token_type == TOKEN_COMMENT:
- out.write(' # %s' % t.contents)
+ if t.contents.lstrip().startswith(TRANSLATOR_COMMENT_MARK):
+ lineno_comment_map.setdefault(t.lineno,
+ []).append(t.contents)
+ comment_lineno_cache = t.lineno
else:
out.write(blankout(t.contents, 'X'))
return force_str(out.getvalue())
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index e0c07c40fe..79fa3ffb86 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -47,6 +47,26 @@ Backwards incompatible changes in 1.6
should review it as ``type='text'`` widgets might be now output as
``type='email'`` or ``type='url'`` depending on their corresponding field type.
+* Extraction of translatable literals from templates with the
+ :djadmin:`makemessages` command now correctly detects i18n constructs when
+ they are located after a ``{#`` / ``#}``-type comment on the same line. E.g.:
+
+ .. code-block:: html+django
+
+ {# A comment #}{% trans "This literal was incorrectly ignored. Not anymore" %}
+
+* (Related to the above item.) Validation of the placement of
+ :ref:`translator-comments-in-templates` specified using ``{#`` / ``#}`` is now
+ stricter. All translator comments not located at the end of their respective
+ lines in a template are ignored and a warning is generated by
+ :djadmin:`makemessages` when it finds them. E.g.:
+
+ .. code-block:: html+django
+
+ {# Translators: This is ignored #}{% trans "Translate me" %}
+ {{ title }}{# Translators: Extracted and associated with 'Welcome' below #}
+
{% trans "Welcome" %}
+
.. warning::
In addition to the changes outlined in this section, be sure to review the
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 01f168bc10..3cf08e7ddf 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -142,14 +142,22 @@ preceding the string, e.g.::
# Translators: This message appears on the home page only
output = ugettext("Welcome to my site.")
-This also works in templates with the :ttag:`comment` tag:
+The comment will then appear in the resulting ``.po`` file associated with the
+translatable contruct located below it and should also be displayed by most
+translation tools.
-.. code-block:: html+django
+.. note:: Just for completeness, this is the corresponding fragment of the
+ resulting ``.po`` file:
- {% comment %}Translators: This is a text of the base template {% endcomment %}
+ .. code-block:: po
-The comment will then appear in the resulting ``.po`` file and should also be
-displayed by most translation tools.
+ #. Translators: This message appears on the home page only
+ # path/to/python/file.py:123
+ msgid "Welcome to my site."
+ msgstr ""
+
+This also works in templates. See :ref:`translator-comments-in-templates` for
+more details.
Marking strings as no-op
------------------------
@@ -620,6 +628,63 @@ markers` using the ``context`` keyword:
{% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}
+.. _translator-comments-in-templates:
+
+Comments for translators in templates
+-------------------------------------
+
+Just like with :ref:`Python code `, these notes for
+translators can be specified using comments, either with the :ttag:`comment`
+tag:
+
+.. code-block:: html+django
+
+ {% comment %}Translators: View verb{% endcomment %}
+ {% trans "View" %}
+
+ {% comment %}Translators: Short intro blurb{% endcomment %}
+