Code

Ticket #17008: 17008.makemessages-keep-pot.diff

File 17008.makemessages-keep-pot.diff, 19.8 KB (added by julien, 3 years ago)
Line 
1diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
2index a903c3c..796262b 100644
3--- a/django/core/management/commands/makemessages.py
4+++ b/django/core/management/commands/makemessages.py
5@@ -11,7 +11,8 @@ from django.core.management.base import CommandError, NoArgsCommand
6 from django.utils.text import get_text_list
7 from django.utils.jslex import prepare_js_for_gettext
8 
9-plural_forms_re = re.compile(r'^(?P<value>"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL)
10+plural_forms_re = re.compile(
11+    r'^(?P<value>"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL)
12 
13 def handle_extensions(extensions=('html',)):
14     """
15@@ -37,13 +38,16 @@ def handle_extensions(extensions=('html',)):
16             ext_list[i] = '.%s' % ext_list[i]
17     return set([x for x in ext_list if x != '.py'])
18 
19+
20 def _popen(cmd):
21     """
22     Friendly wrapper around Popen for Windows
23     """
24-    p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True)
25+    p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE,
26+              close_fds=os.name != 'nt', universal_newlines=True)
27     return p.communicate()
28 
29+
30 def walk(root, topdown=True, onerror=None, followlinks=False):
31     """
32     A version of os.walk that can follow symlinks for Python < 2.6
33@@ -57,6 +61,7 @@ def walk(root, topdown=True, onerror=None, followlinks=False):
34                     for link_dirpath, link_dirnames, link_filenames in walk(p):
35                         yield (link_dirpath, link_dirnames, link_filenames)
36 
37+
38 def is_ignored(path, ignore_patterns):
39     """
40     Helper function to check if the given path should be ignored or not.
41@@ -66,6 +71,7 @@ def is_ignored(path, ignore_patterns):
42             return True
43     return False
44 
45+
46 def find_files(root, ignore_patterns, verbosity, symlinks=False):
47     """
48     Helper function to get all files in the given root.
49@@ -82,6 +88,7 @@ def find_files(root, ignore_patterns, verbosity, symlinks=False):
50     all_files.sort()
51     return all_files
52 
53+
54 def copy_plural_forms(msgs, locale, domain, verbosity):
55     """
56     Copies plural forms header contents from a Django catalog of locale to
57@@ -89,18 +96,21 @@ def copy_plural_forms(msgs, locale, domain, verbosity):
58     contents of a newly created .po file.
59     """
60     import django
61-    django_dir = os.path.normpath(os.path.join(os.path.dirname(django.__file__)))
62+    django_dir = os.path.normpath(
63+                     os.path.join(os.path.dirname(django.__file__)))
64     if domain == 'djangojs':
65         domains = ('djangojs', 'django')
66     else:
67         domains = ('django',)
68     for domain in domains:
69-        django_po = os.path.join(django_dir, 'conf', 'locale', locale, 'LC_MESSAGES', '%s.po' % domain)
70+        django_po = os.path.join(django_dir, 'conf', 'locale', locale,
71+                                 'LC_MESSAGES', '%s.po' % domain)
72         if os.path.exists(django_po):
73             m = plural_forms_re.search(open(django_po, 'rU').read())
74             if m:
75                 if verbosity > 1:
76-                    sys.stderr.write("copying plural forms: %s\n" % m.group('value'))
77+                    sys.stderr.write(
78+                        "copying plural forms: %s\n" % m.group('value'))
79                 lines = []
80                 seen = False
81                 for line in msgs.split('\n'):
82@@ -115,8 +125,7 @@ def copy_plural_forms(msgs, locale, domain, verbosity):
83 
84 def make_messages(locale=None, domain='django', verbosity='1', all=False,
85         extensions=None, symlinks=False, ignore_patterns=[], no_wrap=False,
86-        no_location=False,
87-        no_obsolete=False):
88+        no_location=False, no_obsolete=False, keep_pot=False):
89     """
90     Uses the locale directory from the Django SVN tree or an application/
91     project to process all
92@@ -139,13 +148,22 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
93     elif os.path.isdir('locale'):
94         localedir = os.path.abspath('locale')
95     else:
96-        raise CommandError("This script should be run from the Django SVN tree or your project or app tree. If you did indeed run it from the SVN checkout or your project or application, maybe you are just missing the conf/locale (in the django tree) or locale (for project and application) directory? It is not created automatically, you have to create it by hand if you want to enable i18n for your project or application.")
97+        raise CommandError(
98+            "This script should be run from the Django SVN tree or your "
99+            "project or app tree. If you did indeed run it from the SVN "
100+            "checkout or your project or application, maybe you are just "
101+            "missing the conf/locale (in the django tree) or locale (for "
102+            "project and application) directory? It is not created "
103+            "automatically, you have to create it by hand if you want to "
104+            "enable i18n for your project or application.")
105 
106     if domain not in ('django', 'djangojs'):
107-        raise CommandError("currently makemessages only supports domains 'django' and 'djangojs'")
108+        raise CommandError("currently makemessages only supports domains "
109+                           "'django' and 'djangojs'")
110 
111     if (locale is None and not all) or domain is None:
112-        message = "Type '%s help %s' for usage information." % (os.path.basename(sys.argv[0]), sys.argv[1])
113+        message = "Type '%s help %s' for usage information." % (
114+                  os.path.basename(sys.argv[0]), sys.argv[1])
115         raise CommandError(message)
116 
117     # We require gettext version 0.15 or newer.
118@@ -154,7 +172,10 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
119     if match:
120         xversion = (int(match.group('major')), int(match.group('minor')))
121         if xversion < (0, 15):
122-            raise CommandError("Django internationalization requires GNU gettext 0.15 or newer. You are using version %s, please upgrade your gettext toolset." % match.group())
123+            raise CommandError(
124+                "Django internationalization requires GNU gettext 0.15 or "
125+                "newer. You are using version %s, please upgrade your gettext "
126+                "toolset." % match.group())
127 
128     languages = []
129     if locale is not None:
130@@ -179,11 +200,13 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
131         if os.path.exists(potfile):
132             os.unlink(potfile)
133 
134-        for dirpath, file in find_files(".", ignore_patterns, verbosity, symlinks=symlinks):
135+        for dirpath, file in find_files(".", ignore_patterns, verbosity,
136+                                        symlinks=symlinks):
137             file_base, file_ext = os.path.splitext(file)
138             if domain == 'djangojs' and file_ext in extensions:
139                 if verbosity > 1:
140-                    sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
141+                    sys.stdout.write('processing file %s in %s\n'
142+                                     % (file, dirpath))
143                 src = open(os.path.join(dirpath, file), "rU").read()
144                 src = prepare_js_for_gettext(src)
145                 thefile = '%s.c' % file
146@@ -196,14 +219,13 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
147                     'xgettext -d %s -L C %s %s --keyword=gettext_noop '
148                     '--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
149                     '--keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 '
150-                    '--from-code UTF-8 --add-comments=Translators -o - "%s"' % (
151-                        domain, wrap, location, os.path.join(dirpath, thefile)
152-                    )
153+                    '--from-code UTF-8 --add-comments=Translators -o - "%s"'
154+                    % (domain, wrap, location, os.path.join(dirpath, thefile))
155                 )
156                 msgs, errors = _popen(cmd)
157                 if errors:
158                     os.unlink(os.path.join(dirpath, thefile))
159-                    if os.path.exists(potfile):
160+                    if os.path.exists(potfile) and not keep_pot:
161                         os.unlink(potfile)
162                     raise CommandError(
163                         "errors happened while running xgettext on %s\n%s" %
164@@ -223,7 +245,8 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
165                     finally:
166                         f.close()
167                 os.unlink(os.path.join(dirpath, thefile))
168-            elif domain == 'django' and (file_ext == '.py' or file_ext in extensions):
169+            elif (domain == 'django'
170+                  and (file_ext == '.py' or file_ext in extensions)):
171                 thefile = file
172                 orig_file = os.path.join(dirpath, file)
173                 if file_ext in extensions:
174@@ -235,7 +258,8 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
175                     finally:
176                         f.close()
177                 if verbosity > 1:
178-                    sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
179+                    sys.stdout.write('processing file %s in %s\n'
180+                                     % (file, dirpath))
181                 cmd = (
182                     'xgettext -d %s -L Python %s %s --keyword=gettext_noop '
183                     '--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
184@@ -250,7 +274,7 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
185                 if errors:
186                     if thefile != file:
187                         os.unlink(os.path.join(dirpath, thefile))
188-                    if os.path.exists(potfile):
189+                    if os.path.exists(potfile) and not keep_pot:
190                         os.unlink(potfile)
191                     raise CommandError(
192                         "errors happened while running xgettext on %s\n%s" %
193@@ -277,7 +301,8 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
194             msgs, errors = _popen('msguniq %s %s --to-code=utf-8 "%s"' %
195                                   (wrap, location, potfile))
196             if errors:
197-                os.unlink(potfile)
198+                if not keep_pot:
199+                    os.unlink(potfile)
200                 raise CommandError(
201                     "errors happened while running msguniq\n%s" % errors)
202             if os.path.exists(pofile):
203@@ -289,22 +314,26 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
204                 msgs, errors = _popen('msgmerge %s %s -q "%s" "%s"' %
205                                       (wrap, location, pofile, potfile))
206                 if errors:
207-                    os.unlink(potfile)
208+                    if not keep_pot:
209+                        os.unlink(potfile)
210                     raise CommandError(
211                         "errors happened while running msgmerge\n%s" % errors)
212             elif not invoked_for_django:
213                 msgs = copy_plural_forms(msgs, locale, domain, verbosity)
214             msgs = msgs.replace(
215-                "#. #-#-#-#-#  %s.pot (PACKAGE VERSION)  #-#-#-#-#\n" % domain, "")
216+                "#. #-#-#-#-#  %s.pot (PACKAGE VERSION)  #-#-#-#-#\n"
217+                % domain, "")
218             f = open(pofile, 'wb')
219             try:
220                 f.write(msgs)
221             finally:
222                 f.close()
223-            os.unlink(potfile)
224+            if not keep_pot:
225+                os.unlink(potfile)
226             if no_obsolete:
227-                msgs, errors = _popen('msgattrib %s %s -o "%s" --no-obsolete "%s"' %
228-                                      (wrap, location, pofile, pofile))
229+                msgs, errors = _popen(
230+                    'msgattrib %s %s -o "%s" --no-obsolete "%s"'
231+                    %(wrap, location, pofile, pofile))
232                 if errors:
233                     raise CommandError(
234                         "errors happened while running msgattrib\n%s" % errors)
235@@ -313,32 +342,45 @@ def make_messages(locale=None, domain='django', verbosity='1', all=False,
236 class Command(NoArgsCommand):
237     option_list = NoArgsCommand.option_list + (
238         make_option('--locale', '-l', default=None, dest='locale',
239-            help='Creates or updates the message files for the given locale (e.g. pt_BR).'),
240+            help='Creates or updates the message files for the given locale '
241+                 '(e.g. pt_BR).'),
242         make_option('--domain', '-d', default='django', dest='domain',
243             help='The domain of the message files (default: "django").'),
244         make_option('--all', '-a', action='store_true', dest='all',
245-            default=False, help='Updates the message files for all existing locales.'),
246-        make_option('--extension', '-e', dest='extensions',
247-            help='The file extension(s) to examine (default: "html,txt", or "js" if the domain is "djangojs"). Separate multiple extensions with commas, or use -e multiple times.',
248-            action='append'),
249+            default=False, help='Updates the message files for all existing '
250+                                'locales.'),
251+        make_option('--extension', '-e', dest='extensions', action='append',
252+            help='The file extension(s) to examine (default: "html,txt", or '
253+                 '"js" if the domain is "djangojs"). Separate multiple '
254+                 'extensions with commas, or use -e multiple times.'),
255         make_option('--symlinks', '-s', action='store_true', dest='symlinks',
256-            default=False, help='Follows symlinks to directories when examining source code and templates for translation strings.'),
257+            default=False, help='Follows symlinks to directories when '
258+                                'examining source code and templates for '
259+                                'translation strings.'),
260         make_option('--ignore', '-i', action='append', dest='ignore_patterns',
261-            default=[], metavar='PATTERN', help='Ignore files or directories matching this glob-style pattern. Use multiple times to ignore more.'),
262-        make_option('--no-default-ignore', action='store_false', dest='use_default_ignore_patterns',
263-            default=True, help="Don't ignore the common glob-style patterns 'CVS', '.*' and '*~'."),
264+            default=[], metavar='PATTERN',
265+            help='Ignore files or directories matching this glob-style '
266+                 'pattern. Use multiple times to ignore more.'),
267+        make_option('--no-default-ignore', action='store_false',
268+            default=True, dest='use_default_ignore_patterns',
269+            help="Don't ignore the common glob-style patterns 'CVS', '.*' and "
270+                 "'*~'."),
271         make_option('--no-wrap', action='store_true', dest='no_wrap',
272-            default=False, help="Don't break long message lines into several lines"),
273+            default=False, help="Don't break long message lines into several "
274+                                "lines"),
275         make_option('--no-location', action='store_true', dest='no_location',
276             default=False, help="Don't write '#: filename:line' lines"),
277         make_option('--no-obsolete', action='store_true', dest='no_obsolete',
278             default=False, help="Remove obsolete message strings"),
279+        make_option('--keep-pot', action='store_true', dest='keep_pot',
280+            default=False, help="Keep .pot file after making messages. Useful "
281+                                "when debugging."),
282     )
283     help = ( "Runs over the entire source tree of the current directory and "
284-"pulls out all strings marked for translation. It creates (or updates) a message "
285-"file in the conf/locale (in the django tree) or locale (for projects and "
286-"applications) directory.\n\nYou must run this command with one of either the "
287-"--locale or --all options.")
288+"pulls out all strings marked for translation. It creates (or updates) a "
289+"message file in the conf/locale (in the django tree) or locale (for projects "
290+"and applications) directory.\n\nYou must run this command with one of either "
291+"the --locale or --all options.")
292 
293     requires_model_validation = False
294     can_import_settings = False
295@@ -357,6 +399,7 @@ class Command(NoArgsCommand):
296         no_wrap = options.get('no_wrap')
297         no_location = options.get('no_location')
298         no_obsolete = options.get('no_obsolete')
299+        keep_pot = options.get('keep_pot')
300         if domain == 'djangojs':
301             extensions = handle_extensions(extensions or ['js'])
302         else:
303@@ -366,4 +409,6 @@ class Command(NoArgsCommand):
304             sys.stdout.write('examining files with the extensions: %s\n'
305                              % get_text_list(list(extensions), 'and'))
306 
307-        make_messages(locale, domain, verbosity, process_all, extensions, symlinks, ignore_patterns, no_wrap, no_location, no_obsolete)
308+        make_messages(locale, domain, verbosity, process_all, extensions,
309+            symlinks, ignore_patterns, no_wrap, no_location, no_obsolete,
310+            keep_pot)
311diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
312index 8b83a43..5d71901 100644
313--- a/docs/ref/django-admin.txt
314+++ b/docs/ref/django-admin.txt
315@@ -483,6 +483,14 @@ Use the ``--no-location`` option to not write '``#: filename:line``'
316 comment lines in language files. Note that using this option makes it harder
317 for technically skilled translators to understand each message's context.
318 
319+.. django-admin-option:: --keep-pot
320+
321+.. versionadded:: 1.4
322+
323+Use the ``--keep-pot`` option to prevent django from deleting the temporary
324+.pot file it generates before creating the .po file. This is useful for
325+debugging errors which may prevent the final language files from being created.
326+
327 reset <appname appname ...>
328 ---------------------------
329 
330diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py
331index 6566ef4..7a2683c 100644
332--- a/tests/regressiontests/i18n/commands/extraction.py
333+++ b/tests/regressiontests/i18n/commands/extraction.py
334@@ -265,3 +265,36 @@ class NoLocationExtractorTests(ExtractorTests):
335         with open(self.PO_FILE, 'r') as fp:
336             po_contents = fp.read()
337             self.assertTrue('#: templates/test.html:55' in po_contents)
338+
339+
340+class KeepPotFileExtractorTests(ExtractorTests):
341+
342+    def setUp(self):
343+        self.POT_FILE = self.PO_FILE + 't'
344+        super(KeepPotFileExtractorTests, self).setUp()
345+
346+    def tearDown(self):
347+        super(KeepPotFileExtractorTests, self).tearDown()
348+        os.chdir(self.test_dir)
349+        try:
350+            os.unlink(self.POT_FILE)
351+        except OSError:
352+            pass
353+        os.chdir(self._cwd)
354+
355+    def test_keep_pot_disabled_by_default(self):
356+        os.chdir(self.test_dir)
357+        management.call_command('makemessages', locale=LOCALE, verbosity=0)
358+        self.assertFalse(os.path.exists(self.POT_FILE))
359+
360+    def test_keep_pot_explicitly_disabled(self):
361+        os.chdir(self.test_dir)
362+        management.call_command('makemessages', locale=LOCALE, verbosity=0,
363+            keep_pot=False)
364+        self.assertFalse(os.path.exists(self.POT_FILE))
365+
366+    def test_keep_pot_enabled(self):
367+        os.chdir(self.test_dir)
368+        management.call_command('makemessages', locale=LOCALE, verbosity=0,
369+            keep_pot=True)
370+        self.assertTrue(os.path.exists(self.POT_FILE))
371diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
372index cc17d74..a87de2e 100644
373--- a/tests/regressiontests/i18n/tests.py
374+++ b/tests/regressiontests/i18n/tests.py
375@@ -29,7 +29,7 @@ if can_run_extraction_tests:
376     from .commands.extraction import (ExtractorTests, BasicExtractorTests,
377         JavascriptExtractorTests, IgnoredExtractorTests, SymlinkExtractorTests,
378         CopyPluralFormsExtractorTests, NoWrapExtractorTests,
379-        NoLocationExtractorTests)
380+        NoLocationExtractorTests, KeepPotFileExtractorTests)
381 if can_run_compilation_tests:
382     from .commands.compilation import (PoFileTests, PoFileContentsTests,
383         PercentRenderingTests)