diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
a
|
b
|
|
11 | 11 | from django.utils.text import get_text_list |
12 | 12 | from django.utils.jslex import prepare_js_for_gettext |
13 | 13 | |
14 | | plural_forms_re = re.compile(r'^(?P<value>"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL) |
| 14 | plural_forms_re = re.compile( |
| 15 | r'^(?P<value>"Plural-Forms.+?\\n")\s*$', re.MULTILINE | re.DOTALL) |
15 | 16 | |
16 | 17 | def handle_extensions(extensions=('html',), ignored=('py',)): |
17 | 18 | """ |
… |
… |
|
41 | 42 | """ |
42 | 43 | Friendly wrapper around Popen for Windows |
43 | 44 | """ |
44 | | p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True) |
| 45 | p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, |
| 46 | close_fds=os.name != 'nt', universal_newlines=True) |
45 | 47 | return p.communicate() |
46 | 48 | |
47 | 49 | def walk(root, topdown=True, onerror=None, followlinks=False): |
… |
… |
|
95 | 97 | else: |
96 | 98 | domains = ('django',) |
97 | 99 | for domain in domains: |
98 | | django_po = os.path.join(django_dir, 'conf', 'locale', locale, 'LC_MESSAGES', '%s.po' % domain) |
| 100 | django_po = os.path.join(django_dir, 'conf', 'locale', locale, |
| 101 | 'LC_MESSAGES', '%s.po' % domain) |
99 | 102 | if os.path.exists(django_po): |
100 | 103 | m = plural_forms_re.search(open(django_po, 'rU').read()) |
101 | 104 | if m: |
102 | 105 | if verbosity > 1: |
103 | | sys.stderr.write("copying plural forms: %s\n" % m.group('value')) |
| 106 | sys.stderr.write( |
| 107 | "copying plural forms: %s\n" % m.group('value')) |
104 | 108 | lines = [] |
105 | 109 | seen = False |
106 | 110 | for line in msgs.split('\n'): |
… |
… |
|
133 | 137 | f.close() |
134 | 138 | |
135 | 139 | def process_file(file, dirpath, potfile, domain, verbosity, extensions, wrap, |
136 | | location): |
| 140 | location, keep_pot): |
137 | 141 | """ |
138 | 142 | Extract translatable literals from :param file: for :param domain: |
139 | 143 | creating or updating the :param potfile: POT file. |
… |
… |
|
196 | 200 | if errors: |
197 | 201 | if is_templatized: |
198 | 202 | os.unlink(work_file) |
199 | | if os.path.exists(potfile): |
| 203 | if not keep_pot and os.path.exists(potfile): |
200 | 204 | os.unlink(potfile) |
201 | 205 | raise CommandError( |
202 | 206 | "errors happened while running xgettext on %s\n%s" % |
… |
… |
|
207 | 211 | os.unlink(work_file) |
208 | 212 | |
209 | 213 | def write_po_file(pofile, potfile, domain, locale, verbosity, |
210 | | copy_pforms, wrap, location, no_obsolete): |
| 214 | copy_pforms, wrap, location, no_obsolete, keep_pot): |
211 | 215 | """ |
212 | 216 | Creates of updates the :param pofile: PO file for :param domain: and :param |
213 | 217 | locale:. Uses contents of the existing :param potfile:. |
… |
… |
|
217 | 221 | msgs, errors = _popen('msguniq %s %s --to-code=utf-8 "%s"' % |
218 | 222 | (wrap, location, potfile)) |
219 | 223 | if errors: |
220 | | os.unlink(potfile) |
| 224 | if not keep_pot: |
| 225 | os.unlink(potfile) |
221 | 226 | raise CommandError("errors happened while running msguniq\n%s" % errors) |
222 | 227 | if os.path.exists(pofile): |
223 | 228 | f = open(potfile, 'w') |
… |
… |
|
228 | 233 | msgs, errors = _popen('msgmerge %s %s -q "%s" "%s"' % |
229 | 234 | (wrap, location, pofile, potfile)) |
230 | 235 | if errors: |
231 | | os.unlink(potfile) |
| 236 | if not keep_pot: |
| 237 | os.unlink(potfile) |
232 | 238 | raise CommandError( |
233 | 239 | "errors happened while running msgmerge\n%s" % errors) |
234 | 240 | elif copy_pforms: |
… |
… |
|
240 | 246 | f.write(msgs) |
241 | 247 | finally: |
242 | 248 | f.close() |
243 | | os.unlink(potfile) |
| 249 | if not keep_pot: |
| 250 | os.unlink(potfile) |
244 | 251 | if no_obsolete: |
245 | 252 | msgs, errors = _popen('msgattrib %s %s -o "%s" --no-obsolete "%s"' % |
246 | 253 | (wrap, location, pofile, pofile)) |
… |
… |
|
250 | 257 | |
251 | 258 | def make_messages(locale=None, domain='django', verbosity=1, all=False, |
252 | 259 | extensions=None, symlinks=False, ignore_patterns=None, no_wrap=False, |
253 | | no_location=False, no_obsolete=False): |
| 260 | no_location=False, no_obsolete=False, keep_pot=False): |
254 | 261 | """ |
255 | 262 | Uses the ``locale/`` directory from the Django SVN tree or an |
256 | 263 | application/project to process all files with translatable literals for |
… |
… |
|
284 | 291 | "if you want to enable i18n for your project or application.") |
285 | 292 | |
286 | 293 | if domain not in ('django', 'djangojs'): |
287 | | raise CommandError("currently makemessages only supports domains 'django' and 'djangojs'") |
| 294 | raise CommandError("currently makemessages only supports domains " |
| 295 | "'django' and 'djangojs'") |
288 | 296 | |
289 | 297 | if (locale is None and not all) or domain is None: |
290 | | message = "Type '%s help %s' for usage information." % (os.path.basename(sys.argv[0]), sys.argv[1]) |
| 298 | message = "Type '%s help %s' for usage information." % ( |
| 299 | os.path.basename(sys.argv[0]), sys.argv[1]) |
291 | 300 | raise CommandError(message) |
292 | 301 | |
293 | 302 | # We require gettext version 0.15 or newer. |
… |
… |
|
326 | 335 | for dirpath, file in find_files(".", ignore_patterns, verbosity, |
327 | 336 | symlinks=symlinks): |
328 | 337 | process_file(file, dirpath, potfile, domain, verbosity, extensions, |
329 | | wrap, location) |
| 338 | wrap, location, keep_pot) |
330 | 339 | |
331 | 340 | if os.path.exists(potfile): |
332 | 341 | write_po_file(pofile, potfile, domain, locale, verbosity, |
333 | | not invoked_for_django, wrap, location, no_obsolete) |
| 342 | not invoked_for_django, wrap, location, no_obsolete, |
| 343 | keep_pot) |
334 | 344 | |
335 | 345 | |
336 | 346 | class Command(NoArgsCommand): |
337 | 347 | option_list = NoArgsCommand.option_list + ( |
338 | 348 | make_option('--locale', '-l', default=None, dest='locale', |
339 | | help='Creates or updates the message files for the given locale (e.g. pt_BR).'), |
| 349 | help='Creates or updates the message files for the given locale ' |
| 350 | '(e.g. pt_BR).'), |
340 | 351 | make_option('--domain', '-d', default='django', dest='domain', |
341 | 352 | help='The domain of the message files (default: "django").'), |
342 | 353 | make_option('--all', '-a', action='store_true', dest='all', |
343 | | default=False, help='Updates the message files for all existing locales.'), |
344 | | make_option('--extension', '-e', dest='extensions', |
345 | | 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.', |
346 | | action='append'), |
| 354 | default=False, help='Updates the message files for all existing ' |
| 355 | 'locales.'), |
| 356 | make_option('--extension', '-e', dest='extensions', action='append', |
| 357 | help='The file extension(s) to examine (default: "html,txt", or ' |
| 358 | '"js" if the domain is "djangojs"). Separate multiple ' |
| 359 | 'extensions with commas, or use -e multiple times.'), |
347 | 360 | make_option('--symlinks', '-s', action='store_true', dest='symlinks', |
348 | | default=False, help='Follows symlinks to directories when examining source code and templates for translation strings.'), |
| 361 | default=False, help='Follows symlinks to directories when ' |
| 362 | 'examining source code and templates for ' |
| 363 | 'translation strings.'), |
349 | 364 | make_option('--ignore', '-i', action='append', dest='ignore_patterns', |
350 | | default=[], metavar='PATTERN', help='Ignore files or directories matching this glob-style pattern. Use multiple times to ignore more.'), |
351 | | make_option('--no-default-ignore', action='store_false', dest='use_default_ignore_patterns', |
352 | | default=True, help="Don't ignore the common glob-style patterns 'CVS', '.*' and '*~'."), |
| 365 | default=[], metavar='PATTERN', |
| 366 | help='Ignore files or directories matching this glob-style ' |
| 367 | 'pattern. Use multiple times to ignore more.'), |
| 368 | make_option('--no-default-ignore', action='store_false', |
| 369 | default=True, dest='use_default_ignore_patterns', |
| 370 | help="Don't ignore the common glob-style patterns 'CVS', '.*' and " |
| 371 | "'*~'."), |
353 | 372 | make_option('--no-wrap', action='store_true', dest='no_wrap', |
354 | | default=False, help="Don't break long message lines into several lines"), |
| 373 | default=False, help="Don't break long message lines into several " |
| 374 | "lines"), |
355 | 375 | make_option('--no-location', action='store_true', dest='no_location', |
356 | 376 | default=False, help="Don't write '#: filename:line' lines"), |
357 | 377 | make_option('--no-obsolete', action='store_true', dest='no_obsolete', |
358 | 378 | default=False, help="Remove obsolete message strings"), |
| 379 | make_option('--keep-pot', action='store_true', dest='keep_pot', |
| 380 | default=False, help="Keep .pot file after making messages. Useful " |
| 381 | "when debugging."), |
359 | 382 | ) |
360 | 383 | help = ( "Runs over the entire source tree of the current directory and " |
361 | | "pulls out all strings marked for translation. It creates (or updates) a message " |
362 | | "file in the conf/locale (in the django tree) or locale (for projects and " |
363 | | "applications) directory.\n\nYou must run this command with one of either the " |
364 | | "--locale or --all options.") |
| 384 | "pulls out all strings marked for translation. It creates (or updates) a " |
| 385 | "message file in the conf/locale (in the django tree) or locale (for projects " |
| 386 | "and applications) directory.\n\nYou must run this command with one of either " |
| 387 | "the --locale or --all options.") |
365 | 388 | |
366 | 389 | requires_model_validation = False |
367 | 390 | can_import_settings = False |
… |
… |
|
380 | 403 | no_wrap = options.get('no_wrap') |
381 | 404 | no_location = options.get('no_location') |
382 | 405 | no_obsolete = options.get('no_obsolete') |
| 406 | keep_pot = options.get('keep_pot') |
383 | 407 | if domain == 'djangojs': |
384 | 408 | extensions = handle_extensions(extensions or ['js']) |
385 | 409 | else: |
… |
… |
|
389 | 413 | sys.stdout.write('examining files with the extensions: %s\n' |
390 | 414 | % get_text_list(list(extensions), 'and')) |
391 | 415 | |
392 | | make_messages(locale, domain, verbosity, process_all, extensions, symlinks, ignore_patterns, no_wrap, no_location, no_obsolete) |
| 416 | make_messages(locale, domain, verbosity, process_all, extensions, |
| 417 | symlinks, ignore_patterns, no_wrap, no_location, no_obsolete, |
| 418 | keep_pot) |
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
a
|
b
|
|
483 | 483 | comment lines in language files. Note that using this option makes it harder |
484 | 484 | for technically skilled translators to understand each message's context. |
485 | 485 | |
| 486 | .. django-admin-option:: --keep-pot |
| 487 | |
| 488 | .. versionadded:: 1.4 |
| 489 | |
| 490 | Use the ``--keep-pot`` option to prevent django from deleting the temporary |
| 491 | .pot file it generates before creating the .po file. This is useful for |
| 492 | debugging errors which may prevent the final language files from being created. |
| 493 | |
486 | 494 | reset <appname appname ...> |
487 | 495 | --------------------------- |
488 | 496 | |
diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py
a
|
b
|
|
263 | 263 | with open(self.PO_FILE, 'r') as fp: |
264 | 264 | po_contents = fp.read() |
265 | 265 | self.assertTrue('#: templates/test.html:55' in po_contents) |
| 266 | |
| 267 | |
| 268 | class KeepPotFileExtractorTests(ExtractorTests): |
| 269 | |
| 270 | def setUp(self): |
| 271 | self.POT_FILE = self.PO_FILE + 't' |
| 272 | super(KeepPotFileExtractorTests, self).setUp() |
| 273 | |
| 274 | def tearDown(self): |
| 275 | super(KeepPotFileExtractorTests, self).tearDown() |
| 276 | os.chdir(self.test_dir) |
| 277 | try: |
| 278 | os.unlink(self.POT_FILE) |
| 279 | except OSError: |
| 280 | pass |
| 281 | os.chdir(self._cwd) |
| 282 | |
| 283 | def test_keep_pot_disabled_by_default(self): |
| 284 | os.chdir(self.test_dir) |
| 285 | management.call_command('makemessages', locale=LOCALE, verbosity=0) |
| 286 | self.assertFalse(os.path.exists(self.POT_FILE)) |
| 287 | |
| 288 | def test_keep_pot_explicitly_disabled(self): |
| 289 | os.chdir(self.test_dir) |
| 290 | management.call_command('makemessages', locale=LOCALE, verbosity=0, |
| 291 | keep_pot=False) |
| 292 | self.assertFalse(os.path.exists(self.POT_FILE)) |
| 293 | |
| 294 | def test_keep_pot_enabled(self): |
| 295 | os.chdir(self.test_dir) |
| 296 | management.call_command('makemessages', locale=LOCALE, verbosity=0, |
| 297 | keep_pot=True) |
| 298 | self.assertTrue(os.path.exists(self.POT_FILE)) |
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
a
|
b
|
|
29 | 29 | from .commands.extraction import (ExtractorTests, BasicExtractorTests, |
30 | 30 | JavascriptExtractorTests, IgnoredExtractorTests, SymlinkExtractorTests, |
31 | 31 | CopyPluralFormsExtractorTests, NoWrapExtractorTests, |
32 | | NoLocationExtractorTests) |
| 32 | NoLocationExtractorTests, KeepPotFileExtractorTests) |
33 | 33 | if can_run_compilation_tests: |
34 | 34 | from .commands.compilation import (PoFileTests, PoFileContentsTests, |
35 | 35 | PercentRenderingTests) |