Made (make|compile)messages check for availability of gettext commands.
Refs #19584.
This commit is contained in:
parent
3f43f5f3b0
commit
7fca4416c7
|
@ -5,7 +5,7 @@ import os
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.core.management.utils import popen_wrapper
|
from django.core.management.utils import find_command, popen_wrapper
|
||||||
from django.utils._os import npath
|
from django.utils._os import npath
|
||||||
|
|
||||||
def has_bom(fn):
|
def has_bom(fn):
|
||||||
|
@ -16,6 +16,10 @@ def has_bom(fn):
|
||||||
sample.startswith(codecs.BOM_UTF16_BE)
|
sample.startswith(codecs.BOM_UTF16_BE)
|
||||||
|
|
||||||
def compile_messages(stderr, locale=None):
|
def compile_messages(stderr, locale=None):
|
||||||
|
program = 'msgfmt'
|
||||||
|
if find_command(program) is None:
|
||||||
|
raise CommandError("Can't find %s. Make sure you have GNU gettext tools 0.15 or newer installed." % program)
|
||||||
|
|
||||||
basedirs = [os.path.join('conf', 'locale'), 'locale']
|
basedirs = [os.path.join('conf', 'locale'), 'locale']
|
||||||
if os.environ.get('DJANGO_SETTINGS_MODULE'):
|
if os.environ.get('DJANGO_SETTINGS_MODULE'):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -42,7 +46,6 @@ def compile_messages(stderr, locale=None):
|
||||||
if has_bom(fn):
|
if has_bom(fn):
|
||||||
raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn)
|
raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn)
|
||||||
pf = os.path.splitext(fn)[0]
|
pf = os.path.splitext(fn)[0]
|
||||||
program = 'msgfmt'
|
|
||||||
args = [program, '--check-format', '-o', npath(pf + '.mo'), npath(pf + '.po')]
|
args = [program, '--check-format', '-o', npath(pf + '.mo'), npath(pf + '.po')]
|
||||||
output, errors, status = popen_wrapper(args)
|
output, errors, status = popen_wrapper(args)
|
||||||
if status:
|
if status:
|
||||||
|
|
|
@ -5,11 +5,11 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from itertools import dropwhile
|
from itertools import dropwhile
|
||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
from subprocess import PIPE, Popen
|
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.core.management.base import CommandError, NoArgsCommand
|
from django.core.management.base import CommandError, NoArgsCommand
|
||||||
from django.core.management.utils import handle_extensions
|
from django.core.management.utils import (handle_extensions, find_command,
|
||||||
|
popen_wrapper)
|
||||||
from django.utils.functional import total_ordering
|
from django.utils.functional import total_ordering
|
||||||
from django.utils.text import get_text_list
|
from django.utils.text import get_text_list
|
||||||
from django.utils.jslex import prepare_js_for_gettext
|
from django.utils.jslex import prepare_js_for_gettext
|
||||||
|
@ -18,6 +18,13 @@ plural_forms_re = re.compile(r'^(?P<value>"Plural-Forms.+?\\n")\s*$', re.MULTILI
|
||||||
STATUS_OK = 0
|
STATUS_OK = 0
|
||||||
|
|
||||||
|
|
||||||
|
def check_programs(*programs):
|
||||||
|
for program in programs:
|
||||||
|
if find_command(program) is None:
|
||||||
|
raise CommandError("Can't find %s. Make sure you have GNU "
|
||||||
|
"gettext tools 0.15 or newer installed." % program)
|
||||||
|
|
||||||
|
|
||||||
@total_ordering
|
@total_ordering
|
||||||
class TranslatableFile(object):
|
class TranslatableFile(object):
|
||||||
def __init__(self, dirpath, file_name):
|
def __init__(self, dirpath, file_name):
|
||||||
|
@ -58,12 +65,24 @@ class TranslatableFile(object):
|
||||||
work_file = os.path.join(self.dirpath, thefile)
|
work_file = os.path.join(self.dirpath, thefile)
|
||||||
with open(work_file, "w") as fp:
|
with open(work_file, "w") as fp:
|
||||||
fp.write(src_data)
|
fp.write(src_data)
|
||||||
cmd = (
|
args = [
|
||||||
'xgettext -d %s -L C %s %s --keyword=gettext_noop '
|
'xgettext',
|
||||||
'--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
|
'-d', domain,
|
||||||
'--keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 '
|
'--language=C',
|
||||||
'--from-code UTF-8 --add-comments=Translators -o - "%s"' %
|
'--keyword=gettext_noop',
|
||||||
(domain, command.wrap, command.location, work_file))
|
'--keyword=gettext_lazy',
|
||||||
|
'--keyword=ngettext_lazy:1,2',
|
||||||
|
'--keyword=pgettext:1c,2',
|
||||||
|
'--keyword=npgettext:1c,2,3',
|
||||||
|
'--from-code=UTF-8',
|
||||||
|
'--add-comments=Translators',
|
||||||
|
'--output=-'
|
||||||
|
]
|
||||||
|
if command.wrap:
|
||||||
|
args.append(command.wrap)
|
||||||
|
if command.location:
|
||||||
|
args.append(command.location)
|
||||||
|
args.append(work_file)
|
||||||
elif domain == 'django' and (file_ext == '.py' or file_ext in command.extensions):
|
elif domain == 'django' and (file_ext == '.py' or file_ext in command.extensions):
|
||||||
thefile = self.file
|
thefile = self.file
|
||||||
orig_file = os.path.join(self.dirpath, self.file)
|
orig_file = os.path.join(self.dirpath, self.file)
|
||||||
|
@ -76,18 +95,32 @@ class TranslatableFile(object):
|
||||||
with open(os.path.join(self.dirpath, thefile), "w") as fp:
|
with open(os.path.join(self.dirpath, thefile), "w") as fp:
|
||||||
fp.write(content)
|
fp.write(content)
|
||||||
work_file = os.path.join(self.dirpath, thefile)
|
work_file = os.path.join(self.dirpath, thefile)
|
||||||
cmd = (
|
args = [
|
||||||
'xgettext -d %s -L Python %s %s --keyword=gettext_noop '
|
'xgettext',
|
||||||
'--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
|
'-d', domain,
|
||||||
'--keyword=ugettext_noop --keyword=ugettext_lazy '
|
'--language=Python',
|
||||||
'--keyword=ungettext_lazy:1,2 --keyword=pgettext:1c,2 '
|
'--keyword=gettext_noop',
|
||||||
'--keyword=npgettext:1c,2,3 --keyword=pgettext_lazy:1c,2 '
|
'--keyword=gettext_lazy',
|
||||||
'--keyword=npgettext_lazy:1c,2,3 --from-code UTF-8 '
|
'--keyword=ngettext_lazy:1,2',
|
||||||
'--add-comments=Translators -o - "%s"' %
|
'--keyword=ugettext_noop',
|
||||||
(domain, command.wrap, command.location, work_file))
|
'--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',
|
||||||
|
'--add-comments=Translators',
|
||||||
|
'--output=-'
|
||||||
|
]
|
||||||
|
if command.wrap:
|
||||||
|
args.append(command.wrap)
|
||||||
|
if command.location:
|
||||||
|
args.append(command.location)
|
||||||
|
args.append(work_file)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
msgs, errors, status = _popen(cmd)
|
msgs, errors, status = popen_wrapper(args)
|
||||||
if errors:
|
if errors:
|
||||||
if status != STATUS_OK:
|
if status != STATUS_OK:
|
||||||
if is_templatized:
|
if is_templatized:
|
||||||
|
@ -109,15 +142,6 @@ class TranslatableFile(object):
|
||||||
if is_templatized:
|
if is_templatized:
|
||||||
os.unlink(work_file)
|
os.unlink(work_file)
|
||||||
|
|
||||||
|
|
||||||
def _popen(cmd):
|
|
||||||
"""
|
|
||||||
Friendly wrapper around Popen for Windows
|
|
||||||
"""
|
|
||||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True)
|
|
||||||
output, errors = p.communicate()
|
|
||||||
return output, errors, p.returncode
|
|
||||||
|
|
||||||
def write_pot_file(potfile, msgs):
|
def write_pot_file(potfile, msgs):
|
||||||
"""
|
"""
|
||||||
Write the :param potfile: POT file with the :param msgs: contents,
|
Write the :param potfile: POT file with the :param msgs: contents,
|
||||||
|
@ -225,8 +249,9 @@ class Command(NoArgsCommand):
|
||||||
"is not created automatically, you have to create it by hand "
|
"is not created automatically, you have to create it by hand "
|
||||||
"if you want to enable i18n for your project or application.")
|
"if you want to enable i18n for your project or application.")
|
||||||
|
|
||||||
|
check_programs('xgettext')
|
||||||
# We require gettext version 0.15 or newer.
|
# We require gettext version 0.15 or newer.
|
||||||
output, errors, status = _popen('xgettext --version')
|
output, errors, status = popen_wrapper(['xgettext', '--version'])
|
||||||
if status != STATUS_OK:
|
if status != STATUS_OK:
|
||||||
raise CommandError("Error running xgettext. Note that Django "
|
raise CommandError("Error running xgettext. Note that Django "
|
||||||
"internationalization requires GNU gettext 0.15 or newer.")
|
"internationalization requires GNU gettext 0.15 or newer.")
|
||||||
|
@ -248,6 +273,9 @@ class Command(NoArgsCommand):
|
||||||
locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir))
|
locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir))
|
||||||
locales = [os.path.basename(l) for l in locale_dirs]
|
locales = [os.path.basename(l) for l in locale_dirs]
|
||||||
|
|
||||||
|
if locales:
|
||||||
|
check_programs('msguniq', 'msgmerge', 'msgattrib')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for locale in locales:
|
for locale in locales:
|
||||||
if self.verbosity > 0:
|
if self.verbosity > 0:
|
||||||
|
@ -307,8 +335,13 @@ class Command(NoArgsCommand):
|
||||||
|
|
||||||
Uses mguniq, msgmerge, and msgattrib GNU gettext utilities.
|
Uses mguniq, msgmerge, and msgattrib GNU gettext utilities.
|
||||||
"""
|
"""
|
||||||
msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' %
|
args = ['msguniq', '--to-code=utf-8']
|
||||||
(self.wrap, self.location, potfile))
|
if self.wrap:
|
||||||
|
args.append(self.wrap)
|
||||||
|
if self.location:
|
||||||
|
args.append(self.location)
|
||||||
|
args.append(potfile)
|
||||||
|
msgs, errors, status = popen_wrapper(args)
|
||||||
if errors:
|
if errors:
|
||||||
if status != STATUS_OK:
|
if status != STATUS_OK:
|
||||||
raise CommandError(
|
raise CommandError(
|
||||||
|
@ -324,8 +357,13 @@ class Command(NoArgsCommand):
|
||||||
if os.path.exists(pofile):
|
if os.path.exists(pofile):
|
||||||
with open(potfile, 'w') as fp:
|
with open(potfile, 'w') as fp:
|
||||||
fp.write(msgs)
|
fp.write(msgs)
|
||||||
msgs, errors, status = _popen('msgmerge %s %s -q "%s" "%s"' %
|
args = ['msgmerge', '-q']
|
||||||
(self.wrap, self.location, pofile, potfile))
|
if self.wrap:
|
||||||
|
args.append(self.wrap)
|
||||||
|
if self.location:
|
||||||
|
args.append(self.location)
|
||||||
|
args.extend([pofile, potfile])
|
||||||
|
msgs, errors, status = popen_wrapper(args)
|
||||||
if errors:
|
if errors:
|
||||||
if status != STATUS_OK:
|
if status != STATUS_OK:
|
||||||
raise CommandError(
|
raise CommandError(
|
||||||
|
@ -340,9 +378,13 @@ class Command(NoArgsCommand):
|
||||||
fp.write(msgs)
|
fp.write(msgs)
|
||||||
|
|
||||||
if self.no_obsolete:
|
if self.no_obsolete:
|
||||||
msgs, errors, status = _popen(
|
args = ['msgattrib', '-o', pofile, '--no-obsolete']
|
||||||
'msgattrib %s %s -o "%s" --no-obsolete "%s"' %
|
if self.wrap:
|
||||||
(self.wrap, self.location, pofile, pofile))
|
args.append(self.wrap)
|
||||||
|
if self.location:
|
||||||
|
args.append(self.location)
|
||||||
|
args.append(pofile)
|
||||||
|
msgs, errors, status = popen_wrapper(args)
|
||||||
if errors:
|
if errors:
|
||||||
if status != STATUS_OK:
|
if status != STATUS_OK:
|
||||||
raise CommandError(
|
raise CommandError(
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from subprocess import PIPE, Popen
|
from subprocess import PIPE, Popen
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.utils.encoding import force_text, DEFAULT_LOCALE_ENCODING
|
from django.utils.encoding import force_text, DEFAULT_LOCALE_ENCODING
|
||||||
|
from django.utils import six
|
||||||
|
|
||||||
|
from .base import CommandError
|
||||||
|
|
||||||
|
|
||||||
def popen_wrapper(args):
|
def popen_wrapper(args, os_err_exc_type=CommandError):
|
||||||
"""
|
"""
|
||||||
Friendly wrapper around Popen.
|
Friendly wrapper around Popen.
|
||||||
|
|
||||||
Returns stdout output, stderr output and OS status code.
|
Returns stdout output, stderr output and OS status code.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
p = Popen(args, shell=False, stdout=PIPE, stderr=PIPE,
|
p = Popen(args, shell=False, stdout=PIPE, stderr=PIPE,
|
||||||
close_fds=os.name != 'nt', universal_newlines=True)
|
close_fds=os.name != 'nt', universal_newlines=True)
|
||||||
|
except OSError as e:
|
||||||
|
six.reraise(os_err_exc_type, os_err_exc_type('Error executing %s: %s' %
|
||||||
|
(args[0], e.strerror)), sys.exc_info()[2])
|
||||||
output, errors = p.communicate()
|
output, errors = p.communicate()
|
||||||
return (
|
return (
|
||||||
output,
|
output,
|
||||||
|
@ -43,3 +53,27 @@ def handle_extensions(extensions=('html',), ignored=('py',)):
|
||||||
if not ext.startswith('.'):
|
if not ext.startswith('.'):
|
||||||
ext_list[i] = '.%s' % ext_list[i]
|
ext_list[i] = '.%s' % ext_list[i]
|
||||||
return set([x for x in ext_list if x.strip('.') not in ignored])
|
return set([x for x in ext_list if x.strip('.') not in ignored])
|
||||||
|
|
||||||
|
def find_command(cmd, path=None, pathext=None):
|
||||||
|
if path is None:
|
||||||
|
path = os.environ.get('PATH', []).split(os.pathsep)
|
||||||
|
if isinstance(path, six.string_types):
|
||||||
|
path = [path]
|
||||||
|
# check if there are funny path extensions for executables, e.g. Windows
|
||||||
|
if pathext is None:
|
||||||
|
pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD').split(os.pathsep)
|
||||||
|
# don't use extensions if the command ends with one of them
|
||||||
|
for ext in pathext:
|
||||||
|
if cmd.endswith(ext):
|
||||||
|
pathext = ['']
|
||||||
|
break
|
||||||
|
# check if we find the command on PATH
|
||||||
|
for p in path:
|
||||||
|
f = os.path.join(p, cmd)
|
||||||
|
if os.path.isfile(f):
|
||||||
|
return f
|
||||||
|
for ext in pathext:
|
||||||
|
fext = f + ext
|
||||||
|
if os.path.isfile(fext):
|
||||||
|
return fext
|
||||||
|
return None
|
||||||
|
|
|
@ -131,8 +131,13 @@ class BasicExtractorTests(ExtractorTests):
|
||||||
os.chdir(self.test_dir)
|
os.chdir(self.test_dir)
|
||||||
shutil.copyfile('./code.sample', './code_sample.py')
|
shutil.copyfile('./code.sample', './code_sample.py')
|
||||||
stdout = StringIO()
|
stdout = StringIO()
|
||||||
|
try:
|
||||||
management.call_command('makemessages', locale=LOCALE, stdout=stdout)
|
management.call_command('makemessages', locale=LOCALE, stdout=stdout)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
os.remove('./code_sample.py')
|
os.remove('./code_sample.py')
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
self.assertIn("code_sample.py:4", force_text(stdout.getvalue()))
|
self.assertIn("code_sample.py:4", force_text(stdout.getvalue()))
|
||||||
|
|
||||||
def test_template_message_context_extractor(self):
|
def test_template_message_context_extractor(self):
|
||||||
|
|
|
@ -2,35 +2,11 @@ import os
|
||||||
import re
|
import re
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
from django.utils import six
|
from django.core.management.utils import find_command
|
||||||
|
|
||||||
can_run_extraction_tests = False
|
can_run_extraction_tests = False
|
||||||
can_run_compilation_tests = False
|
can_run_compilation_tests = False
|
||||||
|
|
||||||
def find_command(cmd, path=None, pathext=None):
|
|
||||||
if path is None:
|
|
||||||
path = os.environ.get('PATH', []).split(os.pathsep)
|
|
||||||
if isinstance(path, six.string_types):
|
|
||||||
path = [path]
|
|
||||||
# check if there are funny path extensions for executables, e.g. Windows
|
|
||||||
if pathext is None:
|
|
||||||
pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD').split(os.pathsep)
|
|
||||||
# don't use extensions if the command ends with one of them
|
|
||||||
for ext in pathext:
|
|
||||||
if cmd.endswith(ext):
|
|
||||||
pathext = ['']
|
|
||||||
break
|
|
||||||
# check if we find the command on PATH
|
|
||||||
for p in path:
|
|
||||||
f = os.path.join(p, cmd)
|
|
||||||
if os.path.isfile(f):
|
|
||||||
return f
|
|
||||||
for ext in pathext:
|
|
||||||
fext = f + ext
|
|
||||||
if os.path.isfile(fext):
|
|
||||||
return fext
|
|
||||||
return None
|
|
||||||
|
|
||||||
# checks if it can find xgettext on the PATH and
|
# checks if it can find xgettext on the PATH and
|
||||||
# imports the extraction tests if yes
|
# imports the extraction tests if yes
|
||||||
xgettext_cmd = find_command('xgettext')
|
xgettext_cmd = find_command('xgettext')
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from django.core import management
|
from django.core import management
|
||||||
from django.core.management.base import CommandError
|
from django.core.management import CommandError
|
||||||
from django.test import TestCase
|
from django.core.management.utils import popen_wrapper
|
||||||
|
from django.test import SimpleTestCase
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.six import StringIO
|
from django.utils.six import StringIO
|
||||||
|
|
||||||
|
|
||||||
class CommandTests(TestCase):
|
class CommandTests(SimpleTestCase):
|
||||||
def test_command(self):
|
def test_command(self):
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
management.call_command('dance', stdout=out)
|
management.call_command('dance', stdout=out)
|
||||||
|
@ -58,3 +59,9 @@ class CommandTests(TestCase):
|
||||||
with translation.override('pl'):
|
with translation.override('pl'):
|
||||||
management.call_command('leave_locale_alone_true', stdout=out)
|
management.call_command('leave_locale_alone_true', stdout=out)
|
||||||
self.assertEqual(out.getvalue(), "pl\n")
|
self.assertEqual(out.getvalue(), "pl\n")
|
||||||
|
|
||||||
|
|
||||||
|
class UtilsTests(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_no_existent_external_program(self):
|
||||||
|
self.assertRaises(CommandError, popen_wrapper, ['a_42_command_that_doesnt_exist_42'])
|
||||||
|
|
Loading…
Reference in New Issue