diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py index f6755fd465..8c73d760af 100644 --- a/django/core/management/commands/makemessages.py +++ b/django/core/management/commands/makemessages.py @@ -196,7 +196,7 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False, 'xgettext -d %s -L Perl %s --keyword=gettext_noop ' '--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 ' '--keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 ' - '--from-code UTF-8 -o - "%s"' % ( + '--from-code UTF-8 --add-comments=Translators -o - "%s"' % ( domain, wrap, os.path.join(dirpath, thefile) ) ) @@ -240,8 +240,9 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False, '--keyword=ugettext_noop --keyword=ugettext_lazy ' '--keyword=ungettext_lazy:1,2 --keyword=pgettext:1c,2 ' '--keyword=npgettext:1c,2,3 --keyword=pgettext_lazy:1c,2 ' - '--keyword=npgettext_lazy:1c,2,3 --from-code UTF-8 -o - ' - '"%s"' % (domain, wrap, os.path.join(dirpath, thefile)) + '--keyword=npgettext_lazy:1c,2,3 --from-code UTF-8 ' + '--add-comments=Translators -o - "%s"' % ( + domain, wrap, os.path.join(dirpath, thefile)) ) msgs, errors = _popen(cmd) if errors: @@ -282,6 +283,8 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False, raise CommandError("errors happened while running msgmerge\n%s" % errors) elif not invoked_for_django: msgs = copy_plural_forms(msgs, locale, domain, verbosity) + msgs = msgs.replace( + "#. #-#-#-#-# %s.pot (PACKAGE VERSION) #-#-#-#-#\n" % domain, "") f = open(pofile, 'wb') try: f.write(msgs) diff --git a/django/template/__init__.py b/django/template/__init__.py index 74403584be..319a521eb3 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -82,6 +82,7 @@ VARIABLE_TAG_START = '{{' VARIABLE_TAG_END = '}}' COMMENT_TAG_START = '{#' COMMENT_TAG_END = '#}' +TRANSLATOR_COMMENT_MARK = 'Translators' SINGLE_BRACE_START = '{' SINGLE_BRACE_END = '}' @@ -237,7 +238,10 @@ class Lexer(object): elif token_string.startswith(BLOCK_TAG_START): token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) elif token_string.startswith(COMMENT_TAG_START): - token = Token(TOKEN_COMMENT, '') + content = '' + if token_string.find(TRANSLATOR_COMMENT_MARK): + content = token_string[len(COMMENT_TAG_START):-len(COMMENT_TAG_END)].strip() + token = Token(TOKEN_COMMENT, content) else: token = Token(TOKEN_TEXT, token_string) return token diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 827337c837..832acc76e7 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -427,14 +427,23 @@ def templatize(src): does so by translating the Django translation tags into standard gettext function invocations. """ - from django.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK + from django.template import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT out = StringIO() intrans = False inplural = False singular = [] plural = [] + incomment = False + comment = [] for t in Lexer(src, None).tokenize(): - if intrans: + if incomment: + if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment': + out.write(' # %s' % ''.join(comment)) + incomment = False + comment = [] + else: + comment.append(t.contents) + elif intrans: if t.token_type == TOKEN_BLOCK: endbmatch = endblock_re.match(t.contents) pluralmatch = plural_re.match(t.contents) @@ -488,6 +497,8 @@ def templatize(src): elif cmatches: for cmatch in cmatches: out.write(' _(%s) ' % cmatch) + elif t.contents == 'comment': + incomment = True else: out.write(blankout(t.contents, 'B')) elif t.token_type == TOKEN_VAR: @@ -500,6 +511,8 @@ def templatize(src): out.write(' %s ' % p.split(':',1)[1]) else: out.write(blankout(p, 'F')) + elif t.token_type == TOKEN_COMMENT: + out.write(' # %s' % t.contents) else: out.write(blankout(t.contents, 'X')) return out.getvalue() diff --git a/docs/releases/1.3-alpha-2.txt b/docs/releases/1.3-alpha-2.txt index c8671a618c..4f71564630 100644 --- a/docs/releases/1.3-alpha-2.txt +++ b/docs/releases/1.3-alpha-2.txt @@ -39,6 +39,22 @@ See the :doc:`reference documentation of the app ` for more details or learn how to :doc:`manage static files `. +Translation comments +~~~~~~~~~~~~~~~~~~~~ + +If you would like to give translators hints about a translatable string, you +can add a comment prefixed with the ``Translators`` keyword on the line +preceding the string, e.g.:: + + def my_view(request): + # Translators: This message appears on the home page only + output = ugettext("Welcome to my site.") + +The comment will appear in the resulting .po file and should also be +displayed by most translation tools. + +For more information, see :ref:`translator-comments`. + Backwards-incompatible changes in 1.3 alpha 2 ============================================= diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 20c98f29c4..7c4622f9c5 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -121,13 +121,17 @@ value, protect, or do nothing. For more information, see the :attr:`~django.db.models.ForeignKey.on_delete` documentation. -Contextual markers in translatable strings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Contextual markers and comments for translatable strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For translation strings with ambiguous meaning, you can now use the ``pgettext`` function to specify the context of the string. -For more information, see :ref:`contextual-markers` +And if you just want to add some information for translators, you +can also add special translator comments in the source. + +For more information, see :ref:`contextual-markers` and +:ref:`translator-comments`. Everything else ~~~~~~~~~~~~~~~ diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 583f53e007..6d58813e75 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -100,6 +100,30 @@ instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you have more than a single parameter. If you used positional interpolation, translations wouldn't be able to reorder placeholder text. +.. _translator-comments: + +Comments for translators +------------------------ + +.. versionadded:: 1.3 + +If you would like to give translators hints about a translatable string, you +can add a comment prefixed with the ``Translators`` keyword on the line +preceding the string, e.g.:: + + def my_view(request): + # 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: + +.. code-block:: django+html + + {% comment %}Translators: This is a text of the base template {% endcomment %} + +The comment will then appear in the resulting .po file and should also be +displayed by most translation tools. + Marking strings as no-op ------------------------ diff --git a/tests/regressiontests/i18n/commands/__init__.py b/tests/regressiontests/i18n/commands/__init__.py index e69de29bb2..ffd439140e 100644 --- a/tests/regressiontests/i18n/commands/__init__.py +++ b/tests/regressiontests/i18n/commands/__init__.py @@ -0,0 +1,8 @@ +from django.utils.translation import ugettext as _ + +# Translators: This comment should be extracted +dummy1 = _("This is a translatable string.") + +# This comment should not be extracted +dummy2 = _("This is another translatable string.") + diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py index 116dfa6420..3e8d2d6228 100644 --- a/tests/regressiontests/i18n/commands/extraction.py +++ b/tests/regressiontests/i18n/commands/extraction.py @@ -38,7 +38,18 @@ class ExtractorTests(TestCase): return self.assert_(not re.search('^msgid %s' % msgid, s, re.MULTILINE)) -class TemplateExtractorTests(ExtractorTests): +class BasicExtractorTests(ExtractorTests): + + def test_comments_extractor(self): + os.chdir(self.test_dir) + management.call_command('makemessages', locale=LOCALE, verbosity=0) + self.assert_(os.path.exists(self.PO_FILE)) + po_contents = open(self.PO_FILE, 'r').read() + self.assert_('#. Translators: This comment should be extracted' in po_contents) + self.assert_('This comment should not be extracted' not in po_contents) + # Comments in templates + self.assert_('#. Translators: Django template comment for translators' in po_contents) + self.assert_('#. Translators: Django comment block for translators' in po_contents) def test_templatize(self): os.chdir(self.test_dir) diff --git a/tests/regressiontests/i18n/commands/templates/test.html b/tests/regressiontests/i18n/commands/templates/test.html index e1c3eee573..ff8485ead5 100644 --- a/tests/regressiontests/i18n/commands/templates/test.html +++ b/tests/regressiontests/i18n/commands/templates/test.html @@ -1,5 +1,8 @@ {% load i18n %} +{% comment %}Translators: Django comment block for translators {% endcomment %} {% trans "This literal should be included." %} {% trans "This literal should also be included wrapped or not wrapped depending on the use of the --no-wrap option." %} -{% blocktrans %}I think that 100% is more that 50% of anything.{% endblocktrans %} -{% blocktrans with 'txt' as obj %}I think that 100% is more that 50% of {{ obj }}.{% endblocktrans %} \ No newline at end of file + +{# Translators: Django template comment for translators #} +

{% blocktrans %}I think that 100% is more that 50% of anything.{% endblocktrans %}

+{% blocktrans with 'txt' as obj %}I think that 100% is more that 50% of {{ obj }}.{% endblocktrans %}