Index: tests/regressiontests/i18n/commands/extraction.py
===================================================================
--- tests/regressiontests/i18n/commands/extraction.py	(wersja 17653)
+++ tests/regressiontests/i18n/commands/extraction.py	(kopia robocza)
@@ -42,6 +42,15 @@
         msgid = re.escape(msgid)
         return self.assertTrue(re.search('^msgid %s' % msgid, s, re.MULTILINE), 'Could not find %(q)s%(n)s%(q)s in generated PO file' % {'n':needle, 'q':q})
 
+    def assertMsgIdPlural(self, msgid, s, use_quotes=True):
+        q = '"'
+        if use_quotes:
+            msgid = '"%s"' % msgid
+            q = "'"
+        needle = 'msgid_plural %s' % msgid
+        msgid = re.escape(msgid)
+        return self.assertTrue(re.search('^msgid_plural %s' % msgid, s, re.MULTILINE), 'Could not find %(q)s%(n)s%(q)s in generated PO file' % {'n':needle, 'q':q})
+
     def assertNotMsgId(self, msgid, s, use_quotes=True):
         if use_quotes:
             msgid = '"%s"' % msgid
@@ -268,3 +277,38 @@
         with open(self.PO_FILE, 'r') as fp:
             po_contents = fp.read()
             self.assertTrue('#: templates/test.html:55' in po_contents)
+
+
+class MultipleFilesPluralExtractorTests(ExtractorTests):
+
+    def setUp(self):
+        self._cwd = os.getcwd()
+        self.test_dir = os.path.abspath(os.path.dirname(__file__))
+        self.second_test_template = os.path.join(self.test_dir, 'templates', 'plural_test_template.html')
+
+        second_test_template = '''{% load i18n %}{% trans 'My string' %}'''
+
+        if not os.path.exists(self.second_test_template):
+            f = open(self.second_test_template, 'w+')
+            try:
+                f.write(second_test_template)
+            finally:
+                f.close()
+
+    def tearDown(self):
+        super(MultipleFilesPluralExtractorTests, self).tearDown()
+        os.chdir(self.test_dir)
+        try:
+            os.remove(self.second_test_template)
+        except OSError:
+            pass
+        os.chdir(self._cwd)
+
+    def test_multiple_files_plural_extraction(self):
+        os.chdir(self.test_dir)
+        management.call_command('makemessages', locale=LOCALE, verbosity=0, symlinks=True)
+        self.assertTrue(os.path.exists(self.PO_FILE))
+        with open(self.PO_FILE, 'r') as fp:
+            po_contents = fp.read()
+            self.assertMsgId('My string', po_contents)
+            self.assertMsgIdPlural('My strings', po_contents)
\ No newline at end of file
Index: tests/regressiontests/i18n/commands/templates/test.html
===================================================================
--- tests/regressiontests/i18n/commands/templates/test.html	(wersja 17653)
+++ tests/regressiontests/i18n/commands/templates/test.html	(kopia robocza)
@@ -77,3 +77,4 @@
 {% trans "Shouldn't double escape this sequence %% either" context "ctx1" %}
 {% trans "Looks like a str fmt spec %s but shouldn't be interpreted as such" %}
 {% trans "Looks like a str fmt spec % o but shouldn't be interpreted as such" %}
+{% blocktrans count counter=mylist|length %}My string{% plural %}My strings{% endblocktrans %}
Index: tests/regressiontests/i18n/tests.py
===================================================================
--- tests/regressiontests/i18n/tests.py	(wersja 17653)
+++ tests/regressiontests/i18n/tests.py	(kopia robocza)
@@ -29,7 +29,7 @@
     from .commands.extraction import (ExtractorTests, BasicExtractorTests,
         JavascriptExtractorTests, IgnoredExtractorTests, SymlinkExtractorTests,
         CopyPluralFormsExtractorTests, NoWrapExtractorTests,
-        NoLocationExtractorTests)
+        NoLocationExtractorTests, MultipleFilesPluralExtractorTests)
 if can_run_compilation_tests:
     from .commands.compilation import (PoFileTests, PoFileContentsTests,
         PercentRenderingTests)
Index: django/core/management/commands/makemessages.py
===================================================================
--- django/core/management/commands/makemessages.py	(wersja 17653)
+++ django/core/management/commands/makemessages.py	(kopia robocza)
@@ -3,6 +3,7 @@
 import os
 import re
 import sys
+import tempfile
 from itertools import dropwhile
 from optparse import make_option
 from subprocess import PIPE, Popen
@@ -127,15 +128,16 @@
                 break
     return msgs
 
-def write_pot_file(potfile, msgs, file, work_file, is_templatized):
+def write_pot_file(potfile, msgs, all_files):
     """
     Write the :param potfile: POT file with the :param msgs: contents,
     previously making sure its format is valid.
     """
-    if is_templatized:
-        old = '#: ' + work_file[2:]
-        new = '#: ' + file[2:]
-        msgs = msgs.replace(old, new)
+    for f_data in all_files:
+        if f_data['is_templatized']:
+            old = '#: ' + f_data['work'][2:]
+            new = '#: ' + f_data['orig'][2:]
+            msgs = msgs.replace(old, new)
     if os.path.exists(potfile):
         # Strip the header
         msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
@@ -147,52 +149,51 @@
     finally:
         f.close()
 
-def process_file(file, dirpath, potfile, domain, verbosity,
-                 extensions, wrap, location, stdout=sys.stdout):
+def process_files(potfile, domain, verbosity, extensions, wrap, location,
+                  stdout, ignore_patterns, symlinks):
     """
-    Extract translatable literals from :param file: for :param domain:
+    Extract translatable literals from current directory for :param domain:
     creating or updating the :param potfile: POT file.
 
     Uses the xgettext GNU gettext utility.
+
+    Procedure used is as follows:
+    1. Prepare template of xgettext command that will be executed
+       (depending on domain) - cmd
+
+    2. Parse all files and prepare list of dictionaries containing
+       information about each file that will be processed:
+          {'orig': ''  # path to original file
+           'work': ''  # path to either original file or templatized
+                       # version if one will be created
+           'is_templatized': False  # defines if file will be templatized
+
+    3. Create temporary file containing all filenames that are going to
+       be processed by xgettext
+
+    4. Execute xgettext command (cmd prepared in step 1) using path to
+       file created in step 4
+
+    5. If everything is ok: write POT file
+       If error occured while running xgettext: raise exception
+
+    6. For both success and error remove all templatized files that were
+       created
     """
-
     from django.utils.translation import templatize
 
-    if verbosity > 1:
-        stdout.write('processing file %s in %s\n' % (file, dirpath))
-    _, file_ext = os.path.splitext(file)
-    if domain == 'djangojs' and file_ext in extensions:
-        is_templatized = True
-        orig_file = os.path.join(dirpath, file)
-        src_data = open(orig_file).read()
-        src_data = prepare_js_for_gettext(src_data)
-        thefile = '%s.c' % file
-        work_file = os.path.join(dirpath, thefile)
-        f = open(work_file, "w")
-        try:
-            f.write(src_data)
-        finally:
-            f.close()
+    # stores information about all processed files
+    all_files = []
+
+    # prepare command to run xgettext depending on domain type
+    if domain == 'djangojs':
         cmd = (
             'xgettext -d %s -L C %s %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 --add-comments=Translators -o - "%s"' %
-            (domain, wrap, location, work_file))
-    elif domain == 'django' and (file_ext == '.py' or file_ext in extensions):
-        thefile = file
-        orig_file = os.path.join(dirpath, file)
-        is_templatized = file_ext in extensions
-        if is_templatized:
-            src_data = open(orig_file, "rU").read()
-            thefile = '%s.py' % file
-            content = templatize(src_data, orig_file[2:])
-            f = open(os.path.join(dirpath, thefile), "w")
-            try:
-                f.write(content)
-            finally:
-                f.close()
-        work_file = os.path.join(dirpath, thefile)
+            '--from-code UTF-8 --add-comments=Translators -o - -f %s'
+        )
+    elif domain == 'django':
         cmd = (
             'xgettext -d %s -L Python %s %s --keyword=gettext_noop '
             '--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
@@ -200,24 +201,111 @@
             '--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 '
-            '--add-comments=Translators -o - "%s"' %
-            (domain, wrap, location, work_file))
+            '--add-comments=Translators -o - -f %s'
+        )
+
+    # check all files and prepare list of dictionaries containing information
+    # about each file that is going to be processed by xgettext
+    for dirpath, file in find_files(".", ignore_patterns, verbosity,
+                                    stdout, symlinks=symlinks):
+        file_dict = process_file(file, dirpath, domain, verbosity,
+                                 extensions, stdout)
+        if file_dict:
+            all_files.append(file_dict)
+
+    if all_files:
+        # Prepare command that runs xgettext.
+        # Part of this command is a path to config file that
+        # contains paths of all files to be processed
+        input_files = '\n'.join([f_data['work'] for f_data in all_files])
+
+        # create temporary config file for xgettext
+        f_handle, input_files_path = tempfile.mkstemp()
+        f = open(input_files_path, 'w')
+        try:
+            f.write(input_files)
+        finally:
+            f.close()
+
+        # run xgettext against temporary config file
+        cmd = cmd % (domain, wrap, location, input_files_path)
+        msgs, errors = _popen(cmd)
+
+        # remove temporary config file
+        os.unlink(input_files_path)
+
+        if errors:
+            # if error(s) occured we have to remove all templatized files
+            # We have to check if file exists because when symlinks are used it might
+            # happen that file was already removed
+            [os.unlink(f_data['work']) for f_data in all_files
+               if f_data['is_templatized'] and os.path.exists(f_data['work'])]
+
+            input_files = ', '.join([f_data['work'] for f_data in all_files])
+            raise CommandError(
+                "errors happened while running xgettext on %s\n%s" %
+                (input_files, errors))
+
+        if msgs:
+            write_pot_file(potfile, msgs, all_files)
+        # check if file exists because when symlinks are used it might
+        # happen that file was already removed
+        [os.unlink(f_data['work']) for f_data in all_files
+               if f_data['is_templatized'] and os.path.exists(f_data['work'])]
+
+def process_file(file, dirpath, domain, verbosity,
+                 extensions, stdout=sys.stdout):
+    """
+    Extract translatable literals from :param file: for :param domain:
+    and return dictionary containing informations about original file path,
+    and path to templatized file if one was created.
+    """
+
+    from django.utils.translation import templatize
+
+    if verbosity > 1:
+        stdout.write('processing file %s in %s\n' % (file, dirpath))
+    _, file_ext = os.path.splitext(file)
+
+    is_templatized = file_ext in extensions
+
+    # path to the processed file
+    orig_file_path = os.path.join(dirpath, file)
+
+    # prepare information about processed file;
+    # 'orig' - path to original file
+    # 'work' - path to either original file or templatized version if one will be created
+    # 'is_templatized' - defines if file will be templatized
+    file_dict = {'orig': orig_file_path,
+                 'work': orig_file_path,
+                 'is_templatized': is_templatized}
+
+    # if file is going to be templatized then we have to create its special
+    # version prepared for gettext and save it with new filename
+    if domain == 'djangojs' and is_templatized:
+        src_data = open(orig_file_path).read()
+        content = prepare_js_for_gettext(src_data)
+        thefile = '%s.c' % file
+    elif domain == 'django' and (file_ext == '.py' or is_templatized):
+        if is_templatized:
+            src_data = open(orig_file_path, "rU").read()
+            content = templatize(src_data, orig_file_path[2:])
+            thefile = '%s.py' % file
     else:
         return
-    msgs, errors = _popen(cmd)
-    if errors:
-        if is_templatized:
-            os.unlink(work_file)
-        if os.path.exists(potfile):
-            os.unlink(potfile)
-        raise CommandError(
-            "errors happened while running xgettext on %s\n%s" %
-            (file, errors))
-    if msgs:
-        write_pot_file(potfile, msgs, orig_file, work_file, is_templatized)
+
     if is_templatized:
-        os.unlink(work_file)
+        # for templatized files create work file
+        work_file = os.path.join(dirpath, thefile)
+        file_dict['work'] = work_file
 
+        f = open(work_file, "w")
+        try:
+            f.write(content)
+        finally:
+            f.close()
+    return file_dict
+
 def write_po_file(pofile, potfile, domain, locale, verbosity, stdout,
                   copy_pforms, wrap, location, no_obsolete):
     """
@@ -335,10 +423,8 @@
         if os.path.exists(potfile):
             os.unlink(potfile)
 
-        for dirpath, file in find_files(".", ignore_patterns, verbosity,
-                stdout, symlinks=symlinks):
-            process_file(file, dirpath, potfile, domain, verbosity, extensions,
-                    wrap, location, stdout)
+        process_files(potfile, domain, verbosity, extensions,
+                      wrap, location, stdout, ignore_patterns, symlinks)
 
         if os.path.exists(potfile):
             write_po_file(pofile, potfile, domain, locale, verbosity, stdout,
