web/lib/django/core/management/commands/makemessages.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 import re
       
     2 import os
       
     3 import sys
       
     4 import glob
       
     5 import warnings
       
     6 from itertools import dropwhile
       
     7 from optparse import make_option
       
     8 
       
     9 from django.core.management.base import CommandError, BaseCommand
       
    10 
       
    11 try:
       
    12     set
       
    13 except NameError:
       
    14     from sets import Set as set     # For Python 2.3
       
    15 
       
    16 # Intentionally silence DeprecationWarnings about os.popen3 in Python 2.6. It's
       
    17 # still sensible for us to use it, since subprocess didn't exist in 2.3.
       
    18 warnings.filterwarnings('ignore', category=DeprecationWarning, message=r'os\.popen3')
       
    19 
       
    20 pythonize_re = re.compile(r'\n\s*//')
       
    21 
       
    22 def handle_extensions(extensions=('html',)):
       
    23     """
       
    24     organizes multiple extensions that are separated with commas or passed by
       
    25     using --extension/-e multiple times.
       
    26 
       
    27     for example: running 'django-admin makemessages -e js,txt -e xhtml -a'
       
    28     would result in a extension list: ['.js', '.txt', '.xhtml']
       
    29 
       
    30     >>> handle_extensions(['.html', 'html,js,py,py,py,.py', 'py,.py'])
       
    31     ['.html', '.js']
       
    32     >>> handle_extensions(['.html, txt,.tpl'])
       
    33     ['.html', '.tpl', '.txt']
       
    34     """
       
    35     ext_list = []
       
    36     for ext in extensions:
       
    37         ext_list.extend(ext.replace(' ','').split(','))
       
    38     for i, ext in enumerate(ext_list):
       
    39         if not ext.startswith('.'):
       
    40             ext_list[i] = '.%s' % ext_list[i]
       
    41 
       
    42     # we don't want *.py files here because of the way non-*.py files
       
    43     # are handled in make_messages() (they are copied to file.ext.py files to
       
    44     # trick xgettext to parse them as Python files)
       
    45     return set([x for x in ext_list if x != '.py'])
       
    46 
       
    47 def make_messages(locale=None, domain='django', verbosity='1', all=False, extensions=None):
       
    48     """
       
    49     Uses the locale directory from the Django SVN tree or an application/
       
    50     project to process all
       
    51     """
       
    52     # Need to ensure that the i18n framework is enabled
       
    53     from django.conf import settings
       
    54     if settings.configured:
       
    55         settings.USE_I18N = True
       
    56     else:
       
    57         settings.configure(USE_I18N = True)
       
    58 
       
    59     from django.utils.translation import templatize
       
    60 
       
    61     if os.path.isdir(os.path.join('conf', 'locale')):
       
    62         localedir = os.path.abspath(os.path.join('conf', 'locale'))
       
    63     elif os.path.isdir('locale'):
       
    64         localedir = os.path.abspath('locale')
       
    65     else:
       
    66         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.")
       
    67 
       
    68     if domain not in ('django', 'djangojs'):
       
    69         raise CommandError("currently makemessages only supports domains 'django' and 'djangojs'")
       
    70 
       
    71     if (locale is None and not all) or domain is None:
       
    72         # backwards compatible error message
       
    73         if not sys.argv[0].endswith("make-messages.py"):
       
    74             message = "Type '%s help %s' for usage.\n" % (os.path.basename(sys.argv[0]), sys.argv[1])
       
    75         else:
       
    76             message = "usage: make-messages.py -l <language>\n   or: make-messages.py -a\n"
       
    77         raise CommandError(message)
       
    78 
       
    79     # xgettext versions prior to 0.15 assumed Python source files were encoded
       
    80     # in iso-8859-1, and produce utf-8 output.  In the case where xgettext is
       
    81     # given utf-8 input (required for Django files with non-ASCII characters),
       
    82     # this results in a utf-8 re-encoding of the original utf-8 that needs to be
       
    83     # undone to restore the original utf-8.  So we check the xgettext version
       
    84     # here once and set a flag to remember if a utf-8 decoding needs to be done
       
    85     # on xgettext's output for Python files.  We default to assuming this isn't
       
    86     # necessary if we run into any trouble determining the version.
       
    87     xgettext_reencodes_utf8 = False
       
    88     (stdin, stdout, stderr) = os.popen3('xgettext --version', 't')
       
    89     match = re.search(r'(?P<major>\d+)\.(?P<minor>\d+)', stdout.read())
       
    90     if match:
       
    91         xversion = (int(match.group('major')), int(match.group('minor')))
       
    92         if xversion < (0, 15):
       
    93             xgettext_reencodes_utf8 = True
       
    94  
       
    95     languages = []
       
    96     if locale is not None:
       
    97         languages.append(locale)
       
    98     elif all:
       
    99         locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir)) 
       
   100         languages = [os.path.basename(l) for l in locale_dirs]
       
   101     
       
   102     for locale in languages:
       
   103         if verbosity > 0:
       
   104             print "processing language", locale
       
   105         basedir = os.path.join(localedir, locale, 'LC_MESSAGES')
       
   106         if not os.path.isdir(basedir):
       
   107             os.makedirs(basedir)
       
   108 
       
   109         pofile = os.path.join(basedir, '%s.po' % domain)
       
   110         potfile = os.path.join(basedir, '%s.pot' % domain)
       
   111 
       
   112         if os.path.exists(potfile):
       
   113             os.unlink(potfile)
       
   114 
       
   115         all_files = []
       
   116         for (dirpath, dirnames, filenames) in os.walk("."):
       
   117             all_files.extend([(dirpath, f) for f in filenames])
       
   118         all_files.sort()
       
   119         for dirpath, file in all_files:
       
   120             file_base, file_ext = os.path.splitext(file)
       
   121             if domain == 'djangojs' and file_ext == '.js':
       
   122                 if verbosity > 1:
       
   123                     sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
       
   124                 src = open(os.path.join(dirpath, file), "rU").read()
       
   125                 src = pythonize_re.sub('\n#', src)
       
   126                 thefile = '%s.py' % file
       
   127                 open(os.path.join(dirpath, thefile), "w").write(src)
       
   128                 cmd = 'xgettext -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (domain, os.path.join(dirpath, thefile))
       
   129                 (stdin, stdout, stderr) = os.popen3(cmd, 't')
       
   130                 msgs = stdout.read()
       
   131                 errors = stderr.read()
       
   132                 if errors:
       
   133                     raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
       
   134                 old = '#: '+os.path.join(dirpath, thefile)[2:]
       
   135                 new = '#: '+os.path.join(dirpath, file)[2:]
       
   136                 msgs = msgs.replace(old, new)
       
   137                 if os.path.exists(potfile):
       
   138                     # Strip the header
       
   139                     msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
       
   140                 else:
       
   141                     msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
       
   142                 if msgs:
       
   143                     open(potfile, 'ab').write(msgs)
       
   144                 os.unlink(os.path.join(dirpath, thefile))
       
   145             elif domain == 'django' and (file_ext == '.py' or file_ext in extensions):
       
   146                 thefile = file
       
   147                 if file_ext in extensions:
       
   148                     src = open(os.path.join(dirpath, file), "rU").read()
       
   149                     thefile = '%s.py' % file
       
   150                     try:
       
   151                         open(os.path.join(dirpath, thefile), "w").write(templatize(src))
       
   152                     except SyntaxError, msg:
       
   153                         msg = "%s (file: %s)" % (msg, os.path.join(dirpath, file))
       
   154                         raise SyntaxError(msg)
       
   155                 if verbosity > 1:
       
   156                     sys.stdout.write('processing file %s in %s\n' % (file, dirpath))
       
   157                 cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % (
       
   158                     domain, os.path.join(dirpath, thefile))
       
   159                 (stdin, stdout, stderr) = os.popen3(cmd, 't')
       
   160                 msgs = stdout.read()
       
   161                 errors = stderr.read()
       
   162                 if errors:
       
   163                     raise CommandError("errors happened while running xgettext on %s\n%s" % (file, errors))
       
   164 
       
   165                 if xgettext_reencodes_utf8:
       
   166                     msgs = msgs.decode('utf-8').encode('iso-8859-1')
       
   167 
       
   168                 if thefile != file:
       
   169                     old = '#: '+os.path.join(dirpath, thefile)[2:]
       
   170                     new = '#: '+os.path.join(dirpath, file)[2:]
       
   171                     msgs = msgs.replace(old, new)
       
   172                 if os.path.exists(potfile):
       
   173                     # Strip the header
       
   174                     msgs = '\n'.join(dropwhile(len, msgs.split('\n')))
       
   175                 else:
       
   176                     msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8')
       
   177                 if msgs:
       
   178                     open(potfile, 'ab').write(msgs)
       
   179                 if thefile != file:
       
   180                     os.unlink(os.path.join(dirpath, thefile))
       
   181 
       
   182         if os.path.exists(potfile):
       
   183             (stdin, stdout, stderr) = os.popen3('msguniq --to-code=utf-8 "%s"' % potfile, 't')
       
   184             msgs = stdout.read()
       
   185             errors = stderr.read()
       
   186             if errors:
       
   187                 raise CommandError("errors happened while running msguniq\n%s" % errors)
       
   188             open(potfile, 'w').write(msgs)
       
   189             if os.path.exists(pofile):
       
   190                 (stdin, stdout, stderr) = os.popen3('msgmerge -q "%s" "%s"' % (pofile, potfile), 't')
       
   191                 msgs = stdout.read()
       
   192                 errors = stderr.read()
       
   193                 if errors:
       
   194                     raise CommandError("errors happened while running msgmerge\n%s" % errors)
       
   195             open(pofile, 'wb').write(msgs)
       
   196             os.unlink(potfile)
       
   197 
       
   198 
       
   199 class Command(BaseCommand):
       
   200     option_list = BaseCommand.option_list + (
       
   201         make_option('--locale', '-l', default=None, dest='locale',
       
   202             help='Creates or updates the message files only for the given locale (e.g. pt_BR).'),
       
   203         make_option('--domain', '-d', default='django', dest='domain',
       
   204             help='The domain of the message files (default: "django").'),
       
   205         make_option('--all', '-a', action='store_true', dest='all',
       
   206             default=False, help='Reexamines all source code and templates for new translation strings and updates all message files for all available languages.'),
       
   207         make_option('--extension', '-e', dest='extensions',
       
   208             help='The file extension(s) to examine (default: ".html", separate multiple extensions with commas, or use -e multiple times)',
       
   209             action='append'),
       
   210     )
       
   211     help = "Runs over the entire source tree of the current directory and pulls out all strings marked for translation. It creates (or updates) a message file in the conf/locale (in the django tree) or locale (for project and application) directory."
       
   212 
       
   213     requires_model_validation = False
       
   214     can_import_settings = False
       
   215 
       
   216     def handle(self, *args, **options):
       
   217         if len(args) != 0:
       
   218             raise CommandError("Command doesn't accept any arguments")
       
   219 
       
   220         locale = options.get('locale')
       
   221         domain = options.get('domain')
       
   222         verbosity = int(options.get('verbosity'))
       
   223         process_all = options.get('all')
       
   224         extensions = options.get('extensions') or ['html']
       
   225 
       
   226         if domain == 'djangojs':
       
   227             extensions = []
       
   228         else:
       
   229             extensions = handle_extensions(extensions)
       
   230 
       
   231         if '.js' in extensions:
       
   232             raise CommandError("JavaScript files should be examined by using the special 'djangojs' domain only.")
       
   233 
       
   234         make_messages(locale, domain, verbosity, process_all, extensions)