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